Skip to content
This repository has been archived by the owner on Jan 22, 2024. It is now read-only.

Implementation of review comments for classpath and jar #196

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,354 @@
/*
* (C) Copyright 2012, IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.ibm.jaggr.core.impl.resource;

import com.ibm.jaggr.core.resource.IResource;
import com.ibm.jaggr.core.resource.IResourceVisitor;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
* An implementation of {@link IResource} for classpath resources
*/
public class ClasspathResource implements IResource {
static final Logger log = Logger.getLogger(ClasspathResource.class.getName());


final String zipEntryUri;
final ArrayList<String> zipFileEntries;
URL classpathUrl;
URI classpathUri;
String classpathName;
String zipFileEntry;
ZipEntry zipEntry;
ZipFile zipFile;



/**
* Public constructor used by factory
*
* @param entry
* the entry within the jar
* @param entries
* list of all entries within the jar
*/
public ClasspathResource(String entry, ArrayList<String> entries) {
zipEntryUri = entry;
zipFileEntries = entries;
try {
if (entry.startsWith("file:") && entry.contains("!")) { //$NON-NLS-1$ //$NON-NLS-2$
String[] split = entry.split("!"); //$NON-NLS-1$
classpathUrl = new URL(split[0]);
classpathUri = new URI(split[0]);
classpathName = (split[0].lastIndexOf("/") != -1) ? split[0].substring(split[0].lastIndexOf("/") + 1) : split[0]; //$NON-NLS-1$ //$NON-NLS-2$
zipFileEntry = split[1].startsWith("/") ? split[1].substring(1) : split[1]; //$NON-NLS-1$
// need to remove the forward slash

zipFile = new ZipFile(new File(classpathUri));
zipEntry = zipFile.getEntry(zipFileEntry);
} else {
throw new ClassPathResourceException("Improper classpath resource"); //$NON-NLS-1$
}
} catch (Exception e) {
e.printStackTrace();
}
}

/*
* (non-Javadoc)
*
* @see com.ibm.jaggr.service.modules.Resource#getURI()
*/
@Override
public URI getURI() {
return URI.create("jar:" + zipEntryUri); //$NON-NLS-1$
}

/*
* (non-Javadoc)
*
* @see com.ibm.jaggr.service.modules.Resource#getPath()
*/
@Override
public String getPath() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still doesn't look right. If zipFileEntry specifies a jar file name (e.g. jarfile!/pathComponent), then getPath() needs to return only the part after the !/ separator. The jar file name is not part of the path, just like a host name is not part of an HTTP resource path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable zipFileEntry is initialized in the ctor of ClasspathResource in such a way that it is actually the path after the jar name. So we are good here.

return "classpath:///" + zipFileEntry; //$NON-NLS-1$
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having to override this method in the subclass, provide a protected method that returns the scheme string and override that. This will enable you to reuse more code (e.g. in resolve()).

For example, getPath would be implemented as follows:
return getScheme() + ":///: + zipFileEntry;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I believe that getPath() shouldn't include a scheme at all. It;s supposed to return the path component of the resource URI, which is the part of the URI following the scheme and host components.

/*
* (non-Javadoc)
*
* @see com.ibm.jaggr.service.modules.Resource#exists()
*/
@Override
public boolean exists() {
return lastModified() != 0;
}

/*
* (non-Javadoc)
*
* @see com.ibm.jaggr.service.modules.Resource#lastModified()
*/
@Override
public long lastModified() {
long lastmod = 0L;
try {
lastmod = getURI().toURL().openConnection().getLastModified();
} catch (IOException e) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, e.getMessage(), e);
}
}
return lastmod;
}

/* (non-Javadoc)
* @see com.ibm.jaggr.service.resource.IResource#resolve(java.net.URI)
*/
@Override
public IResource resolve(String relative) {
String classpathUriString, finalclasspathString = null;
URI fileUri, finalFileUri = null;
ClasspathResource classpathResource = null;
try {
classpathUriString = getURI().toString();
if (classpathUriString.startsWith("jar:")) { //$NON-NLS-1$
classpathUriString = classpathUriString.substring(4);
}
fileUri = new URI(classpathUriString).resolve(relative);
finalclasspathString = "classpath:/" + fileUri; //$NON-NLS-1$
finalFileUri = new URI(finalclasspathString);
classpathResource = newInstance(finalFileUri);
} catch (Exception e) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, e.getMessage(), e);
}
}
return classpathResource;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this logic is that getURI().resolve(relative) doesnt work and throws an exception and thats because the jar uris of the type jar:/file:/.... doesnt work with resolve(). So I had to remove the jar: part and extract the uri starting with file:/ and perform the resolve operation. And then back convert to add classpath:/ in front of the uri.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's useful information. It would be good if it was in the code as comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work when relative starts with '/'. In this case, the relative path should replace any path component in the current URI, but leave the jar filename intact. Would be good to see some unit test cases for the various scenarios.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to test this. But it doesnt impact the sample application.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. In case the relative starts with '/', the entire path is replaced with relative which means the jar name is not returned. I tested using a sample program which constructs a file uri like file:/C:/test.jar!/x/y.js and applying resolve("/z") returns file:/z
Do we need to implement this ? Looks like we need to perform manual operations to do this task as its not handled by the API

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need some way to separate the jar file part of the URI from the path part. It sounds like java.net.URI can't handle this for you so you'll need to do it yourself. Basically, the steps would be as follows:

  • Extract path portion of the URI (everything after the !/, preceded with a /). This is what ClasspathResource.getPath() should be returning, so you could just call this.getPath() when that's fixed. You may be able to use classpathUrl.getPath() if it works, Maybe java.net.URL has better support for the jar protocol than java.net.URI.
  • Use a path only URI constructed from the path obtained above to resolve relative against.
  • Return a new URI constructed from the original URI and the new path.
    Something like the following might work.
if ("jar".equals(getScheme()) {
    URI pathOnlyUri = new URI(getPath() // should start with '/');
    URI resolved = pathOnlyUri.resolve(relative);
    int idx = getUri().toString().indexOf("!/");
    return new URI(getUri().toString().substring(0, idx+1) + resolved.getPath();
} else {
    return classpathUri.resolve(relative);
} 

I didn't try this code, so it might need some tweaking, but you get the idea...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already implemented this review comment

/*
* (non-Javadoc)
*
* @see com.ibm.jaggr.service.modules.Resource#getReader()
*/
@Override
public Reader getReader() throws IOException {
return new InputStreamReader(getURI().toURL().openConnection().getInputStream(), "UTF-8"); //$NON-NLS-1$
}

/* (non-Javadoc)
* @see com.ibm.jaggr.service.resource.IResource#getInputStream()
*/
@Override
public InputStream getInputStream() throws IOException {
return getURI().toURL().openConnection().getInputStream();
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.modules.Resource#walkTree(com.ibm.jaggr.modules
* .ResourceVisitor, boolean)
*/
@Override
public void walkTree(IResourceVisitor visitor) throws IOException {
if (!exists()) {
throw new FileNotFoundException(zipEntry.getName());
}
if (!zipEntry.isDirectory()) {
return;
}
recurse(zipEntry, visitor);
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.resource.IResource#asVisitorResource()
*/
@Override
public VisitorResource asVisitorResource() throws IOException {
if (!exists()) {
throw new IOException(zipEntry.getName());
}
return new VisitorResource(zipEntry, zipEntryUri);
}

/**
* Internal method for recursing sub directories.
*
* @param file
* The file object
* @param visitor
* The {@link IResourceVisitor} to call for non-folder resources
* @throws IOException
*/
private void recurse(ZipEntry file, IResourceVisitor visitor)
throws IOException {
ArrayList<ZipEntry> files = getChildZipEntries(file);

if (files == null) {
return;
}
for (ZipEntry child : files) {
String childName = child.getName().substring(file.getName().length());
String childEntryUri = zipEntryUri + childName;
visitor.visitResource(new VisitorResource(child,
childEntryUri), childName);
}
}

protected ClasspathResource newInstance(URI uri) {
return (ClasspathResource) new ClasspathResourceFactory().newResource(uri);
}

private ArrayList<ZipEntry> getChildZipEntries(ZipEntry entry){
String entryName = entry.getName();
ArrayList<ZipEntry> files = new ArrayList<ZipEntry>();
for (int i = 0; i < zipFileEntries.size(); i ++){
if(zipFileEntries.get(i).startsWith(entryName) && !(zipFileEntries.get(i).equalsIgnoreCase(entryName)) && zipFileEntries.get(i).endsWith(".js")){ //$NON-NLS-1$
files.add(zipFile.getEntry(zipFileEntries.get(i)));
}
}

return files;
}

/**
* Implementation of {@link IResourceVisitor.Resource} for classpath resources.
*/
private static class VisitorResource implements IResourceVisitor.Resource {

ZipEntry entry;
String ZipFileUri;


private VisitorResource(ZipEntry entry, String uri) {
this.entry = entry;
this.ZipFileUri = uri;

}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.resource.IResourceVisitor.Resource
* #isFolder()
*/
@Override
public boolean isFolder() {
if (entry == null) {
return false;
} else {
return entry.isDirectory();
}
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.modules.ResourceVisitor.Resource#
* getURI()
*/
@Override
public URI getURI() {
return URI.create("jar:" + ZipFileUri); //$NON-NLS-1$
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.modules.ResourceVisitor.Resource#
* getPath()
*/
@Override
public String getPath() {
return ZipFileUri;
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.modules.ResourceVisitor.Resource#
* lastModified()
*/
@Override
public long lastModified() {
long lastmodified = 0;
try {
lastmodified = getURI().toURL().openConnection().getLastModified();
} catch (IOException e) {
if (log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, e.getMessage(), e);
}
}
return lastmodified;
}

/*
* (non-Javadoc)
*
* @see
* com.ibm.jaggr.service.modules.ResourceVisitor.Resource#
* getReader()
*/
@Override
public Reader getReader() throws IOException {
return new InputStreamReader(getURI().toURL().openConnection().getInputStream(), "UTF-8"); //$NON-NLS-1$
}

/* (non-Javadoc)
* @see com.ibm.jaggr.service.resource.IResourceVisitor.Resource#getInputStream()
*/
@Override
public InputStream getInputStream() throws IOException {
return getURI().toURL().openConnection().getInputStream();
}
}

public class ClassPathResourceException extends Exception {

public ClassPathResourceException(String string) {
super(string);
}

private static final long serialVersionUID = 1L;

}


}
Loading