Permalink
Browse files

Created.

  • Loading branch information...
1 parent 92d3c20 commit 1bc8c1ef8034889571e695d7afcd30042a34d33f Jakub Holy committed Feb 6, 2012
Showing with 263 additions and 0 deletions.
  1. +263 −0 miniprojects/generics-detector/CollectionGenericsTypeExctractor.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.tree.JCTree;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import java.io.File;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.Collections.singleton;
+
+/**
+ * Parse Java classes and log the type of elements of generics collections and maps returned from getters.
+ *
+ * <h3>Current limitations</h3>
+ * <ul>
+ * <li>Doesn't take into account inherited getters</li>
+ * <li>Not very fail-proof</li>
+ * <li>Doesn't recognize Collection/Map implementations that are nested/inner classes</li>
+ * </ul>
+ *
+ * @see <a href="See http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html">
+ * Source Code Analysis Using Java 6 APIs</a> by Seema Richard, 4/2008 - most of the code taken from here
+ * @see <a href="http://crazyjavahacking.org/the-story-of-javac-ast-visualization-on-netbeans-platform/">
+ * The story of javac AST Visualization on NetBeans Platform</a> - blog and open-source project - some of its code might be useful
+ */
+public class CollectionGenericsTypeExctractor {
+
+ /**
+ * Sun Java Compiler Tree API visitor that finds all getters returning maps/collections with generics
+ * and logs type of values they declare to contain.
+ */
+ public static class CodeAnalyzerTreeVisitor extends TreePathScanner<Object, Trees> {
+
+ /**
+ * Category of type returned by a getter - only collections and maps are interesting for us.
+ */
+ private static enum TypeCategory {
+ COLLECTION(0), MAP(1), OTHER(-1);
+
+ private final int valueTypeArgumentIdx;
+
+ private TypeCategory(int valueTypeArgumentIdx) {
+ this.valueTypeArgumentIdx = valueTypeArgumentIdx;
+ }
+
+ public boolean isCollectionOrMap() {
+ return !this.equals(OTHER);
+ }
+
+ /**
+ * Which type argument of this type is used for values?
+ * For collection it's the first and only one,
+ * for maps it's the second one (first is used for keys).
+ */
+ public int getValueTypeArgumentIdx() {
+ return valueTypeArgumentIdx;
+ }
+ }
+
+ @Override
+ public Object visitMethod(MethodTree methodTree, Trees trees) {
+ String typeNameQualified = getEnclosingClassNameIfAvailable(trees);
+
+ // Skip or bad stuff happens
+ if (typeNameQualified == null) {
+ return super.visitMethod(methodTree, trees);
+ }
+
+ Tree returnType = methodTree.getReturnType(); // null for void method
+ if (getter(methodTree) && returnType instanceof ParameterizedTypeTree) {
+ assert Tree.Kind.PARAMETERIZED_TYPE == returnType.getKind();
+ ParameterizedTypeTree parametrizedReturnType = (ParameterizedTypeTree) returnType;
+
+ TypeCategory category = detectTypeCategory(parametrizedReturnType);
+ if (category.isCollectionOrMap()) {
+ Tree valueTypeArgument = parametrizedReturnType.getTypeArguments().get(category.getValueTypeArgumentIdx());
+ final String qualifiedGenericTypeName = getQualifiedType(valueTypeArgument);
+
+ String methodJsfName = getMethodJsfName(methodTree);
+ System.out.println("FOUND " + typeNameQualified + "." + methodJsfName + ".*=" + qualifiedGenericTypeName);
+ // Unqualified name:
+ // assert Tree.Kind.IDENTIFIER == valueTypeArgument.getKind(); IdentifierTree typeIdentifier = (IdentifierTree) valueTypeArgument;
+ // typeIdentifier.getName().toString();
+ }
+ }
+ return super.visitMethod(methodTree, trees);
+ }
+
+ private String getEnclosingClassNameIfAvailable(Trees trees) {
+ // Method element is enclosed by its class, I suppose
+ try {
+ TypeElement typeElement = (TypeElement) trees.getElement(getCurrentPath()).getEnclosingElement();
+ return typeElement.getQualifiedName().toString();
+ } catch (NullPointerException e) {
+ // getElement will return null for an inner anonymous class
+ return null;
+ }
+ }
+
+ private String getQualifiedType(/*IdentifierTree*/Tree typeArgument) {
+ // Ugly hack; without this we could get only typeArgument.getName().toString() bu it is
+ // usually unqualified and I've no idea how to use the API to look up the fully qualified name
+ return ((JCTree) typeArgument).type.toString();
+ }
+
+ private TypeCategory detectTypeCategory(ParameterizedTypeTree parametrizedReturnType) {
+ // BEWARE: Doesn't work for nested classes (we'd need to replace the last '.' with '$'
+ String qualifiedReturnType = getQualifiedType((IdentifierTree) parametrizedReturnType.getType());
+ try {
+ Class<?> returnClazz = Class.forName(qualifiedReturnType);
+ if (Collection.class.isAssignableFrom(returnClazz)) {
+ return TypeCategory.COLLECTION;
+ } else if (Map.class.isAssignableFrom(returnClazz)) {
+ return TypeCategory.MAP;
+ }
+ } catch (ClassNotFoundException e) {
+ System.err.println("detectTypeCategory: Failed to load class for '" + qualifiedReturnType + "', ignoring");
+ }
+
+ return TypeCategory.OTHER;
+ }
+
+ private static boolean getter(MethodTree methodTree) {
+ final String methodName = methodTree.getName().toString();
+ return methodTree.getModifiers().getFlags().contains(Modifier.PUBLIC) &&
+ methodTree.getParameters().size() == 0 &&
+ methodName.length() > 3 &&
+ methodName.startsWith("get");
+ }
+
+ /** For "getName" return "name". */
+ private static String getMethodJsfName(MethodTree methodTree) {
+ String nameWithoutGet = methodTree.getName().toString().substring(3);
+ return nameWithoutGet.substring(0, 1).toLowerCase() + nameWithoutGet.substring(1);
+ }
+
+ }
+
+ /**
+ * Custom annotation processor to run our visitor
+ */
+ @SupportedSourceVersion(SourceVersion.RELEASE_6)
+ @SupportedAnnotationTypes("*")
+ public static class CodeAnalyzerProcessor extends AbstractProcessor {
+
+ private final CodeAnalyzerTreeVisitor visitor = new CodeAnalyzerTreeVisitor();
+ private Trees trees;
+
+ @Override
+ public void init(ProcessingEnvironment pe) {
+ super.init(pe);
+ trees = Trees.instance(pe);
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
+ for (Element e : roundEnvironment.getRootElements()) {
+ // Normally the ellement should represent a class
+ TreePath tp = trees.getPath(e);
+ // invoke the scanner
+ visitor.scan(tp, trees);
+ }
+ return true; // handled, don't invoke other processors
+ }
+ }
+
+ /**
+ * Detect types of elements of generic collections/maps in classes in the provided directory.
+ * Required argument: the directory to search (might be relative path).
+ * BEWARE: It won't work if the sources cannot be compiled, i.e. if the dependencies aren't on the classpath.
+ * <p>
+ * Set the system property sourcefind.debug to get more info.
+ * </p>
+ * <p>
+ * NOTE: You need to have Sun's tool.jar on the classpath (i.e. run this with JDK, not JRE).
+ * </p>
+ *
+ */
+ public static void main(String[] args) throws Exception {
+
+ if (args.length != 1) {
+ throw new IllegalArgumentException("Exactly one argument required: the root directory to search for source codes");
+ }
+
+ File searchDir = new File(args[0]);
+ if (!searchDir.isDirectory()) {
+ throw new IllegalArgumentException("The provided path is not a directory: " + searchDir.getAbsolutePath());
+ }
+
+ System.out.println(">>> Going to search Java files under " + searchDir);
+
+ //Get an instance of java compiler
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+
+ //Get a new instance of the standard file manager implementation
+ StandardJavaFileManager fileManager = compiler.
+ getStandardFileManager(null, null, null);
+
+ // Get the list of java file objects, in this case we have only
+ // one file, TestClass.java
+
+ System.out.println(">>> DETECTING SOURCES");
+ fileManager.setLocation(StandardLocation.SOURCE_PATH, singleton(searchDir.getAbsoluteFile()));
+ Iterable<JavaFileObject> sources = fileManager.list(
+ StandardLocation.SOURCE_PATH //locationFor("src/test/java")
+ , ""
+ , singleton(JavaFileObject.Kind.SOURCE)
+ , true);
+
+ if (System.getProperty("sourcefind.debug", null) != null) {
+ System.out.println(">>> Sources found:\n" + sources);
+ }
+
+ // Create the compilation task
+ JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sources);
+
+ // Set the annotation processor to the compiler task
+ task.setProcessors(singleton(new CodeAnalyzerProcessor()));
+
+ // Perform the compilation task.
+ System.out.println(">>> DETECTING GENERICS");
+ task.call();
+
+ System.out.println(">>>> DONE DETECTING GENERICS!");
+ }
+}

0 comments on commit 1bc8c1e

Please sign in to comment.