blob: 592b3587d776479fd71113fcb6bd0403586e2039 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2010 IBM Corporation, Red Hat, and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.common.internal.modulecore.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jem.util.emf.workbench.WorkbenchByteArrayOutputStream;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.componentcore.resources.IVirtualFile;
import com.ibm.icu.util.StringTokenizer;
public class ManifestUtilities {
public static final String MANIFEST_HEADER = "Manifest-Version: 1.0\r\nClass-Path: \r\n\r\n"; //$NON-NLS-1$
public static void createManifestFile(IFile file) throws CoreException, IOException {
try {
WorkbenchByteArrayOutputStream out = new WorkbenchByteArrayOutputStream(file);
out.write(MANIFEST_HEADER.getBytes());
out.close();
} catch (IOException ioe) {
throw ioe;
}
}
public static String[] getTokens(String aString) {
return getTokens(aString, null);
}
public static String[] getTokens(String aString, String delimiter) {
StringTokenizer tok = (delimiter == null) ? new StringTokenizer(aString) : new StringTokenizer(aString, delimiter);
int size = tok.countTokens();
String[] tokens = new String[size];
for (int i = 0; i < size && tok.hasMoreTokens(); i++) {
tokens[i] = tok.nextToken();
}
return tokens;
}
/**
* getValueIgnoreKeyCase method comment.
*/
public static java.lang.String getValueIgnoreKeyCase(java.lang.String key, java.util.jar.Attributes attr) {
Iterator keysAndValues = attr.entrySet().iterator();
while (keysAndValues.hasNext()) {
Map.Entry entry = (Map.Entry) keysAndValues.next();
String entryKey = entry.getKey().toString();
if (entryKey.equalsIgnoreCase(key))
return entry.getValue() == null ? null : entry.getValue().toString();
}
return null;
}
public static ArchiveManifest getManifest(IVirtualComponent component, IPath manifestPath) {
if( !component.isBinary() )
return getNonBinaryComponentManifest(component, manifestPath);
return getBinaryComponentManifest(component, manifestPath);
}
public static ArchiveManifest getBinaryComponentManifest(IVirtualComponent component, IPath manifestPath) {
java.io.File file = component.getAdapter(File.class);
if( file != null && file.exists()) {
ArchiveManifest manifest = readBinaryManifest(file, manifestPath);
return manifest;
}
return null;
}
public static ArchiveManifest getManifest(IFile f) {
File f2 = f.getLocation().toFile();
return getManifest(f2);
}
public static ArchiveManifest getManifest(File f) {
if( f != null && f.exists()) {
InputStream in;
try {
in = new FileInputStream(f);
ArchiveManifest manifest = new ArchiveManifestImpl(in);
return manifest;
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
return null;
}
public static void writeManifest(IFile aFile, ArchiveManifest manifest) throws java.io.IOException {
OutputStream out = new WorkbenchByteArrayOutputStream(aFile);
manifest.writeSplittingClasspath(out);
out.close();
}
public static ArchiveManifest getNonBinaryComponentManifest(IVirtualComponent component, IPath manifestPath) {
try {
if(!component.isBinary()){
IVirtualFile vManifest = component.getRootFolder().getFile(manifestPath);
if (vManifest.exists()) {
IFile manifestFile = vManifest.getUnderlyingFile();
InputStream in = null;
try {
in = manifestFile.getContents();
ArchiveManifest manifest = new ArchiveManifestImpl(in);
return manifest;
} finally {
if (in != null) {
in.close();
in = null;
}
}
}
}
} catch( IOException ioe ) {
} catch(CoreException ce) {
}
return null;
}
public static String[] getManifestClasspath(IVirtualComponent component, IPath manifestPath) {
ArchiveManifest mf = getManifest(component, manifestPath);
if( mf != null )
return mf.getClassPathTokenized();
return new String[]{};
}
public static ArchiveManifest readBinaryManifest(File file, IPath manifestPath) {
ArchiveManifest manifest = null;
ZipFile zipFile = null;
if( file != null ) {
try {
zipFile = ManifestUtilities.newZipFile(file);
ZipEntry entry = zipFile.getEntry(manifestPath.toString());
if( entry != null ) {
InputStream entryStream = getInputstreamForZipEntry(zipFile, manifestPath.toString());
manifest = new ArchiveManifestImpl(entryStream);
zipFile.close();
}
} catch( IOException ioe) {
if( zipFile != null ) {
try {
zipFile.close();
} catch( IOException ioe2) {}
}
}
}
return manifest;
}
public static InputStream getInputstreamForZipEntry(ZipFile zipFile, String uri) throws IOException {
try {
ZipEntry entry = zipFile.getEntry(uri);
if (entry == null) {
// this is a hack, but zip files are sensitive to the difference
// between '/' and '\\'
// so the hack is to try all combinations to see if any exist
char[] chars = uri.toCharArray();
int[] slashIndices = new int[chars.length];
int slashCount = 0;
for (int i = 0; i < uri.length(); i++) {
if (chars[i] == '/' || chars[i] == '\\') {
slashIndices[slashCount] = i;
slashCount++;
}
}
int slashPow = (int) Math.pow(2, slashCount);
boolean foundIt = false;
for (int i = 0; i < slashPow && !foundIt; i++) {
for (int j = 0; j < slashCount; j++) {
if ((i >> j & 1) == 1) {
chars[slashIndices[j]] = '/';
} else {
chars[slashIndices[j]] = '\\';
}
}
entry = zipFile.getEntry(new String(chars));
if (entry != null) {
foundIt = true;
}
}
if (entry == null) {
Exception del = new FileNotFoundException(uri);
throw new IOException(del.toString());
}
}
return new java.io.BufferedInputStream(zipFile.getInputStream(entry));
} catch (IllegalStateException zipClosed) {
throw new IOException(zipClosed.toString());
}
}
public static String[] getNonBinaryComponentManifestClasspath(IVirtualComponent component, IPath manifestPath)
throws IOException, CoreException {
String[] manifestClasspath = null;
if(!component.isBinary()){
IVirtualFile vManifest = component.getRootFolder().getFile(manifestPath);
if (vManifest.exists()) {
IFile manifestFile = vManifest.getUnderlyingFile();
InputStream in = null;
try {
in = manifestFile.getContents();
ArchiveManifest manifest = new ArchiveManifestImpl(in);
manifestClasspath = manifest.getClassPathTokenized();
} finally {
if (in != null) {
in.close();
in = null;
}
}
}
}
return manifestClasspath;
}
public static ZipFile newZipFile(String fileName)throws ZipException, IOException {
return ManifestUtilities.newZipFile(new File(fileName), ZipFile.OPEN_READ);
}
public static ZipFile newZipFile(File aFile)throws ZipException, IOException {
return ManifestUtilities.newZipFile(aFile, ZipFile.OPEN_READ);
}
/**
* Utility to create ZipFiles which avoid memory leaks
* because closing them fails to close open inputstreams.
* There is a SUN bug open for this: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6735255
* but it looks like the "fix" will be to change the Javadoc :-(
* @param aFile mode
* @return
* @throws ZipException
* @throws IOException
*/
public static ZipFile newZipFile(File aFile, int mode) throws ZipException, IOException {
return new ZipFile(aFile, mode){
Collection <InputStream> openStreams = null;
@Override
public InputStream getInputStream(ZipEntry entry) throws IOException {
InputStream in = super.getInputStream(entry);
if(in != null){
if(openStreams == null){
openStreams = new ArrayList<InputStream>();
}
openStreams.add(in);
}
return in;
}
@Override
public void close() throws IOException {
closeOpenStreams();
super.close();
}
private void closeOpenStreams() {
if(openStreams != null){
for (Iterator iterator = openStreams.iterator(); iterator.hasNext();) {
InputStream in = (InputStream) iterator.next();
try {
in.close();
} catch (IOException e) {
org.eclipse.jem.util.logger.proxy.Logger.getLogger().logWarning(e);
}
iterator.remove();
}
}
}
};
}
public static ArchiveManifest readManifest(IFile aFile) {
InputStream in = null;
try {
if (aFile == null || !aFile.exists())
return null;
in = aFile.getContents();
return new ArchiveManifestImpl(in);
} catch (Exception ex) {
// TODO J2EEPlugin.logError(ex);
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException weTried) {
}
}
}
}
private static ConcurrentHashMap<String, String> manifestClasspaths = new ConcurrentHashMap<String, String>();
/**
* Generates new MANIFEST.MF with a dynamically updated classpath that is written to the specified
* output stream.
* @param manifestFile The current MANIFEST.MF file.
* @param dynamicURIs Is List of URIs to dynamically add to the manifest classpath.
* @param outputStream Stream to which the modified entry should be written.
* @throws IOException
* @throws FileNotFoundException
*/
public static void updateManifestClasspath(final IFile manifestFile, final List dynamicURIs, final OutputStream outputStream) throws IOException, FileNotFoundException {
updateManifestClasspathImpl(manifestFile, dynamicURIs, null, outputStream);
}
/**
* Generates new MANIFEST.MF with a dynamically updated classpath that is written to the specified
* output stream.
* @param manifestFile The current MANIFEST.MF file.
* @param dynamicURIs Is List of URIs to dynamically add to the manifest classpath.
* @param outputFile File to which the modified entry should be written.
* @throws IOException
* @throws FileNotFoundException
*/
public static void updateManifestClasspath(final IFile manifestFile, final List dynamicURIs, final File outputFile) throws IOException, FileNotFoundException {
updateManifestClasspathImpl(manifestFile, dynamicURIs, outputFile, null);
}
/**
* Generates new MANIFEST.MF with a dynamically updated classpath that is written to the specified
* file or output stream, with the stream taking precedence.
* @param manifestFile The current MANIFEST.MF file.
* @param dynamicURIs Is List of URIs to dynamically add to the manifest classpath.
* @param outputFile File to which the modified entry should be written.
* @param OutputStream stream Stream to which the modified entry should be written. If not null,
* the stream will be written and the outputFile ignored.
* @throws IOException
* @throws FileNotFoundException
*/
private static void updateManifestClasspathImpl(final IFile manifestFile, final List dynamicURIs, final File outputFile, final OutputStream stream) throws IOException, FileNotFoundException {
OutputStream outputStream = stream;
try {
InputStream in = null;
ArchiveManifest manifest = null;
try {
in = manifestFile.getContents();
manifest = new ArchiveManifestImpl(in);
} catch (CoreException ce) {
throw new IOException(ce.getLocalizedMessage());
} finally {
if (in != null) {
try {
in.close();
in = null;
} catch (IOException e) {
org.eclipse.jem.util.logger.proxy.Logger.getLogger().logWarning(e);
}
}
}
final String[] manifestClasspath = manifest.getClassPathTokenized();
final List updatedCP = new ArrayList();
for (int i = 0; i < manifestClasspath.length; i++) {
updatedCP.add(manifestClasspath[i]);
}
// update manifest classpath to include dynamic entries
for (int j = 0; j < dynamicURIs.size(); j++) {
final String containerURI = (String) dynamicURIs.get(j);
// need to check existing entries to ensure it doesn't are exist on the classpath
boolean exists = false;
for (int i = 0; i < manifestClasspath.length; i++) {
if (manifestClasspath[i].equals(containerURI)) {
exists = true;
break;
}
}
if (!exists) {
updatedCP.add(containerURI);
}
}
final StringBuffer cpBuffer = new StringBuffer();
boolean first = true;
for (int j = 0; j < updatedCP.size(); j++) {
if (!first) {
cpBuffer.append(" "); //$NON-NLS-1$
} else {
first = false;
}
cpBuffer.append((String) updatedCP.get(j));
}
String cp = cpBuffer.toString();
// If we have an output stream, always write to the stream
if (outputStream != null) {
manifest.setClassPath(cp);
manifest.write(outputStream);
outputStream.flush();
}
// Else, without an output stream, conditionally update the specified file
else {
// To avoid making the internally generated manifest file appear to projects
// as a perpetually modified resource, check if the file needs to change.
// This checking helps with the usefulness of "delta" deployment with
// utility projects.
String manifestPath = manifestFile.getFullPath().toString();
String priorClasspath = manifestClasspaths.get(manifestPath);
if (priorClasspath == null || !priorClasspath.equals(cp) || !outputFile.exists()) {
manifestClasspaths.put(manifestPath, cp);
manifest.setClassPath(cp);
outputStream = new FileOutputStream(outputFile);
manifest.write(outputStream);
outputStream.flush();
}
}
} finally {
if (outputStream != null) {
outputStream.close();
}
}
}
}