| /******************************************************************************* |
| * 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) {} |
| } |
| } |