| /******************************************************************************* |
| * Copyright (c) 2004, 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.violation; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| 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.core.runtime.Platform; |
| import org.eclipse.jdt.core.util.ClassFormatException; |
| 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.ClassUse; |
| import org.eclipse.wtp.releng.tools.component.api.ComponentUse; |
| import org.eclipse.wtp.releng.tools.component.api.ComponentXMLVisitor; |
| import org.eclipse.wtp.releng.tools.component.api.Source; |
| import org.eclipse.wtp.releng.tools.component.images.ImagesUtil; |
| 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.eclipse.wtp.releng.tools.component.internal.Type; |
| import org.eclipse.wtp.releng.tools.component.xsl.XSLUtil; |
| |
| public class APIViolationScanner implements IClassVisitor |
| { |
| private Collection src; |
| private Collection api; |
| private String outputDir; |
| private Collection includes; |
| private Collection excludes; |
| private boolean classRefOnly; |
| private boolean debug; |
| private boolean html; |
| private String xsl; |
| |
| public String getOutputDir() |
| { |
| return outputDir; |
| } |
| |
| public void setOutputDir(String outputDir) |
| { |
| this.outputDir = addTrailingSeperator(outputDir); |
| } |
| |
| public Collection getSrc() |
| { |
| return src; |
| } |
| |
| public void setSrc(Collection src) |
| { |
| this.src = src; |
| } |
| |
| public Collection getApi() |
| { |
| return api; |
| } |
| |
| public void setApi(Collection api) |
| { |
| this.api = api; |
| } |
| |
| 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 isClassRefOnly() |
| { |
| return classRefOnly; |
| } |
| |
| public void setClassRefOnly(boolean classRefOnly) |
| { |
| this.classRefOnly = classRefOnly; |
| } |
| |
| public boolean isDebug() |
| { |
| return debug; |
| } |
| |
| public void setDebug(boolean debug) |
| { |
| this.debug = debug; |
| } |
| |
| public boolean isHtml() |
| { |
| return html; |
| } |
| |
| public void setHtml(boolean html) |
| { |
| this.html = html; |
| } |
| |
| public String getXsl() |
| { |
| return xsl; |
| } |
| |
| public void setXsl(String xsl) |
| { |
| this.xsl = xsl; |
| } |
| private Map pluginId2CompXML = new HashMap(); |
| |
| public void execute() |
| { |
| // Collect component.xml files |
| for (Iterator i = api.iterator(); i.hasNext();) |
| { |
| String locationString = (String)i.next(); |
| ILocation apiLocation = Location.createLocation(new File(locationString)); |
| ComponentXMLVisitor compXMLVisitor = new ComponentXMLVisitor(); |
| if (apiLocation == null) { |
| System.out.println("ERROR - Can't find location at: "+locationString); |
| continue; |
| } |
| 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); |
| } |
| } |
| } |
| for (Iterator it = src.iterator(); it.hasNext();) |
| { |
| // Visit all .class files |
| visit1 = true; |
| ILocation srcLocation = Location.createLocation(new File((String)it.next())); |
| LibVisitor libVisitor = new LibVisitor(); |
| srcLocation.accept(libVisitor); |
| libVisitor.setClassVisitor(this); |
| // 1st visit is to recreate the plugin to packages map |
| srcLocation.accept(libVisitor); |
| visit1 = false; |
| // 2nd visit is to scan .class for API violations |
| srcLocation.accept(libVisitor); |
| } |
| try |
| { |
| if (cachedCompUse != null) |
| cachedCompUse.save(); |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| if (isHtml()) |
| { |
| ImagesUtil.copyAll(outputDir); |
| genHTML(); |
| } |
| } |
| |
| private void genHTML() |
| { |
| 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-violation.xml")) |
| { |
| try |
| { |
| XSLUtil.transform |
| ( |
| xsl != null && xsl.length() > 0 ? Location.createLocation(new File(xsl)).getInputStream() : ClassLoader.getSystemResourceAsStream("org/eclipse/wtp/releng/tools/component/xsl/api-violation.xsl"), |
| location.getInputStream(), |
| new FileOutputStream(((FileLocation)location.createSibling("api-violation.html")).getFile()) |
| ); |
| } |
| catch (Throwable e) |
| { |
| try |
| { |
| XSLUtil.transform |
| ( |
| Platform.getBundle("org.eclipse.wtp.releng.tools.component.core").getResource("org/eclipse/wtp/releng/tools/component/xsl/api-violation.xsl").openStream(), |
| location.getInputStream(), |
| new FileOutputStream(((FileLocation)location.createSibling("api-violation.html")).getFile()) |
| ); |
| } |
| catch (Throwable e2) |
| { |
| e2.printStackTrace(); |
| } |
| } |
| summary.append("<violation 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-violation-summary.xsl"), |
| new ByteArrayInputStream(summary.toString().getBytes()), |
| new FileOutputStream(new File(outputDir + "/api-violation-summary.html")), |
| outputDir |
| ); |
| } |
| catch (Throwable e) |
| { |
| try |
| { |
| XSLUtil.transform |
| ( |
| Platform.getBundle("org.eclipse.wtp.releng.tools.component.core").getResource("org/eclipse/wtp/releng/tools/component/xsl/api-violation-summary.xsl").openStream(), |
| new ByteArrayInputStream(summary.toString().getBytes()), |
| new FileOutputStream(new File(outputDir + "/api-violation-summary.html")), |
| outputDir |
| ); |
| } |
| catch (Throwable e2) |
| { |
| e2.printStackTrace(); |
| } |
| } |
| } |
| |
| private boolean visit1 = true; |
| |
| public boolean visit(String pluginId, ILocation classLoc) |
| { |
| if (visit1) |
| return visit1(pluginId, classLoc); |
| else |
| return visit2(pluginId, classLoc); |
| } |
| private Map pluginId2Pkgs = new HashMap(); |
| |
| private boolean visit1(String pluginId, ILocation classLoc) |
| { |
| String className = classLoc.getName(); |
| className = className.substring(0, className.length() - ".class".length()); |
| className = className.replace('/', '.'); |
| className = className.replace('\\', '.'); |
| int i = className.lastIndexOf('.'); |
| String packageName = (i != -1) ? className.substring(0, i) : ""; |
| List pkgs = (List)pluginId2Pkgs.get(pluginId); |
| if (pkgs == null) |
| { |
| pkgs = new ArrayList(); |
| pluginId2Pkgs.put(pluginId, pkgs); |
| } |
| if (!pkgs.contains(packageName)) |
| pkgs.add(packageName); |
| return true; |
| } |
| private Class2Reference class2Ref = new Class2Reference(); |
| |
| private boolean visit2(String pluginId, ILocation classLoc) |
| { |
| try |
| { |
| ComponentUse compUse = getComponentUse(pluginId); |
| class2Ref.setIncludes(getIncludes()); |
| class2Ref.setExcludes(getExcludes()); |
| class2Ref.setClassRefOnly(isClassRefOnly()); |
| class2Ref.setDebug(isDebug()); |
| Source uses = class2Ref.visit(classLoc); |
| // remove reference to classes in the same component |
| removeSelfRefs(pluginId, uses); |
| // remove valid API reference to classes in other components |
| for (Iterator it = getDependCompXML(pluginId).iterator(); it.hasNext();) |
| removeAPIUses((ComponentXML)it.next(), uses); |
| if (!uses.getClassUses().isEmpty()) |
| compUse.addSource(uses); |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| catch (ClassFormatException e) |
| { |
| throw new RuntimeException(e); |
| } |
| return true; |
| } |
| |
| private void removeSelfRefs(String pluginId, Source source) |
| { |
| removeSelfRefs2(pluginId, source); |
| ComponentXML compXML = (ComponentXML)pluginId2CompXML.get(pluginId); |
| if (compXML != null) |
| { |
| for (Iterator it = compXML.getPlugins().iterator(); it.hasNext();) |
| { |
| String id = ((Plugin)it.next()).getId(); |
| if (!id.equals(pluginId)) |
| { |
| removeSelfRefs2(id, source); |
| } |
| } |
| } |
| } |
| |
| private void removeSelfRefs2(String pluginId, Source source) |
| { |
| List pkgs = (List)pluginId2Pkgs.get(pluginId); |
| if (pkgs != null) |
| { |
| for (Iterator it = new ArrayList(source.getClassUses()).iterator(); it.hasNext();) |
| { |
| ClassUse classUse = (ClassUse)it.next(); |
| String className = classUse.getName(); |
| int i = className.lastIndexOf('.'); |
| String packageName = (i != -1) ? className.substring(0, i) : ""; |
| if (pkgs.contains(packageName)) |
| source.removeClassUse(classUse); |
| } |
| } |
| } |
| |
| private Collection getDependCompXML(String pluginId) |
| { |
| List compXMLs = new ArrayList(); |
| ComponentXML thisCompXML = (ComponentXML)pluginId2CompXML.get(pluginId); |
| if (thisCompXML != null) |
| { |
| ComponentDepends depends = thisCompXML.getComponentDepends(); |
| boolean unrestricted = (depends != null) ? depends.isUnrestricted() : false; |
| Collection compRefs = (depends != null) ? depends.getComponentRefs() : new ArrayList(0); |
| for (Iterator it = pluginId2CompXML.values().iterator(); it.hasNext();) |
| { |
| ComponentXML compXML = (ComponentXML)it.next(); |
| if (compXML != thisCompXML) |
| { |
| if (unrestricted || compRefs.contains(compXML.getName())) |
| { |
| compXMLs.add(compXML); |
| } |
| } |
| } |
| } |
| else |
| { |
| compXMLs.addAll(pluginId2CompXML.values()); |
| } |
| return compXMLs; |
| } |
| |
| private void removeAPIUses(ComponentXML compXML, Source source) |
| { |
| for (Iterator it = new ArrayList(source.getClassUses()).iterator(); it.hasNext();) |
| { |
| ClassUse classUse = (ClassUse)it.next(); |
| if (validateUse(compXML, classUse)) |
| { |
| source.removeClassUse(classUse); |
| } |
| } |
| } |
| |
| private boolean validateUse(ComponentXML compXML, ClassUse classUse) |
| { |
| String className = classUse.getName(); |
| int i = className.lastIndexOf('.'); |
| String packageName = (i != -1) ? className.substring(0, i) : ""; |
| String localName = (i != -1) ? className.substring(i + 1) : className; |
| Package pkg = compXML.getPackage(packageName); |
| if (pkg != null) |
| { |
| Type type = pkg.getType(localName); |
| if (type != null) |
| { |
| if (!classUse.isReference() || type.isReference()) |
| if (!classUse.isSubclass() || type.isSubclass()) |
| if (!classUse.isImplement() || type.isImplement()) |
| if (!classUse.isInstantiate() || type.isInstantiate()) |
| return true; |
| return false; |
| } |
| else |
| { |
| return pkg.isApi(); |
| } |
| } |
| return false; |
| } |
| |
| private ComponentUse cachedCompUse; |
| |
| private ComponentUse getComponentUse(String pluginId) throws IOException |
| { |
| ComponentXML compXML = (ComponentXML)pluginId2CompXML.get(pluginId); |
| String compId = (compXML != null) ? compXML.getName() : pluginId; |
| if (cachedCompUse != null) |
| { |
| if (cachedCompUse.getName().equals(compId)) |
| { |
| return cachedCompUse; |
| } |
| else |
| { |
| cachedCompUse.save(); |
| } |
| } |
| StringBuffer sb = new StringBuffer(outputDir); |
| sb.append(compId); |
| sb.append("/api-violation.xml"); |
| File file = new File(sb.toString()); |
| cachedCompUse = new ComponentUse(); |
| cachedCompUse.setName(compId); |
| cachedCompUse.setLocation(new FileLocation(file)); |
| if (file.exists()) |
| { |
| cachedCompUse.load(); |
| } |
| return cachedCompUse; |
| } |
| |
| 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; |
| } |
| } |
| |
| public static void main(String[] args) |
| { |
| CommandOptionParser optionParser = new CommandOptionParser(args); |
| Map options = optionParser.getOptions(); |
| Collection src = (Collection)options.get("src"); |
| Collection api = (Collection)options.get("api"); |
| Collection outputDir = (Collection)options.get("outputDir"); |
| Collection includes = (Collection)options.get("includes"); |
| Collection excludes = (Collection)options.get("excludes"); |
| Collection classRefOnly = (Collection)options.get("classRefOnly"); |
| Collection debug = (Collection)options.get("debug"); |
| Collection html = (Collection)options.get("html"); |
| Collection xsl = (Collection)options.get("xsl"); |
| if (src == null || api == null || outputDir == null || src.isEmpty() || api.isEmpty() || outputDir.isEmpty()) |
| { |
| printUsage(); |
| System.exit(-1); |
| } |
| APIViolationScanner vioScanner = new APIViolationScanner(); |
| vioScanner.setSrc(src); |
| vioScanner.setApi(api); |
| vioScanner.setOutputDir((String)outputDir.iterator().next()); |
| vioScanner.setIncludes(includes); |
| vioScanner.setExcludes(excludes); |
| vioScanner.setClassRefOnly(classRefOnly != null); |
| vioScanner.setDebug(debug != null); |
| vioScanner.setHtml(html != null); |
| vioScanner.setXsl(xsl != null && !xsl.isEmpty() ? (String)xsl.iterator().next() : null); |
| vioScanner.execute(); |
| } |
| |
| private static void printUsage() |
| { |
| System.out.println("Usage: java org.eclipse.wtp.releng.tools.component.violation.APIViolationScanner -src <src> -api <api> -outputDir <outputDir> [-options]"); |
| System.out.println(""); |
| System.out.println("\t-src\t\t<src>\t\tlocation of a Eclipse-based product"); |
| System.out.println("\t-api\t\t<api>\t\tlocation of your component.xml"); |
| 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-classRefOnly\t\t\ttreat all violations as class reference"); |
| System.out.println("\t-debug\t\t\t\tgenerate debug information (ex. line numbers)"); |
| System.out.println("\t-html\t\t\tgenerate HTML results"); |
| System.out.println("\t-xsl\t<xsl>\tuse your own stylesheet. You must specify the -html option"); |
| } |
| } |