| /******************************************************************************* |
| * 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.api; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| 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 org.eclipse.jdt.core.dom.Modifier; |
| import org.eclipse.jdt.core.util.ClassFormatException; |
| import org.eclipse.jdt.core.util.IClassFileReader; |
| import org.eclipse.jdt.core.util.IExceptionAttribute; |
| import org.eclipse.jdt.core.util.IFieldInfo; |
| import org.eclipse.jdt.core.util.IMethodInfo; |
| import org.eclipse.jdt.internal.core.util.ClassFileReader; |
| import org.eclipse.wtp.releng.tools.component.CommandOptionParser; |
| import org.eclipse.wtp.releng.tools.component.IClassVisitor; |
| import org.eclipse.wtp.releng.tools.component.ILocation; |
| import org.eclipse.wtp.releng.tools.component.ILocationVisitor; |
| import org.eclipse.wtp.releng.tools.component.api.violation.LibVisitor; |
| import org.eclipse.wtp.releng.tools.component.images.ImagesUtil; |
| 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.eclipse.wtp.releng.tools.component.internal.Type; |
| import org.eclipse.wtp.releng.tools.component.xsl.XSLUtil; |
| |
| public class API2ComponentAPI implements IClassVisitor |
| { |
| private Collection api; |
| private Collection src; |
| private String outputDir; |
| private Collection includes; |
| private Collection excludes; |
| private boolean html; |
| private boolean readInterface = true; |
| private boolean skipAPIGen = false; |
| private Map interface2ImplClasses = new HashMap(0); |
| private Map super2SubClasses = new HashMap(0); |
| private boolean includeInnerClass = false; |
| private boolean includeInterfaces = false; |
| |
| public Collection getApi() |
| { |
| return api; |
| } |
| |
| public void setApi(String api) |
| { |
| this.api = new ArrayList(1); |
| this.api.add(api); |
| } |
| |
| public void setApi(Collection api) |
| { |
| this.api = api; |
| } |
| |
| public Collection getSrc() |
| { |
| return src; |
| } |
| |
| public void setSrc(String src) |
| { |
| this.src = new ArrayList(1); |
| this.src.add(src); |
| } |
| |
| public void setSrc(Collection src) |
| { |
| this.src = src; |
| } |
| |
| public String getOutputDir() |
| { |
| return outputDir; |
| } |
| |
| public void setOutputDir(String outputDir) |
| { |
| this.outputDir = addTrailingSeperator(outputDir); |
| } |
| |
| public Collection getIncludes() |
| { |
| return includes; |
| } |
| |
| public void setIncludes(Collection includes) |
| { |
| this.includes = includes; |
| } |
| |
| public Collection getExcludes() |
| { |
| return excludes; |
| } |
| |
| public void setExcludes(Collection excludes) |
| { |
| this.excludes = excludes; |
| } |
| |
| public boolean isHtml() |
| { |
| return html; |
| } |
| |
| public void setHtml(boolean html) |
| { |
| this.html = html; |
| } |
| |
| public boolean isReadInterface() |
| { |
| return readInterface; |
| } |
| |
| public void setReadInterface(boolean readInterface) |
| { |
| this.readInterface = readInterface; |
| } |
| |
| public boolean isSkipAPIGen() |
| { |
| return skipAPIGen; |
| } |
| |
| public void setSkipAPIGen(boolean skipAPIGen) |
| { |
| this.skipAPIGen = skipAPIGen; |
| } |
| |
| public boolean isIncludeInnerClass() |
| { |
| return includeInnerClass; |
| } |
| |
| public void setIncludeInnerClass(boolean includeInnerClass) |
| { |
| this.includeInnerClass = includeInnerClass; |
| } |
| |
| public boolean isIncludeInterfaces() |
| { |
| return includeInterfaces; |
| } |
| |
| public void setIncludeInterfaces(boolean includeInterfaces) |
| { |
| this.includeInterfaces = includeInterfaces; |
| } |
| |
| public List getImplClasses(String interfaceName) |
| { |
| List list = new ArrayList(); |
| getImplClasses(interfaceName, list); |
| return list; |
| } |
| |
| private void getImplClasses(String interfaceName, List list) |
| { |
| List implClasses = (List)interface2ImplClasses.get(interfaceName); |
| if (implClasses != null) |
| { |
| list.addAll(implClasses); |
| for (Iterator it = implClasses.iterator(); it.hasNext();) |
| { |
| String className = (String)it.next(); |
| if (interface2ImplClasses.get(className) != null) |
| getImplClasses(className, list); |
| else |
| { |
| getSubClasses(className, list); |
| getSuperClasses(className, list); |
| } |
| } |
| } |
| } |
| |
| private void getSuperClasses(String className, List list) |
| { |
| ClassHierarchyInfo info = (ClassHierarchyInfo)super2SubClasses.get(className); |
| if (info != null) |
| { |
| String superClassName = info.getSuperClass(); |
| while (superClassName != null) |
| { |
| list.add(superClassName); |
| superClassName = null; |
| info = (ClassHierarchyInfo)super2SubClasses.get(superClassName); |
| if (info != null) |
| superClassName = info.getSuperClass(); |
| } |
| } |
| } |
| |
| private void getSubClasses(String className, List list) |
| { |
| ClassHierarchyInfo info = (ClassHierarchyInfo)super2SubClasses.get(className); |
| if (info != null) |
| { |
| List subClasses = info.getSubClass(); |
| if (subClasses != null) |
| { |
| list.addAll(subClasses); |
| for (Iterator it = subClasses.iterator(); it.hasNext();) |
| getSubClasses((String)it.next(), list); |
| } |
| } |
| } |
| |
| private Map pluginId2CompXML = new HashMap(); |
| |
| public void execute() |
| { |
| // Collection component.xml files |
| for (Iterator i = api.iterator(); i.hasNext();) |
| { |
| ILocation apiLocation = Location.createLocation(new File((String)i.next())); |
| ComponentXMLVisitor compXMLVisitor = new ComponentXMLVisitor(); |
| apiLocation.accept(compXMLVisitor); |
| for (Iterator it = compXMLVisitor.getCompXMLs().iterator(); it.hasNext();) |
| { |
| ComponentXML compXML = (ComponentXML)it.next(); |
| for (Iterator it2 = compXML.getPlugins().iterator(); it2.hasNext();) |
| { |
| pluginId2CompXML.put(((Plugin)it2.next()).getId(), compXML); |
| } |
| } |
| } |
| // Generate api-info.xml files |
| for (Iterator it = src.iterator(); it.hasNext();) |
| { |
| ILocation srcLocation = Location.createLocation(new File((String)it.next())); |
| LibVisitor libVisitor = new LibVisitor(); |
| if (srcLocation != null) { |
| srcLocation.accept(libVisitor); |
| libVisitor.setClassVisitor(this); |
| srcLocation.accept(libVisitor); |
| } else { |
| System.out.println("Null source location found, continuing."); |
| } |
| } |
| try |
| { |
| if (cachedCompAPI != null) |
| cachedCompAPI.save(); |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| // Generate HTML |
| if (isHtml()) |
| genHTML(); |
| } |
| |
| private void genHTML() |
| { |
| ImagesUtil.copyAll(outputDir); |
| final StringBuffer summary = new StringBuffer(); |
| summary.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
| summary.append("<root>"); |
| ILocation outputLoc = Location.createLocation(new File(outputDir)); |
| outputLoc.accept(new ILocationVisitor() |
| { |
| public boolean accept(ILocation location) |
| { |
| if (location.getName().endsWith("api-info.xml")) |
| { |
| summary.append("<api-info file=\""); |
| summary.append(location.getAbsolutePath().substring(outputDir.length())); |
| summary.append("\"/>"); |
| } |
| return true; |
| } |
| }); |
| summary.append("</root>"); |
| try |
| { |
| XSLUtil.transform(ClassLoader.getSystemResourceAsStream("org/eclipse/wtp/releng/tools/component/xsl/api-info-summary.xsl"), new ByteArrayInputStream(summary.toString().getBytes()), new FileOutputStream(new File(outputDir + "/api-info-summary.html")), outputDir); |
| } |
| catch (Throwable e) |
| { |
| e.printStackTrace(); |
| } |
| } |
| |
| public boolean visit(String pluginId, ILocation classLoc) |
| { |
| try |
| { |
| IClassFileReader reader = read(classLoc); |
| String className = new String(reader.getClassName()).replace('/', '.'); |
| if (include(className)) |
| { |
| int i = className.lastIndexOf('.'); |
| String packageName = (i != -1) ? className.substring(0, i) : ""; |
| String localName = (i != -1) ? className.substring(i + 1) : className; |
| ClassAPIInfo classAPIInfo = isAPI(reader, pluginId, packageName, localName); |
| boolean isAPI = classAPIInfo.api; |
| if (readInterface) |
| { |
| String superClassName = new String(reader.getSuperclassName()).replace('/', '.'); |
| ClassHierarchyInfo info = new ClassHierarchyInfo(); |
| info.setSuperClass(superClassName); |
| super2SubClasses.put(className, info); |
| info = (ClassHierarchyInfo)super2SubClasses.get(superClassName); |
| if (info == null) |
| { |
| info = new ClassHierarchyInfo(); |
| super2SubClasses.put(superClassName, info); |
| } |
| info.addSubClass(className); |
| char[][] names = reader.getInterfaceNames(); |
| for (int j = 0; j < names.length; j++) |
| { |
| String interfaceName = new String(names[j]).replace('/', '.'); |
| List implClasses = (List)interface2ImplClasses.get(interfaceName); |
| if (implClasses == null) |
| { |
| implClasses = new ArrayList(1); |
| interface2ImplClasses.put(interfaceName, implClasses); |
| } |
| implClasses.add(className); |
| } |
| } |
| if (!isSkipAPIGen() && isAPI) |
| { |
| try |
| { |
| ComponentAPI compAPI = getComponentAPI(pluginId); |
| PackageAPI pkgAPI = (PackageAPI)compAPI.getPackageAPI(packageName); |
| if (pkgAPI == null) |
| { |
| pkgAPI = new PackageAPI(); |
| pkgAPI.setName(packageName); |
| compAPI.addPackageAPI(pkgAPI); |
| } |
| ClassAPI classAPI = pkgAPI.getClassAPI(localName); |
| if (classAPI == null) |
| { |
| boolean isExclusive = isExclusive(pluginId, packageName); |
| if (reader == null) |
| reader = read(classLoc); |
| classAPI = new ClassAPI(); |
| classAPI.setName(localName); |
| classAPI.setAccess(reader.getAccessFlags()); |
| classAPI.setSuperClass(new String(reader.getSuperclassName()).replace('/', '.')); |
| if (includeInterfaces) |
| { |
| char[][] names = reader.getInterfaceNames(); |
| for (int j = 0; j < names.length; j++) |
| { |
| classAPI.addInterface(new String(names[j]).replace('/', '.')); |
| } |
| } |
| pkgAPI.addClassAPI(classAPI); |
| IMethodInfo[] methods = reader.getMethodInfos(); |
| for (int j = 0; j < methods.length; j++) |
| { |
| int methodAccessFlag = methods[j].getAccessFlags(); |
| if (!Modifier.isPrivate(methodAccessFlag)) |
| { |
| if (!isExclusive || Modifier.isPublic(methodAccessFlag) || (classAPIInfo.subclass && Modifier.isProtected(methodAccessFlag))) |
| { |
| MethodAPI methodAPI = new MethodAPI(); |
| methodAPI.setName(new String(methods[j].getName())); |
| methodAPI.setAccess(methodAccessFlag); |
| methodAPI.setDescriptor(new String(methods[j].getDescriptor())); |
| IExceptionAttribute exceptionAttr = methods[j].getExceptionAttribute(); |
| if (exceptionAttr != null) |
| { |
| char[][] exceptionNames = exceptionAttr.getExceptionNames(); |
| List exceptions = new ArrayList(exceptionNames.length); |
| for (int k = 0; k < exceptionNames.length; k++) |
| { |
| exceptions.add(new String(exceptionNames[k]).replace('/', '.')); |
| } |
| methodAPI.addThrows(exceptions); |
| } |
| classAPI.addMethodAPI(methodAPI); |
| } |
| } |
| } |
| IFieldInfo[] fields = reader.getFieldInfos(); |
| for (int j = 0; j < fields.length; j++) |
| { |
| int fieldAccessFlag = fields[j].getAccessFlags(); |
| if (!Modifier.isPrivate(fieldAccessFlag)) |
| { |
| if (!isExclusive || Modifier.isPublic(fieldAccessFlag) || Modifier.isProtected(fieldAccessFlag)) |
| { |
| FieldAPI fieldAPI = new FieldAPI(); |
| fieldAPI.setName(new String(fields[j].getName())); |
| fieldAPI.setAccess(fieldAccessFlag); |
| fieldAPI.setDescriptor(new String(fields[j].getDescriptor())); |
| classAPI.addFieldAPI(fieldAPI); |
| } |
| } |
| } |
| } |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| catch (ClassFormatException e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| } |
| catch (IOException ioe) |
| { |
| ioe.printStackTrace(); |
| } |
| catch (ClassFormatException cfe) |
| { |
| cfe.printStackTrace(); |
| } |
| return true; |
| } |
| |
| private ClassAPIInfo isAPI(IClassFileReader reader, String pluginId, String packageName, String localName) |
| { |
| boolean innerClass = localName.indexOf('$') != -1; |
| int classAccessFlag = reader.getAccessFlags(); |
| if (Modifier.isPrivate(classAccessFlag)) |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| else if (!includeInnerClass && innerClass && !Modifier.isPublic(classAccessFlag) && !Modifier.isProtected(classAccessFlag)) |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| else |
| { |
| ComponentXML compXML = (ComponentXML)pluginId2CompXML.get(pluginId); |
| if (compXML != null) |
| { |
| Package pkg = compXML.getPackage(packageName); |
| if (pkg != null) |
| { |
| // package protected, check exclusive |
| if (pkg.isExclusive() && !Modifier.isPublic(classAccessFlag) && !Modifier.isProtected(classAccessFlag)) |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| Type type; |
| int innerClassSep = localName.indexOf('$'); |
| if (innerClassSep == -1) |
| type = pkg.getType(localName); |
| else |
| type = pkg.getType(localName.substring(0, innerClassSep)); |
| if (type != null) |
| { |
| if (!type.isReference() && !type.isSubclass() && !type.isImplement() && !type.isInstantiate()) |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| else |
| { |
| return new ClassAPIInfo(true, type.isSubclass()); |
| } |
| } |
| else |
| { |
| return new ClassAPIInfo(pkg.isApi(), true); |
| } |
| } |
| else |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| } |
| else |
| { |
| return new ClassAPIInfo(false, false); |
| } |
| } |
| } |
| |
| private boolean isExclusive(String pluginId, String packageName) |
| { |
| ComponentXML compXML = (ComponentXML)pluginId2CompXML.get(pluginId); |
| if (compXML != null) |
| { |
| Package pkg = compXML.getPackage(packageName); |
| if (pkg != null) |
| { |
| return pkg.isExclusive(); |
| } |
| } |
| return true; |
| } |
| |
| private IClassFileReader read(ILocation classLoc) throws IOException, ClassFormatException |
| { |
| InputStream is = null; |
| ByteArrayOutputStream baos = null; |
| try |
| { |
| byte[] b = new byte[8192]; |
| baos = new ByteArrayOutputStream(8192); |
| 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 new ClassFileReader(baos.toByteArray(), IClassFileReader.CONSTANT_POOL | IClassFileReader.METHOD_INFOS | IClassFileReader.FIELD_INFOS | IClassFileReader.SUPER_INTERFACES); |
| } |
| finally |
| { |
| if (is != null) |
| { |
| try |
| { |
| is.close(); |
| } |
| catch (IOException e) |
| { |
| } |
| } |
| if (baos != null) |
| { |
| try |
| { |
| baos.close(); |
| } |
| catch (IOException e) |
| { |
| } |
| } |
| } |
| } |
| |
| private boolean include(String name) |
| { |
| name = name.replace('/', '.'); |
| name = name.replace('\\', '.'); |
| if (excludes != null && !excludes.isEmpty()) |
| for (Iterator it = excludes.iterator(); it.hasNext();) |
| if (name.matches((String)it.next())) |
| return false; |
| if (includes != null && !includes.isEmpty()) |
| { |
| for (Iterator it = includes.iterator(); it.hasNext();) |
| if (name.matches((String)it.next())) |
| return true; |
| return false; |
| } |
| return true; |
| } |
| private ComponentAPI cachedCompAPI; |
| |
| private ComponentAPI getComponentAPI(String id) throws IOException |
| { |
| if (cachedCompAPI != null) |
| { |
| if (cachedCompAPI.getName().equals(id)) |
| { |
| return cachedCompAPI; |
| } |
| else |
| { |
| cachedCompAPI.save(); |
| } |
| } |
| StringBuffer sb = new StringBuffer(outputDir); |
| sb.append(id); |
| sb.append("/api-info.xml"); |
| File file = new File(sb.toString()); |
| cachedCompAPI = new ComponentAPI(); |
| cachedCompAPI.setName(id); |
| cachedCompAPI.setLocation(new FileLocation(file)); |
| if (file.exists()) |
| cachedCompAPI.load(); |
| return cachedCompAPI; |
| } |
| |
| protected String addTrailingSeperator(String s) |
| { |
| if (s != null && !s.endsWith("/") && !s.endsWith("\\")) |
| { |
| StringBuffer sb = new StringBuffer(s); |
| sb.append('/'); |
| return sb.toString(); |
| } |
| else |
| { |
| return s; |
| } |
| } |
| |
| private class ClassAPIInfo |
| { |
| public boolean api; |
| public boolean subclass; |
| |
| public ClassAPIInfo(boolean api, boolean subclass) |
| { |
| this.api = api; |
| this.subclass = subclass; |
| } |
| } |
| |
| private class ClassHierarchyInfo |
| { |
| private String superClass; |
| private List subClass; |
| |
| public List getSubClass() |
| { |
| return subClass; |
| } |
| |
| public void addSubClass(String subClass) |
| { |
| if (this.subClass == null) |
| this.subClass = new ArrayList(1); |
| this.subClass.add(subClass); |
| } |
| |
| public String getSuperClass() |
| { |
| return superClass; |
| } |
| |
| public void setSuperClass(String superClass) |
| { |
| this.superClass = superClass; |
| } |
| } |
| |
| public static void main(String[] args) |
| { |
| CommandOptionParser optionParser = new CommandOptionParser(args); |
| Map options = optionParser.getOptions(); |
| Collection api = (Collection)options.get("api"); |
| Collection src = (Collection)options.get("src"); |
| Collection outputDir = (Collection)options.get("outputDir"); |
| Collection includes = (Collection)options.get("includes"); |
| Collection excludes = (Collection)options.get("excludes"); |
| Collection html = (Collection)options.get("html"); |
| if (api == null || src == null || outputDir == null || api.isEmpty() || src.isEmpty() || outputDir.isEmpty()) |
| { |
| printUsage(); |
| System.exit(-1); |
| } |
| API2ComponentAPI api2CompAPI = new API2ComponentAPI(); |
| api2CompAPI.setApi(api); |
| api2CompAPI.setSrc(src); |
| api2CompAPI.setOutputDir((String)outputDir.iterator().next()); |
| api2CompAPI.setIncludes(includes); |
| api2CompAPI.setExcludes(excludes); |
| api2CompAPI.setHtml(html != null); |
| api2CompAPI.execute(); |
| } |
| |
| private static void printUsage() |
| { |
| System.out.println("Usage: java org.eclipse.wtp.releng.tools.component.api.API2ComponentAPI -api <api> -src <src> -outputDir <outputDir> [-options]"); |
| System.out.println(""); |
| System.out.println("\t-api\t\t<api>\t\tlocation of your component.xml"); |
| System.out.println("\t-src\t\t<src>\t\tlocation of a Eclipse-based product"); |
| System.out.println("\t-outputDir\t<outputDir>\toutput directory of component.xml files"); |
| System.out.println(""); |
| System.out.println("where options include:"); |
| System.out.println(""); |
| System.out.println("\t-includes\t<includes>\tspace seperated packages to include"); |
| System.out.println("\t-excludes\t<excludes>\tspace seperated packages to exclude"); |
| System.out.println("\t-html\t\t\t\t\tgenerate HTML output"); |
| } |
| } |