blob: 29cbb11fdb8b64c986810ac6c2a0c6a5e87a6956 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
* is available at http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* VMware Inc.
*****************************************************************************/
package org.eclipse.gemini.blueprint.test.internal.util.jar;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.Manifest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.test.internal.util.jar.storage.MemoryStorage;
import org.eclipse.gemini.blueprint.test.internal.util.jar.storage.Storage;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StringUtils;
/**
* Helper class for creating Jar files. Note that this class is stateful and the
* same instance should not be reused.
*
* @author Costin Leau
*
*/
public class JarCreator {
private static final Log log = LogFactory.getLog(JarCreator.class);
public static final String EVERYTHING_PATTERN = "/**/*";
private static final String CLASS_EXT = ".class";
private String[] contentPattern = new String[] { EVERYTHING_PATTERN };
private ResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
private Storage storage = new MemoryStorage();
private String rootPath = determineRootPath();
private boolean addFolders = true;
/** collection of packages contained by this jar */
private Collection containedPackages = new TreeSet();
/**
* Resources' root path (the root path does not become part of the jar).
*
* @return the root path
*/
public String determineRootPath() {
return Thread.currentThread().getContextClassLoader().getResource(".").toString();
}
/**
* Actual jar creation.
*
* @param manifest to use
* @param entries array of resource to include in the jar
* @return the number of bytes written to the underlying stream.
*
* @throws IOException
*/
protected int addJarContent(Manifest manifest, Map entries) throws IOException {
// load manifest
// add it to the jar
if (log.isTraceEnabled()) {
if (manifest != null)
log.trace("Adding MANIFEST.MF [" + manifest.getMainAttributes().entrySet() + "]");
log.trace("Adding entries:");
Set key = entries.keySet();
for (Iterator iter = key.iterator(); iter.hasNext();) {
log.trace(iter.next());
}
}
return JarUtils.createJar(manifest, entries, storage.getOutputStream());
}
/**
* Create a jar using the current settings and return a {@link Resource}
* pointing to the jar.
*
* @param manifest
*/
public Resource createJar(Manifest manifest) {
return createJar(manifest, resolveContent());
}
/**
* Create a jar using the current settings and return a {@link Resource}
* pointing to the jar.
*
* @param manifest
*/
public Resource createJar(Manifest manifest, Map content) {
try {
addJarContent(manifest, content);
return storage.getResource();
}
catch (IOException ex) {
throw (RuntimeException) new IllegalStateException("Cannot create jar").initCause(ex);
}
}
/**
* Small utility method used for determining the file name by striping the
* root path from the file full path.
*
* @param rootPath
* @param resource
* @return
*/
private String determineRelativeName(String rootPath, Resource resource) {
try {
String path = StringUtils.cleanPath(resource.getURL().getPath());
return path.substring(path.indexOf(rootPath) + rootPath.length());
}
catch (IOException ex) {
throw (RuntimeException) new IllegalArgumentException("illegal resource " + resource.toString()).initCause(ex);
}
}
/**
* Transform the pattern and rootpath into actual resources.
*
* @return
* @throws Exception
*/
private Resource[][] resolveResources() {
ResourcePatternResolver resolver = getPatternResolver();
String[] patterns = getContentPattern();
Resource[][] resources = new Resource[patterns.length][];
// transform Strings into Resources
for (int i = 0; i < patterns.length; i++) {
StringBuilder buffer = new StringBuilder(rootPath);
// do checking on lost slashes
if (!rootPath.endsWith(JarUtils.SLASH) && !patterns[i].startsWith(JarUtils.SLASH))
buffer.append(JarUtils.SLASH);
buffer.append(patterns[i]);
try {
resources[i] = resolver.getResources(buffer.toString());
}
catch (IOException ex) {
IllegalStateException re = new IllegalStateException("cannot resolve pattern " + buffer.toString());
re.initCause(ex);
throw re;
}
}
return resources;
}
/**
* Resolve the jar content based on its path. Will return a map containing
* the entries relative to the jar root path as keys and Spring Resource
* pointing to the actual resources as values. It will also determine the
* packages contained by this package.
*
* @return
*/
public Map resolveContent() {
Resource[][] resources = resolveResources();
URL rootURL;
String rootP = getRootPath();
try {
rootURL = new URL(rootP);
}
catch (MalformedURLException ex) {
throw (RuntimeException) new IllegalArgumentException("illegal root path given " + rootP).initCause(ex);
}
String rootPath = StringUtils.cleanPath(rootURL.getPath());
// remove duplicates
Map entries = new TreeMap();
// save contained bundle packages
containedPackages.clear();
// empty stream used for folders
Resource folderResource = new ByteArrayResource(new byte[0]);
// add folder entries also
for (int i = 0; i < resources.length; i++) {
for (int j = 0; j < resources[i].length; j++) {
String relativeName = determineRelativeName(rootPath, resources[i][j]);
// be consistent when adding resources to jar
if (!relativeName.startsWith("/"))
relativeName = "/" + relativeName;
entries.put(relativeName, resources[i][j]);
// look for class entries
if (relativeName.endsWith(CLASS_EXT)) {
// determine package (exclude first char)
String clazzName = relativeName.substring(1, relativeName.length() - CLASS_EXT.length()).replace(
'/', '.');
// remove class name
int index = clazzName.lastIndexOf('.');
if (index > 0)
clazzName = clazzName.substring(0, index);
// add it to the collection
containedPackages.add(clazzName);
}
String token = relativeName;
// get folder and walk up to the root
if (addFolders) {
// add META-INF
entries.put("/META-INF/", folderResource);
int slashIndex;
// stop at root folder
while ((slashIndex = token.lastIndexOf('/')) > 1) {
// add the folder with trailing /
entries.put(token.substring(0, slashIndex + 1), folderResource);
// walk the tree
token = token.substring(0, slashIndex);
}
// add root folder
//entries.put("/", folderResource);
}
}
}
if (log.isTraceEnabled())
log.trace("The following packages were discovered in the bundle: " + containedPackages);
return entries;
}
public Collection getContainedPackages() {
return containedPackages;
}
/**
* @return Returns the contentPattern.
*/
public String[] getContentPattern() {
return contentPattern;
}
/**
* Pattern for content matching. Note that using {@link #EVERYTHING_PATTERN}
* can become problematic on windows due to file system locking.
*
* @param contentPattern The contentPattern to set.
*/
public void setContentPattern(String[] contentPattern) {
this.contentPattern = contentPattern;
}
/**
* @return Returns the patternResolver.
*/
public ResourcePatternResolver getPatternResolver() {
return patternResolver;
}
/**
* @param patternResolver The patternResolver to set.
*/
public void setPatternResolver(ResourcePatternResolver patternResolver) {
this.patternResolver = patternResolver;
}
/**
* @return Returns the jarStorage.
*/
public Storage getStorage() {
return storage;
}
/**
* @param jarStorage The jarStorage to set.
*/
public void setStorage(Storage jarStorage) {
this.storage = jarStorage;
}
/**
* @param
*/
public String getRootPath() {
return rootPath;
}
/**
* @param rootPath The rootPath to set.
*/
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}
/**
* @return Returns the addFolders.
*/
public boolean isAddFolders() {
return addFolders;
}
/**
* Whether the folders in which the files reside, should be added to the
* archive. Default is true since otherwise, the archive will contains only
* files and no folders.
*
* @param addFolders The addFolders to set.
*/
public void setAddFolders(boolean addFolders) {
this.addFolders = addFolders;
}
}