blob: 15debb5e299a53cf40805a66e86bda7e7cd814da [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.wtp.releng.tools.component.adopters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.jdt.core.util.ClassFormatException;
import org.eclipse.jdt.core.util.IClassFileReader;
import org.eclipse.jdt.internal.core.util.ClassFileReader;
import org.eclipse.wtp.releng.tools.component.IClassVisitor;
import org.eclipse.wtp.releng.tools.component.IFileLocation;
import org.eclipse.wtp.releng.tools.component.ILocation;
import org.eclipse.wtp.releng.tools.component.ILocationChildrenIterator;
import org.eclipse.wtp.releng.tools.component.ILocationVisitor;
import org.eclipse.wtp.releng.tools.component.IZipLocation;
import org.eclipse.wtp.releng.tools.component.internal.Bundle;
import org.eclipse.wtp.releng.tools.component.internal.ComponentDepends;
import org.eclipse.wtp.releng.tools.component.internal.ComponentXML;
import org.eclipse.wtp.releng.tools.component.internal.FileLocation;
import org.eclipse.wtp.releng.tools.component.internal.Location;
import org.eclipse.wtp.releng.tools.component.internal.Package;
import org.eclipse.wtp.releng.tools.component.internal.Plugin;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
* The Plugin2API executable class will iterate over all of the plguins in the src location
* and determine the API's the classes in the those plugins provide. It uses Java2API to do
* this calculation and it will output this information to the outputDir provided.
*/
public class Plugin2API implements ILocationVisitor, IClassVisitor {
// Arguments which should be set
private Collection src;
private String outputDir;
// Instance variables
private Map lib2pluginId = new HashMap();
private boolean visitPlugins;
private List visitedPlugins;
private ComponentXML cachedCompXML;
// String constants
private static final String COMPONENT_XML = "/component.xml"; //$NON-NLS-1$
private static final String PLUGIN_XML = "plugin.xml"; //$NON-NLS-1$
private static final String FRAGMENT_XML = "fragment.xml"; //$NON-NLS-1$
private static final String MANIFEST_MF = "MANIFEST.MF"; //$NON-NLS-1$
private static final String DOT_CLASSPATH = ".classpath"; //$NON-NLS-1$
private static final String CLASS = ".class"; //$NON-NLS-1$
private static final String PLUGIN = "plugin"; //$NON-NLS-1$
private static final String ID = "id"; //$NON-NLS-1$
private static final String RUNTIME = "runtime"; //$NON-NLS-1$
private static final String LIBRARY = "library"; //$NON-NLS-1$
private static final String NAME = "name"; //$NON-NLS-1$
private static final String FRAGMENT = "fragment"; //$NON-NLS-1$
private static final String META_INF_MANIFEST = "META-INF/" + MANIFEST_MF; //$NON-NLS-1$
private static final String CLASSPATH = "classpath"; //$NON-NLS-1$
private static final String CLASSPATHENTRY = "classpathentry"; //$NON-NLS-1$
private static final String KIND = "kind"; //$NON-NLS-1$
private static final String OUTPUT = "output"; //$NON-NLS-1$
private static final String PATH = "path"; //$NON-NLS-1$
private static final String JAR = "jar"; //$NON-NLS-1$
/**
* @return String output location
*/
public String getOutputDir() {
return outputDir;
}
/**
* Set the output location.
* @param String outputDir
*/
public void setOutputDir(String outputDir) {
this.outputDir = addTrailingSeperator(outputDir);
}
/**
* Helper method to add a trailing seperator to the given string.
* @param s
* @return String
*/
protected String addTrailingSeperator(String s) {
// If the string does not already end with a seperator, add one
if (s != null && !s.endsWith("/") && !s.endsWith("\\")) { //$NON-NLS-1$ //$NON-NLS-2$
StringBuffer sb = new StringBuffer(s);
sb.append('/');
return sb.toString();
}
return s;
}
/**
* @return src Collection
*/
public Collection getSrc() {
return src;
}
/**
* Set the collection to a new collection with only the src argument passed.
* @param aSrc String
*/
public void setSrc(String aSrc) {
src = new ArrayList(1);
src.add(aSrc);
}
/**
* Set the source collection field.
* @param aSrc Collection
*/
public void setSrc(Collection aSrc) {
src = aSrc;
}
/**
* The execute method drives the operation of this class by forwarding each source
* location to the Java2API class. Then, visit all appropriate plugins to cache
* the API information provided.
*/
public void execute() {
Java2API java2API = new Java2API();
java2API.setOutputDir(outputDir);
java2API.setDefaultExclusive(false);
for (Iterator it = src.iterator(); it.hasNext();) {
java2API.setSrc((String)it.next());
java2API.execute();
}
// Get all the visited plugins which have API
visitedPlugins = java2API.getVisitedPlugins();
for (Iterator it = src.iterator(); it.hasNext();) {
visitPlugins = true;
ILocation srcLocation = Location.createLocation(new File((String)it.next()));
srcLocation.accept(this);
visitPlugins = false;
srcLocation.accept(this);
}
}
/**
* The visit implemention drives the visitor design for the given plugin and class location.
* @return boolean
*/
public boolean visit(String pluginId, ILocation classLoc) {
try {
IClassFileReader reader = read(classLoc);
// Get the fully qualified class name.
String className = new String(reader.getClassName()).replace('/', '.');
int i = className.lastIndexOf('.');
// Get the package name.
String pkgName = i != -1 ? className.substring(0, i) : ""; //$NON-NLS-1$
// Find the component xml for the given plugin id
ComponentXML compXML = getComponentXML(pluginId);
// If the package does not exist, we need to add it to the component.xml API list.
if (compXML.getPackage(pkgName) == null) {
Package pkg = new Package();
pkg.setName(pkgName);
pkg.setApi(Boolean.TRUE);
pkg.setExclusive(Boolean.FALSE);
compXML.addPackage(pkg);
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} catch (ClassFormatException cfe) {
throw new RuntimeException(cfe);
}
return true;
}
/**
* The read method will return an IClassFileReader for the given class location.
*
* @param classLoc
* @return IClassFileReader
* @throws IOException
* @throws ClassFormatException
*/
private IClassFileReader read(ILocation classLoc) throws IOException, ClassFormatException {
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
byte[] b = new byte[8192];
baos = new ByteArrayOutputStream(8192);
// Get input stream on class location and write the output stream
is = classLoc.getInputStream();
for (int read = is.read(b); read != -1; read = is.read(b))
baos.write(b, 0, read);
is.close();
baos.close();
// Return the ClassFileReader
return new ClassFileReader(baos.toByteArray(), IClassFileReader.CONSTANT_POOL | IClassFileReader.METHOD_INFOS | IClassFileReader.METHOD_BODIES | IClassFileReader.FIELD_INFOS | IClassFileReader.SUPER_INTERFACES);
} finally {
// Ensure the input stream is closed
if (is != null) {
try {
is.close();
} catch (IOException e) {}
}
// Ensure the output stream is closed
if (baos != null) {
try {
baos.close();
} catch (IOException e) {}
}
}
}
/**
* Get the component xml for the given plugin id, check the cache first.
* @param id
* @return ComponentXML
* @throws IOException
*/
private ComponentXML getComponentXML(String id) throws IOException {
if (cachedCompXML != null) {
if (cachedCompXML.getName().equals(id))
return cachedCompXML;
cachedCompXML.save();
}
// Get a new component.xml file based on output location
StringBuffer sb = new StringBuffer(outputDir);
sb.append(id);
sb.append(COMPONENT_XML);
File file = new File(sb.toString());
// Update the cache with a new component xml file
cachedCompXML = new ComponentXML();
cachedCompXML.setName(id);
cachedCompXML.setLocation(new FileLocation(file));
// Update the plugin id
Plugin plugin = new Plugin();
plugin.setId(id);
cachedCompXML.addPlugin(plugin);
// Update the component dependencies
ComponentDepends compDepends = new ComponentDepends();
compDepends.setUnrestricted(Boolean.TRUE);
cachedCompXML.setComponentDepends(compDepends);
// Load the component xml file if it exists
if (file.exists())
cachedCompXML.load();
return cachedCompXML;
}
/**
* This will forward to the appropriate accept method depending on the current location.
* @return boolean
*/
public boolean accept(ILocation location) {
String locationName = location.getName();
if (visitPlugins) {
if (locationName.endsWith(MANIFEST_MF)) {
acceptManifest(location);
} else if (Location.isArchive(locationName)) {
return acceptSingleJar(location);
} else if (locationName.endsWith(DOT_CLASSPATH)) {
acceptDotClasspath(location);
}
} else {
String idTemp = (String)lib2pluginId.get(locationName.replace('\\', '/'));
if (idTemp == null)
idTemp = (String)lib2pluginId.get(location.getAbsolutePath().replace('\\', '/'));
final String id = idTemp;
if (id != null) {
location.accept( new ILocationVisitor() {
public boolean accept(ILocation aLocation) {
if (aLocation.getName().endsWith(CLASS))
visit(id, aLocation);
return true;
}
}
);
}
}
if (location instanceof IZipLocation)
return true;
else if ((location instanceof IFileLocation) && ((IFileLocation)location).getFile().isDirectory())
return true;
else
return false;
}
/**
* Handle a manifest location of MANIFEST.MF
* @param location
*/
private void acceptManifest(ILocation location) {
String locationName = location.getName();
try {
Manifest manifest = new Manifest(location.getInputStream());
Attributes attrs = manifest.getMainAttributes();
String bundleNameAttr = attrs.getValue(new Attributes.Name(Bundle.CONST_BUNDLE_NAME));
if (bundleNameAttr != null) {
String bundleName = (new StringTokenizer(bundleNameAttr, ";")).nextToken().trim(); //$NON-NLS-1$
if (!visitedPlugins.contains(bundleName)) {
String bundleCPAttr = attrs.getValue(Bundle.CONST_BUNDLE_CLASSPATH);
if (bundleCPAttr != null) {
StringTokenizer cp = new StringTokenizer(bundleCPAttr, ","); //$NON-NLS-1$
while (cp.hasMoreTokens()) {
String path = cp.nextToken().trim();
if (path != null && path.length() > 0) {
StringBuffer sb = new StringBuffer();
if (locationName.equalsIgnoreCase(MANIFEST_MF))
locationName = location.getAbsolutePath();
sb.append(locationName.substring(0, locationName.length() - META_INF_MANIFEST.length() - 1));
sb.append("/"); //$NON-NLS-1$
sb.append(path);
lib2pluginId.put(sb.toString().replace('\\', '/'), bundleName);
}
}
}
}
}
} catch (IOException e) {}
}
/**
* Handle a jar location.
* @param location
* @return boolean
*/
private boolean acceptSingleJar(ILocation location) {
ILocationChildrenIterator it = location.childIterator();
for (ILocation child = it.next(); child != null; child = it.next()) {
String name = child.getName();
// If it is a manifest, see if its bundle name is in map
if (name.equalsIgnoreCase(META_INF_MANIFEST)) {
try {
Manifest manifest = new Manifest(child.getInputStream());
Attributes attrs = manifest.getMainAttributes();
String bundleName = attrs.getValue(new Attributes.Name(Bundle.CONST_BUNDLE_NAME));
if (bundleName != null) {
bundleName = (new StringTokenizer(bundleName, ";")).nextToken().trim(); //$NON-NLS-1$
if (!visitedPlugins.contains(bundleName)) {
lib2pluginId.put(location.getName().replace('\\', '/'), bundleName);
return false;
}
}
} catch (IOException e) {
}
}
}
// If the location is a jar file, get its associated manifest, and check to add bundle id to map
if (Location.getExtension(location.getName()).equalsIgnoreCase(JAR)) {
try {
JarInputStream jis = new JarInputStream(location.getInputStream());
Manifest manifest = jis.getManifest();
if (manifest != null) {
Attributes attrs = manifest.getMainAttributes();
String bundleNameAttr = attrs.getValue(new Attributes.Name(Bundle.CONST_BUNDLE_NAME));
if (bundleNameAttr != null && !visitedPlugins.contains(bundleNameAttr)) {
String bundleName = (new StringTokenizer(bundleNameAttr, ";")).nextToken().trim(); //$NON-NLS-1$
if (bundleName != null) {
bundleName = (new StringTokenizer(bundleName, ";")).nextToken().trim(); //$NON-NLS-1$
lib2pluginId.put(location.getName().replace('\\', '/'), bundleName);
return false;
}
}
}
} catch (IOException ioe) {
}
}
return true;
}
/**
* Handle a classpath location of .classpath
*
* @param location
*/
private void acceptDotClasspath(ILocation location) {
try {
// Build up the DOM object tree from the file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(location.getInputStream());
Element root = doc.getDocumentElement();
// For each classpath entry check to see if we have an output kind, and check its path
if (root.getTagName().equals(CLASSPATH)) {
NodeList cpEntries = root.getElementsByTagName(CLASSPATHENTRY);
for (int i = 0; i < cpEntries.getLength(); i++) {
Element cpEntry = (Element)cpEntries.item(i);
String kind = cpEntry.getAttribute(KIND);
if (kind != null && kind.equals(OUTPUT)) {
String path = cpEntry.getAttribute(PATH);
String absPath = location.getAbsolutePath().replace('\\', '/');
StringBuffer lib = new StringBuffer();
int j = absPath.lastIndexOf('/');
String s = absPath.substring(0, j);
String id = s.substring(s.lastIndexOf('/') + 1, j);
// If visited plugins does not contain id, add the library location key and id value to
// the library to plugin id map
if (!visitedPlugins.contains(id)) {
lib.append(s);
lib.append('/');
lib.append(path);
lib2pluginId.put(lib.toString(), id);
}
}
}
}
} catch (Throwable e) {}
}
}