blob: 2c234beee195c3fe7ac0964313d702b66a63f440 [file] [log] [blame]
/*******************************************************************************
* 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");
}
}