Java tutorial
/* * Copyright 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.singletondetector; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import com.google.singletondetector.classpath.ClasspathRoot; import com.google.singletondetector.classpath.DirectoryClasspathRoot; import com.google.singletondetector.classpath.JarClasspathRoot; import com.google.singletondetector.output.FingletonOutputNode; import com.google.singletondetector.output.HingletonOutputNode; import com.google.singletondetector.output.MingletonOutputNode; import com.google.singletondetector.output.OtherOutputNode; import com.google.singletondetector.output.OutputNode; import com.google.singletondetector.output.SingletonOutputNode; import com.google.singletondetector.visitors.SingletonClassVisitor; import com.google.singletondetector.visitors.SingletonUsageClassVisitor; /** * The main SingletonDetector class, which generates the visitors for each class * and starts them. Also responsible for constructing the OutputNodes and * formatting their output. * * @author David Rubel */ public class SingletonDetector implements Opcodes { // Map of all of the classes ready by SingletonDetector private Map<String, Clazz> classes = new HashMap<String, Clazz>(); // The class currently being read by the visitor objects private Clazz currClass; // The common prefix for all classes private String prefix; // Command line flags passed in from the main() method private Flags flags; // Statistics kept on the SingletonDetector private Stats stats; /** * Test constructor, used to pass in a classpath and a list of class names * directly, avoiding a directory or jar. Uses default flags. */ public SingletonDetector(ClasspathRoot classpath, String prefix, String... classNames) { this(classpath, prefix, new Flags(), classNames); } /** * Test constructor, used to pass in a classpath and a list of class names * directly, avoiding a directory or jar. */ public SingletonDetector(ClasspathRoot classpath, String prefix, Flags flags, String... classNames) { this(getClassReaders(classpath, classNames, prefix), prefix, flags); } /** * Default constructor, used to pass in a string which represents a directory * or a jar. */ public SingletonDetector(String dir, String prefix, Flags flags) throws MalformedURLException { this(getClassReaders(dir, prefix, flags), prefix, flags); } /** * Master constructor, called by all other constructors. Requires a list of * ClassReaders, which is generated in the other constructors by different * methods. */ public SingletonDetector(List<ClassReader> classReaderList, String prefix, Flags flags) { stats = new Stats(); this.prefix = prefix; this.flags = flags; // Verbose: Begin processing if (flags.isVerbose()) { System.out.print("Processing... "); } for (ClassReader classReader : classReaderList) { classReader.accept(new SingletonClassVisitor(this), ClassReader.SKIP_DEBUG); stats.incClassesRead(); } // Second pass: determine which special classes each class uses for (ClassReader cr : classReaderList) { cr.accept(new SingletonUsageClassVisitor(this), ClassReader.SKIP_DEBUG); } // Third pass: set isDrawn for each class for (Clazz cl : classes.values()) { cl.setIsDrawn(flags.getThreshold()); } // Fourth pass: update isDrawn if (flags.getThreshold() > 0) { for (Clazz cl : classes.values()) { cl.updateIsDrawn(); } } // Fifth pass: gather statistics if (flags.showBanner() || flags.showStats()) { // Get stats on current class for (Clazz cl : classes.values()) { if (cl.isDrawn()) { stats.incClassesDrawn(); if (cl.isSingleton()) { stats.incSingletons(); } else if (cl.isHingleton()) { stats.incHingletons(); } else if (cl.isMingleton()) { stats.incMingletons(); } else if (cl.isFingleton()) { stats.incFingletons(); } // Get stats for all used classes for (Clazz used : cl.getClassesIUse()) { if (used.isDrawn()) { if (used.isSingleton()) { stats.incSingletonUsers(); } else if (used.isHingleton()) { stats.incHingletonUsers(); } else if (used.isMingleton()) { stats.incMingletonUsers(); } else if (used.isFingleton()) { stats.incFingletonUsers(); } } } } } } // Verbose: end processing if (flags.isVerbose()) { System.out.println("done."); } } public void setCurrClass(String name) { String label = removePrefix(name); currClass = classes.get(label); if (currClass == null) { currClass = new Clazz(label); classes.put(currClass.getName(), currClass); } } public Clazz getCurrClass() { return currClass; } public Clazz getClass(String name) { return classes.get(removePrefix(name)); } public String getPrefix() { return prefix; } public void setPrefix(String prefix) { this.prefix = prefix; } public String[] splitDesc(String desc) { String[] ret = new String[2]; if (desc.startsWith("(")) { ret[0] = desc.substring(1, desc.indexOf(')')); ret[1] = desc.substring(desc.indexOf(')') + 1); ret[1] = ret[1].substring(0, ret[1].length() - 1); } return ret; } public String removePrefix(String str) { String ret = str; if (str.startsWith(prefix)) { ret = ret.substring(prefix.length()); } return ret; } /*************************************************************************/ /* CLASS READER METHODS */ /*************************************************************************/ private static ClassReader getClassReader(ClasspathRoot classpath, String className) throws IOException { String resourceName = className; if (!resourceName.endsWith(".class")) { resourceName += ".class"; } ClassReader cr = new ClassReader(classpath.getResourceAsStream(resourceName)); return cr; } private static boolean validcr(ClassReader cr, String prefix) { return !cr.getSuperName().equals("java/lang/Enum") && cr.getClassName().startsWith(prefix); } private static List<ClassReader> getClassReaders(ClasspathRoot classpath, String[] classNames, String prefix) { List<ClassReader> crlist = new ArrayList<ClassReader>(); for (String className : classNames) { ClassReader cr; try { cr = getClassReader(classpath, className); if (validcr(cr, prefix)) { crlist.add(cr); } } catch (IOException e) { System.out.println("Failed to read " + className); } } return crlist; } private static List<ClassReader> getClassReaders(String dir, String prefix, Flags flags) throws MalformedURLException { List<ClassReader> crlist = new ArrayList<ClassReader>(); // Get the classpath for the classes directory or jar URL root = new File(dir).toURI().toURL(); ClasspathRoot classpath; if (dir.endsWith(".jar")) { classpath = new JarClasspathRoot(root); } else { classpath = new DirectoryClasspathRoot(root); } // Get the class readers for each class buildCrlist(root, classpath, prefix, "", crlist, flags.isVerbose()); return crlist; } private static void buildCrlist(URL root, ClasspathRoot classpath, String prefix, String packageName, List<ClassReader> crlist, boolean verbose) throws MalformedURLException { for (String resource : classpath.getResources(packageName)) { if (resource.endsWith(".class")) { String className = packageName + resource; //className = className.replace(".class", "").replace('/', '.'); if (!className.contains("$")) { ClassReader cr; try { cr = getClassReader(classpath, className); if (validcr(cr, prefix)) { if (verbose) { System.out.println("Found: " + cr.getClassName().replace("/", ".")); } crlist.add(cr); } } catch (IOException e) { System.out.println("Failed to read " + className); } } } else if (resource.endsWith(".jar")) { String temp = root.getPath() + packageName + resource; URL jarRoot = new File(temp).toURI().toURL(); ClasspathRoot jarPath = new JarClasspathRoot(jarRoot); buildCrlist(jarRoot, jarPath, prefix, "", crlist, verbose); } else { buildCrlist(root, classpath, prefix, packageName + resource + "/", crlist, verbose); } } } /*************************************************************************/ /* VISITOR METHODS */ /*************************************************************************/ public void addStaticField(String name, String desc) { // Visitor guarantees field is private, static and not final if (desc.startsWith("L")) { desc = desc.substring(1, desc.length() - 1); currClass.addStaticField(name, removePrefix(desc)); } } public void visitStaticField(String name, String desc) { // Visitor guarantees field is public and static currClass.visitStaticField(name, desc, flags); } public void visitStaticMethod(String name, String desc) { String[] ret = splitDesc(desc); String params = ret[0]; String returnType = ret[1]; if (returnType.startsWith("L")) { returnType = returnType.substring(1); if (!returnType.startsWith("java/") && returnType.startsWith(prefix)) { currClass.visitStaticMethod(name, params, removePrefix(returnType), flags); } } } public void invokeStatic(String referencedClass, String desc) { String[] ret = splitDesc(desc); String params = ret[0]; String returnType = ret[1]; if (returnType.startsWith("L")) { returnType = returnType.substring(1); returnType = removePrefix(returnType); referencedClass = removePrefix(referencedClass); Clazz cl = classes.get(referencedClass); if (cl != null && cl != currClass && (currClass.isSpecial() || !flags.ignoreOthers())) { if (returnType.equals(referencedClass) && cl.isSingleton()) { currClass.addClassIUse(cl); } else if (cl.doIHingle(returnType) && cl.isHingleton()) { currClass.addClassIUse(cl); } else if (params.equals("") && cl.isMingleton()) { currClass.addClassIUse(cl); } } } } public void fieldInstruction(String owner) { Clazz cl = classes.get(removePrefix(owner)); if (cl != null && cl != currClass && cl.isFingleton()) { currClass.addClassIUse(cl); } } /*************************************************************************/ /* OUTPUT METHODS */ /*************************************************************************/ public String getOutput(boolean pad) { return stats.getOutput(flags, pad); } public String getGraphMlOutput() { HashMap<String, OutputNode> nodes = getOutputNodes(); String nodeOutput = ""; String edgeOutput = ""; // Verbose: begin output if (flags.isVerbose()) { System.out.print("Generating output graph... "); } // Fill buffers for (OutputNode node : nodes.values()) { nodeOutput += node.getGraphMlNode(); edgeOutput += node.getGraphMlEdges(); } String banner = ""; if (flags.showBanner()) { banner = " <node id=\"banner\">\n" + " <data key=\"d0\">\n" + " <y:ShapeNode>\n" + " <y:Fill color = \"#0000FF\"/>\n" + " <y:NodeLabel textColor=\"#FFFFFF\" fontSize=\"24\">" + stats.getOutput(flags, false).replace("\n", "
") + "</y:NodeLabel>\n" + " <y:Shape type=\"rectangle\"/>\n" + " </y:ShapeNode>\n" + " </data>\n" + " </node>\n"; } // Verbose: end output if (flags.isVerbose()) { System.out.println("done."); } return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns/graphml\" xmlns:y=\"http://www.yworks.com/xml/graphml\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd\">\n" + "<key id=\"d0\" for=\"node\" yfiles.type=\"nodegraphics\"/>\n" + "<key id=\"d1\" for=\"edge\" yfiles.type=\"edgegraphics\"/>\n" + " <graph id=\"SingletonDetector\" edgedefault=\"directed\">\n" + banner + nodeOutput + edgeOutput + " </graph>\n" + "</graphml>"; } private HashMap<String, OutputNode> getOutputNodes() { HashMap<String, OutputNode> nodes = new HashMap<String, OutputNode>(); for (String className : classes.keySet()) { Clazz clazz = classes.get(className); if (clazz.isDrawn()) { if (!nodes.containsKey(className)) { nodes.put(className, newOutputNode(clazz)); } // Process all classes that this class uses for (Clazz usedClazz : clazz.getClassesIUse()) { if (usedClazz.isDrawn()) { String usedName = usedClazz.getName(); // Add the using class if (!nodes.containsKey(usedName)) { nodes.put(usedName, newOutputNode(usedClazz)); } // Add the edge nodes.get(className).addEdge(nodes.get(usedName)); } } } } return nodes; } private OutputNode newOutputNode(Clazz clazz) { OutputNode node; if (clazz.isSingleton()) { node = new SingletonOutputNode(clazz.getName()); } else if (clazz.isHingleton()) { String classIHingle = clazz.getClassIHingle(); node = new HingletonOutputNode(clazz.getName(), classIHingle); } else if (clazz.isMingleton()) { node = new MingletonOutputNode(clazz.getName()); } else if (clazz.isFingleton()) { node = new FingletonOutputNode(clazz.getName()); } else { node = new OtherOutputNode(clazz.getName()); } return node; } }