/*==========================================================================*\ | $Id: Annotation.java,v 1.1 2011/06/09 15:31:24 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2011 Virginia Tech | | This file is part of the Student-Library. | | The Student-Library is free software; you can redistribute it and/or | modify it under the terms of the GNU Lesser General Public License as | published by the Free Software Foundation; either version 3 of the | License, or (at your option) any later version. | | The Student-Library is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU Lesser General Public License for more details. | | You should have received a copy of the GNU Lesser General Public License | along with the Student-Library; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package student.testingsupport.reflection; import java.util.ArrayList; import java.util.List; import student.testingsupport.reflection.internal.Types; //------------------------------------------------------------------------- /** * TODO: document. * * TODO: add annotation support. * * @param <ClassType> If present, this is a constraint on the type that * this object represents. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.1 $, $Date: 2011/06/09 15:31:24 $ */ public class Annotation<ClassType> extends NameFilter<Annotation<ClassType>, Class<ClassType>> { //~ Fields ................................................................ //~ Constructor ........................................................... // ---------------------------------------------------------- /** * Create a new Type object that represents a type filter. * @param previous The previous filter in the chain of filters. * @param descriptionOfThisStage A description of this stage in the * filter chain. */ protected Annotation( Annotation<ClassType> previous, String descriptionOfThisStage) { super(previous, descriptionOfThisStage); } //~ Public Methods ........................................................ // ---------------------------------------------------------- /** * A starting point for type filters where the name is unspecified. */ public static final Annotation<?> annotation = new Annotation<Object>(null, null).typeResolver(); // ---------------------------------------------------------- /** * Create a new Type object. * @param name The name of the type (class). * @return a new Type filter object representing the named field. */ public static Annotation<?> annotation(String name) { return annotation.withName(name); } // ---------------------------------------------------------- /** * Create a new Type object from a {@link Class} object. * @param aClass The class to represent. * @param <T> This type is deduced from aClass. * @return a new Type object representing the given class. */ public static <T> Annotation<T> annotation(Class<T> aClass) { if (aClass == null) { @SuppressWarnings("unchecked") Annotation<T> result = (Annotation<T>)annotation; return result; } @SuppressWarnings("unchecked") Annotation<T> result = ((Annotation<T>)annotation) .createFreshFilter(aClass); return result; } // ---------------------------------------------------------- /** * Restrict type searches that look for all classes in a package, or * all classes visible to a class loader, to the specified list of * search locations (the default is the search path in the system * property * <code>student.testingsupport.reflection.internal.Types.searchPath</code>, * or the set of directories visible from the class loader where * this class was loaded). * This restriction affects all type searches when a fully qualified * class name is not provided. * @param searchPath The search path to use. A classpath-like string * denoting the classpath locations where searching should be performed. */ public static void restrictSearchesTo(String searchPath) { Type.restrictSearchesTo(searchPath); } // ---------------------------------------------------------- /** * Force type searches to search all possible locations (i.e., remove * the default initial restrictions, which limit searches either to just * directories (not jar files) or to the locations specified in the * system property * <code>student.testingsupport.reflection.internal.Types.searchPath</code>). * Note that in some instances this may cause some type filters to * exhaust available PermGen space, requiring programs to be run * with an additional JVM command line argument to increase the max * PermGen size. */ public static void searchEverywhere() { Type.searchEverywhere(); } // ---------------------------------------------------------- /** * Restrict this filter to only admit declarations with the specified * name. If the name is a simple name (that is, * @param name The name required by the resulting filter. * @return A new filter with the given restriction. */ public Annotation<ClassType> withName(String name) { if (name == null) { return this; } int pos = name.lastIndexOf('.'); if (pos > 0) { final String simpleName = name.substring(pos + 1); return (new Annotation<ClassType>(this, "with name " + simpleName) { @Override protected String getTargetSimpleName() { return simpleName; } }).inPackage(name.substring(0, pos)); } else { final String simpleName = name; return (new Annotation<ClassType>(this, "with name " + simpleName) { @Override protected String getTargetSimpleName() { return simpleName; } }).typeResolver(); } } // ---------------------------------------------------------- /** * Specify the class loader that should be used to look up (load) the * class this type represents. * @param loader The class loader to use to resolve this type. * @return The current Type object, for method chaining. */ public Annotation<ClassType> fromClassLoader(final ClassLoader loader) { if (loader == null) { return this; } return (new Annotation<ClassType>(this, "loaded from " + loader) { @Override protected ClassLoader getTargetLoader() { return loader; } }).typeResolver(); } // ---------------------------------------------------------- /** * Restrict this filter to only types declared in the specified package. * @param packageName The package where this type must be found. The * empty string ("") represents the default package, * while null represents any possible package. * @return The restricted filter. */ public Annotation<ClassType> inPackage(final String packageName) { String constraint = "in any package"; if (packageName != null) { constraint = packageName.isEmpty() ? "in default package" : ("in package " + packageName); } return (new Annotation<ClassType>(this, constraint) { @Override protected boolean thisFilterAccepts(Class<ClassType> object) { return packageName == null || Types.isInPackage(object, packageName); } @Override protected String getTargetPackage() { return packageName; } }).typeResolver(); } // ---------------------------------------------------------- /** * All this filter to match a corresponding type in any package. * @return The (less) restricted filtered. */ public Annotation<ClassType> inAnyPackage() { return inPackage(null); } // ---------------------------------------------------------- /** * Determine whether the match(es) of this filter are located in a * specified package. This predicate is quantifiable. * @param packageName The package to test against. * @return True if the types matching this filter are found in the * specified package, according to the current quantifier constraints. */ public boolean isInPackage(final String packageName) { return quantify.evaluate(new Predicate<Class<ClassType>>() { public boolean isSatisfiedBy(Class<ClassType> object) { return Types.isInPackage(object, packageName); } }); } // ---------------------------------------------------------- /** * TODO: document. * @param annotation TODO: document. * @return TODO: document. */ public boolean isInSamePackageAs(Annotation<?> annotation) { return isInPackage(annotation.getPackageName()); } // ---------------------------------------------------------- /** * TODO: document. * @param type TODO: document. * @return TODO: document. */ public boolean isInSamePackageAs(Type<?> type) { return isInPackage(type.getPackageName()); } // ---------------------------------------------------------- /** * TODO: document. * @param type TODO: document. * @return TODO: document. */ public boolean isInSamePackageAs(Class<?> type) { return isInSamePackageAs(Type.type(type)); } // ---------------------------------------------------------- /** * TODO: document. * @return TODO: document. */ public String getPackageName() { Package pkg = raw().getPackage(); return (pkg == null) ? null : pkg.getName(); } //~ Protected Methods ..................................................... // ---------------------------------------------------------- @Override protected Annotation<ClassType> createFreshFilter( Annotation<ClassType> previous, String descriptionOfThisStage) { return new Annotation<ClassType>(previous, descriptionOfThisStage); } // ---------------------------------------------------------- protected Annotation<ClassType> createFreshFilter( final Class<ClassType> rawClass) { return new Annotation<ClassType>(null, describe(rawClass)) { private List<Class<ClassType>> result = new ArrayList<Class<ClassType>>(); { result.add(rawClass); } // ---------------------------------------------------------- @Override protected List<Class<ClassType>> candidatesFromThisFilter() { return result; } }; } // ---------------------------------------------------------- @Override protected String filteredObjectDescription() { return "annotation"; } // ---------------------------------------------------------- @Override protected String nameOf(Class<ClassType> object) { return object.getCanonicalName(); } // ---------------------------------------------------------- @Override protected int modifiersFor(Class<ClassType> object) { return object.getModifiers(); } // ---------------------------------------------------------- /** * Look up the class loader that should be used to resolve this type. * A value of null implies using the default class loader. * @return The class loader to use, or null if the default should be used. */ protected ClassLoader getTargetLoader() { if (previousFilter() != null && previousFilter() instanceof Annotation) { return ((Annotation<ClassType>)previousFilter()).getTargetLoader(); } else { return null; } } // ---------------------------------------------------------- /** * Look up the package name that should be used to resolve this type. * A value of "" implies using the default package. A value of null * implies searching in <b>every</b> package. The default here is * to see if anything earlier in the chain has a constraint, and if * not, default to "". * @return The package to search in. */ protected String getTargetPackage() { if (previousFilter() != null && previousFilter() instanceof Annotation) { return ((Annotation<ClassType>)previousFilter()).getTargetPackage(); } else { return ""; } } // ---------------------------------------------------------- /** * Look up the simple (package-less) class name that should be used to * resolve this type. * @return The simple (package-less) name to search for. */ protected String getTargetSimpleName() { if (previousFilter() != null && previousFilter() instanceof Annotation) { return ((Annotation<ClassType>)previousFilter()).getTargetSimpleName(); } else { return null; } } // ---------------------------------------------------------- /** * This creates a "filter" that simply performs type resolution. It * deeply looks through the chain for package constraints, name * constraints, and class loader constraints, and then uses them to * perform the search. * @return A new filter that performs type resolution when necessary. */ protected Annotation<ClassType> typeResolver() { return new Annotation<ClassType>(this, null) { @Override public String getName() { String pkg = getTargetPackage(); String name = getTargetSimpleName(); if (name == null) { name = "<any-name>"; } if (pkg == null) { pkg = "<any-package>"; } if (!pkg.isEmpty()) { name = pkg + "." + name; } return name; } @Override protected boolean guaranteesMultipleMatches() { return getTargetPackage() == null && getTargetSimpleName() == null; } @Override protected List<Class<ClassType>> candidatesFromThisFilter() { String pkg = getTargetPackage(); String name = getTargetSimpleName(); ClassLoader loader = getTargetLoader(); List<Class<?>> raw = null; if (name != null) { if (pkg == null) { // Searching for all classes with this name! raw = Types.allClassesWithSimpleName(name, loader); } else { if (!pkg.isEmpty()) { name = pkg + "." + name; } Class<?> c = Types.classForName(name, loader); if (c != null) { raw = new ArrayList<Class<?>>(); raw.add(c); } } } else if (pkg != null) { raw = Types.classesInPackage(pkg, loader); } else { raw = Types.allClasses(loader); } @SuppressWarnings({ "unchecked", "rawtypes" }) List<Class<ClassType>> found = (List)raw; return found; } }; } }