diff --git a/build.xml b/build.xml index 75c16eee..5e4a3068 100644 --- a/build.xml +++ b/build.xml @@ -23,7 +23,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -231,10 +231,10 @@ - + - + diff --git a/hamcrest-generator/src/main/java/org/hamcrest/generator/FactoryMethod.java b/hamcrest-generator/src/main/java/org/hamcrest/generator/FactoryMethod.java index d7dbf92a..915c66b7 100644 --- a/hamcrest-generator/src/main/java/org/hamcrest/generator/FactoryMethod.java +++ b/hamcrest-generator/src/main/java/org/hamcrest/generator/FactoryMethod.java @@ -46,7 +46,7 @@ public String getMatcherClass() { public String getReturnType() { return returnType; } - + /** * Original name of factory method. */ @@ -115,7 +115,7 @@ public void setJavaDoc(String javaDoc) { public String getJavaDoc() { return javaDoc; } - + // Generated in Eclipse... // n.b. Doesn't include returnType @Override diff --git a/hamcrest-generator/src/main/java/org/hamcrest/generator/QDox.java b/hamcrest-generator/src/main/java/org/hamcrest/generator/QDox.java index c1798f21..6203a9e6 100644 --- a/hamcrest-generator/src/main/java/org/hamcrest/generator/QDox.java +++ b/hamcrest-generator/src/main/java/org/hamcrest/generator/QDox.java @@ -1,11 +1,11 @@ package org.hamcrest.generator; -import com.thoughtworks.qdox.JavaDocBuilder; -import com.thoughtworks.qdox.model.JavaClass; - import java.io.File; import java.io.Reader; +import com.thoughtworks.qdox.JavaProjectBuilder; +import com.thoughtworks.qdox.model.JavaClass; + /** * Wraps QDox library. This is because to ease distribution, QDox is bundled into * hamcrest-generator.jar and has its package renamed to ensure there is no conflict @@ -16,7 +16,7 @@ */ public class QDox { - private final JavaDocBuilder javaDocBuilder = new JavaDocBuilder(); + private final JavaProjectBuilder javaDocBuilder = new JavaProjectBuilder(); public void addSourceTree(File sourceDir) { javaDocBuilder.addSourceTree(sourceDir); diff --git a/hamcrest-generator/src/main/java/org/hamcrest/generator/QDoxFactoryReader.java b/hamcrest-generator/src/main/java/org/hamcrest/generator/QDoxFactoryReader.java index 7883a6f2..7c2ce322 100644 --- a/hamcrest-generator/src/main/java/org/hamcrest/generator/QDoxFactoryReader.java +++ b/hamcrest-generator/src/main/java/org/hamcrest/generator/QDoxFactoryReader.java @@ -1,14 +1,20 @@ package org.hamcrest.generator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + import com.thoughtworks.qdox.model.DocletTag; +import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.JavaParameter; -import com.thoughtworks.qdox.model.Type; - -import java.util.Iterator; -import java.util.List; -import java.util.regex.Pattern; +import com.thoughtworks.qdox.model.JavaType; +import com.thoughtworks.qdox.model.JavaTypeVariable; /** * Wraps an existing sequence of FactoryMethods, and attempts to pull in @@ -20,77 +26,96 @@ */ public class QDoxFactoryReader implements Iterable { - private final Iterable wrapped; private final JavaClass classSource; - private static final Pattern GENERIC_REGEX = Pattern.compile("<.*>"); - private static final Pattern VARARGS_REGEX = Pattern.compile("...", Pattern.LITERAL); + private JavaClass factoryAnnotation; + + private static final Pattern GENERIC_REGEX = Pattern.compile("^<(.*)>$"); - public QDoxFactoryReader(Iterable wrapped, QDox qdox, String className) { - this.wrapped = wrapped; + public QDoxFactoryReader(QDox qdox, String className) { this.classSource = qdox.getClassByName(className); + this.factoryAnnotation = qdox.getClassByName("org.hamcrest.Factory"); } @Override public Iterator iterator() { - final Iterator iterator = wrapped.iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } + List methods = new ArrayList(); - @Override - public FactoryMethod next() { - return enhance(iterator.next()); + for (JavaMethod jm : classSource.getMethods()) { + if (!isFactoryMethod(jm)) { + continue; } - @Override - public void remove() { - iterator.remove(); + FactoryMethod fm = new FactoryMethod(typeToString(classSource), jm.getName(), typeToString(jm.getReturnType())); + + for (JavaTypeVariable tv : jm.getTypeParameters()) { + String decl = tv.getGenericFullyQualifiedName(); + decl = GENERIC_REGEX.matcher(decl).replaceFirst("$1"); + fm.addGenericTypeParameter(decl); } - }; - } - private FactoryMethod enhance(FactoryMethod factoryMethod) { - JavaMethod methodSource = findMethodInSource(factoryMethod); - if (methodSource != null) { - factoryMethod.setJavaDoc(createJavaDocComment(methodSource)); - JavaParameter[] parametersFromSource - = methodSource.getParameters(); - List parametersFromReflection - = factoryMethod.getParameters(); - - if (parametersFromReflection.size() == parametersFromSource.length) { - for (int i = 0; i < parametersFromSource.length; i++) { - parametersFromReflection.get(i).setName( - parametersFromSource[i].getName()); + for (JavaParameter p : jm.getParameters()) { + String type = typeToString(p.getType()); + + // Special case for var args methods.... String[] -> String... + if (p.isVarArgs()) { + type += "..."; } + + fm.addParameter(type, p.getName()); } + + for (JavaType exception : jm.getExceptions()) { + fm.addException(typeToString(exception)); + } + + fm.setJavaDoc(createJavaDocComment(jm)); + + String generifiedType = GENERIC_REGEX.matcher(fm.getReturnType()).replaceFirst("$1"); + if (!generifiedType.equals(fm.getReturnType())) { + fm.setGenerifiedType(generifiedType); + } + + methods.add(fm); } - return factoryMethod; + + Collections.sort(methods, new Comparator() { + @Override public int compare(FactoryMethod o1, FactoryMethod o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + return methods.iterator(); } /** - * Attempts to locate the source code for a specific method, by cross-referencing - * the signature returned by reflection with the list of methods parsed by QDox. + * Determine whether a particular method is classified as a matcher factory method. + *

+ *

The rules for determining this are: + * 1. The method must be public static. + * 2. It must have a return type of org.hamcrest.Matcher (or something that extends this). + * 3. It must be marked with the org.hamcrest.Factory annotation. + *

+ *

To use another set of rules, override this method. */ - private JavaMethod findMethodInSource(FactoryMethod factoryMethod) { - // Note, this doesn't always work - it struggles with some kinds of generics. - // This seems to cover most cases though. - List params = factoryMethod.getParameters(); - Type[] types = new Type[params.size()]; - boolean varArgs = false; - for (int i = 0; i < types.length; i++) { - String type = params.get(i).getType(); - varArgs = VARARGS_REGEX.matcher(type).find(); - // QDox ignores varargs and generics, so we strip them out to help QDox. - type = GENERIC_REGEX.matcher(type).replaceAll(""); - type = VARARGS_REGEX.matcher(type).replaceAll(""); - types[i] = new Type(type); + protected boolean isFactoryMethod(JavaMethod javaMethod) { + return javaMethod.isStatic() + && javaMethod.isPublic() + && hasFactoryAnnotation(javaMethod) + && !javaMethod.getReturnType().equals(JavaType.VOID); + } + + private boolean hasFactoryAnnotation(JavaMethod javaMethod) { + for (JavaAnnotation a : javaMethod.getAnnotations()) { + if (a.getType().equals(factoryAnnotation)) { + return true; + } } - JavaMethod[] methods = classSource.getMethodsBySignature(factoryMethod.getName(), types, false, varArgs); - return methods.length == 1 ? methods[0] : null; + return false; + } + + private static String typeToString(JavaType type) { + return type.getGenericFullyQualifiedName().replace('$', '.'); } /** @@ -98,8 +123,8 @@ private JavaMethod findMethodInSource(FactoryMethod factoryMethod) { */ private static String createJavaDocComment(JavaMethod methodSource) { String comment = methodSource.getComment(); - DocletTag[] tags = methodSource.getTags(); - if ((comment == null || comment.trim().length() == 0) && tags.length == 0) { + List tags = methodSource.getTags(); + if ((comment == null || comment.trim().length() == 0) && tags.isEmpty()) { return null; } StringBuilder result = new StringBuilder(); diff --git a/hamcrest-generator/src/main/java/org/hamcrest/generator/ReflectiveFactoryReader.java b/hamcrest-generator/src/main/java/org/hamcrest/generator/ReflectiveFactoryReader.java deleted file mode 100644 index f51e659c..00000000 --- a/hamcrest-generator/src/main/java/org/hamcrest/generator/ReflectiveFactoryReader.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.hamcrest.generator; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import static java.lang.reflect.Modifier.isPublic; -import static java.lang.reflect.Modifier.isStatic; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Iterator; - -/** - * Reads a list of Hamcrest factory methods from a class, using standard Java reflection. - *

Usage

- *
- * for (FactoryMethod method : new ReflectiveFactoryReader(MyMatchers.class)) {
- *   ...
- * }
- * 
- *

All methods matching signature '@Factory public static Matcher blah(blah)' will be - * treated as factory methods. To change this behavior, override {@link #isFactoryMethod(Method)}. - *

Caveat: Reflection is hassle-free, but unfortunately cannot expose method parameter names or JavaDoc - * comments, making the sugar slightly more obscure. - * - * @author Joe Walnes - * @see SugarGenerator - * @see FactoryMethod - */ -public class ReflectiveFactoryReader implements Iterable { - - private final Class cls; - - private final ClassLoader classLoader; - - public ReflectiveFactoryReader(Class cls) { - this.cls = cls; - this.classLoader = cls.getClassLoader(); - } - - @Override - public Iterator iterator() { - return new Iterator() { - - private int currentMethod = -1; - private Method[] allMethods = cls.getMethods(); - - @Override - public boolean hasNext() { - while (true) { - currentMethod++; - if (currentMethod >= allMethods.length) { - return false; - } else if (isFactoryMethod(allMethods[currentMethod])) { - return true; - } // else carry on looping and try the next one. - } - } - - @Override - public FactoryMethod next() { - if (outsideArrayBounds()) { - throw new IllegalStateException("next() called without hasNext() check."); - } - return buildFactoryMethod(allMethods[currentMethod]); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private boolean outsideArrayBounds() { - return currentMethod < 0 || allMethods.length <= currentMethod; - } - }; - } - - /** - * Determine whether a particular method is classified as a matcher factory method. - *

- *

The rules for determining this are: - * 1. The method must be public static. - * 2. It must have a return type of org.hamcrest.Matcher (or something that extends this). - * 3. It must be marked with the org.hamcrest.Factory annotation. - *

- *

To use another set of rules, override this method. - */ - protected boolean isFactoryMethod(Method javaMethod) { - return isStatic(javaMethod.getModifiers()) - && isPublic(javaMethod.getModifiers()) - && hasFactoryAnnotation(javaMethod) - && !Void.TYPE.equals(javaMethod.getReturnType()); - } - - @SuppressWarnings("unchecked") - private boolean hasFactoryAnnotation(Method javaMethod) { - // We dynamically load the Factory class, to avoid a compile time - // dependency on org.hamcrest.Factory. This gets around - // a circular bootstrap issue (because generator is required to - // compile core). - try { - final Class factoryClass = classLoader.loadClass("org.hamcrest.Factory"); - if (!Annotation.class.isAssignableFrom(factoryClass)) { - throw new RuntimeException("Not an annotation class: " + factoryClass.getCanonicalName()); - } - return javaMethod.getAnnotation((Class)factoryClass) != null; - } catch (ClassNotFoundException e) { - throw new RuntimeException("Cannot load hamcrest core", e); - } - } - - private static FactoryMethod buildFactoryMethod(Method javaMethod) { - FactoryMethod result = new FactoryMethod( - classToString(javaMethod.getDeclaringClass()), - javaMethod.getName(), - classToString(javaMethod.getReturnType())); - - for (TypeVariable typeVariable : javaMethod.getTypeParameters()) { - boolean hasBound = false; - StringBuilder s = new StringBuilder(typeVariable.getName()); - for (Type bound : typeVariable.getBounds()) { - if (bound != Object.class) { - if (hasBound) { - s.append(" & "); - } else { - s.append(" extends "); - hasBound = true; - } - s.append(typeToString(bound)); - } - } - result.addGenericTypeParameter(s.toString()); - } - Type returnType = javaMethod.getGenericReturnType(); - if (returnType instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) returnType; - Type generifiedType = parameterizedType.getActualTypeArguments()[0]; - result.setGenerifiedType(typeToString(generifiedType)); - } - - int paramNumber = 0; - for (Type paramType : javaMethod.getGenericParameterTypes()) { - String type = typeToString(paramType); - // Special case for var args methods.... String[] -> String... - if (javaMethod.isVarArgs() - && paramNumber == javaMethod.getParameterTypes().length - 1) { - type = type.replaceFirst("\\[\\]$", "..."); - } - result.addParameter(type, "param" + (++paramNumber)); - } - - for (Class exception : javaMethod.getExceptionTypes()) { - result.addException(typeToString(exception)); - } - - return result; - } - - /* - * Get String representation of Type (e.g. java.lang.String or Map<Stuff,? extends Cheese>). - *

- * Annoyingly this method wouldn't be needed if java.lang.reflect.Type.toString() behaved consistently - * across implementations. Rock on Liskov. - */ - private static String typeToString(Type type) { - return type instanceof Class ? classToString((Class) type): type.toString(); - } - - private static String classToString(Class cls) { - final String name = cls.isArray() ? cls.getComponentType().getName() + "[]" : cls.getName(); - return name.replace('$', '.'); - } - -} \ No newline at end of file diff --git a/hamcrest-generator/src/main/java/org/hamcrest/generator/config/XmlConfigurator.java b/hamcrest-generator/src/main/java/org/hamcrest/generator/config/XmlConfigurator.java index 42306b63..8a354c7e 100644 --- a/hamcrest-generator/src/main/java/org/hamcrest/generator/config/XmlConfigurator.java +++ b/hamcrest-generator/src/main/java/org/hamcrest/generator/config/XmlConfigurator.java @@ -3,7 +3,6 @@ import org.hamcrest.generator.HamcrestFactoryWriter; import org.hamcrest.generator.QDoxFactoryReader; import org.hamcrest.generator.QuickReferenceWriter; -import org.hamcrest.generator.ReflectiveFactoryReader; import org.hamcrest.generator.SugarConfiguration; import org.hamcrest.generator.SugarGenerator; import org.hamcrest.generator.QDox; @@ -15,6 +14,7 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; + import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -22,13 +22,11 @@ public class XmlConfigurator { private final SugarConfiguration sugarConfiguration; - private final ClassLoader classLoader; private final SAXParserFactory saxParserFactory; private final QDox qdox; - public XmlConfigurator(SugarConfiguration sugarConfiguration, ClassLoader classLoader) { + public XmlConfigurator(SugarConfiguration sugarConfiguration) { this.sugarConfiguration = sugarConfiguration; - this.classLoader = classLoader; saxParserFactory = SAXParserFactory.newInstance(); saxParserFactory.setNamespaceAware(true); qdox = new QDox(); @@ -46,20 +44,15 @@ public void load(InputSource inputSource) public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (localName.equals("factory")) { String className = attributes.getValue("class"); - try { - addClass(className); - } catch (ClassNotFoundException e) { - throw new SAXException("Cannot find Matcher class : " + className); - } + addClass(className); } } }); } - private void addClass(String className) throws ClassNotFoundException { - Class cls = classLoader.loadClass(className); + private void addClass(String className) { sugarConfiguration.addFactoryMethods( - new QDoxFactoryReader(new ReflectiveFactoryReader(cls), qdox, className)); + new QDoxFactoryReader(qdox, className)); } @@ -94,8 +87,8 @@ public static void main(String[] args) throws Exception { String packageName = dotIndex == -1 ? "" : fullClassName.substring(0, dotIndex); String shortClassName = fullClassName.substring(dotIndex + 1); - if (!outputDir.isDirectory()) { - System.err.println("Output directory not found : " + outputDir.getAbsolutePath()); + if (!outputDir.isDirectory() && !outputDir.mkdirs()) { + System.err.println("Unable to create directory not : " + outputDir.getAbsolutePath()); System.exit(-1); } @@ -109,7 +102,7 @@ public static void main(String[] args) throws Exception { sugarGenerator.addWriter(new QuickReferenceWriter(System.out)); XmlConfigurator xmlConfigurator - = new XmlConfigurator(sugarGenerator, XmlConfigurator.class.getClassLoader()); + = new XmlConfigurator(sugarGenerator); if (srcDirs.trim().length() > 0) { for (String srcDir : srcDirs.split(",")) { diff --git a/hamcrest-generator/src/test/java-source/test/AnotherMatcher.java b/hamcrest-generator/src/test/java-source/test/AnotherMatcher.java new file mode 100644 index 00000000..eb369764 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/AnotherMatcher.java @@ -0,0 +1,14 @@ +package test; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +@SuppressWarnings("rawtypes") +public static class AnotherMatcher implements Matcher { + @Override public void describeTo(Description description) { } + @Override public boolean matches(Object item) { return false; } + @Override public void describeMismatch(Object item, Description mismatchDescription) { } + @Override @Deprecated public void _dont_implement_Matcher___instead_extend_BaseMatcher_() { } + @Factory public static AnotherMatcher matcher3() { return null; } +} diff --git a/hamcrest-generator/src/test/java-source/test/ExceptionalMatchers.java b/hamcrest-generator/src/test/java-source/test/ExceptionalMatchers.java new file mode 100644 index 00000000..e6f7cc48 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/ExceptionalMatchers.java @@ -0,0 +1,15 @@ +package test; + +import java.io.IOException; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class ExceptionalMatchers { + + @Factory + public static Matcher withExceptions() throws Error, IOException, RuntimeException { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/G.java b/hamcrest-generator/src/test/java-source/test/G.java new file mode 100644 index 00000000..168120cf --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/G.java @@ -0,0 +1,17 @@ +package test; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class G { + + @Factory + public static & Comparable> Matcher> x(Set t, V v) { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/GenerifiedMatchers.java b/hamcrest-generator/src/test/java-source/test/GenerifiedMatchers.java new file mode 100644 index 00000000..f7845f42 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/GenerifiedMatchers.java @@ -0,0 +1,28 @@ +package test; + +import java.util.Comparator; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class GenerifiedMatchers { + + @Factory + public static Matcher> generifiedType() { + return null; + } + + @SuppressWarnings("rawtypes") + @Factory + public static Matcher noGenerifiedType() { + return null; + } + + @Factory + public static Matcher, Factory>> crazyType() { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/MatcherWithNestedClass.java b/hamcrest-generator/src/test/java-source/test/MatcherWithNestedClass.java new file mode 100644 index 00000000..52f1aff3 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/MatcherWithNestedClass.java @@ -0,0 +1,15 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class MatcherWithNestedClass { + + @Factory + public static SpecificMatcher firstMethod() { + return null; + } + + public static class SpecificMatcher extends Matcher { + } +} diff --git a/hamcrest-generator/src/test/java-source/test/MatchersWithDodgySignatures.java b/hamcrest-generator/src/test/java-source/test/MatchersWithDodgySignatures.java new file mode 100644 index 00000000..747ee687 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/MatchersWithDodgySignatures.java @@ -0,0 +1,36 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class MatchersWithDodgySignatures { + + @Factory + public Matcher notStatic() { + return null; + } + + @Factory + static Matcher notPublic() { + return null; + } + + public static Matcher noAnnotation() { + return null; + } + + @Factory + public static Matcher goodMethod() { + return null; + } + + @Factory + public static String anotherGoodMethod() { + return null; + } + + @Factory + public static void wrongReturnType() { + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/ParameterizedMatchers.java b/hamcrest-generator/src/test/java-source/test/ParameterizedMatchers.java new file mode 100644 index 00000000..eb7e15f4 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/ParameterizedMatchers.java @@ -0,0 +1,31 @@ +package test; + +import java.util.Collection; +import java.util.Set; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class ParameterizedMatchers { + + @Factory + public static Matcher withParam(String someString, int[] numbers, Collection things) { + return null; + } + + @Factory + public static Matcher withArray(String[] array) { + return null; + } + + @Factory + public static Matcher withVarArgs(String... things) { + return null; + } + + @Factory + public static Matcher withGenerifiedParam(Collection> things, Set[] x) { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/SimpleSetOfMatchers.java b/hamcrest-generator/src/test/java-source/test/SimpleSetOfMatchers.java new file mode 100644 index 00000000..c6a6d50e --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/SimpleSetOfMatchers.java @@ -0,0 +1,18 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class SimpleSetOfMatchers { + + @Factory + public static Matcher firstMethod() { + return null; + } + + @Factory + public static Matcher secondMethod() { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java-source/test/SomeMatcher.java b/hamcrest-generator/src/test/java-source/test/SomeMatcher.java new file mode 100644 index 00000000..032b4f71 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/SomeMatcher.java @@ -0,0 +1,10 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +@SuppressWarnings("rawtypes") +public class SomeMatcher { + @Factory public static Matcher matcher1() { return null; } + @Factory public static Matcher matcher2() { return null; } +} diff --git a/hamcrest-generator/src/test/java-source/test/SubMatcher.java b/hamcrest-generator/src/test/java-source/test/SubMatcher.java new file mode 100644 index 00000000..604bd513 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/SubMatcher.java @@ -0,0 +1,11 @@ +package test; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; + +public static final class SubMatcher implements Matcher { + @Override public void describeTo(Description description) { } + @Override public boolean matches(Object item) { return false; } + @Override public void describeMismatch(Object item, Description mismatchDescription) { } + @Override @Deprecated public void _dont_implement_Matcher___instead_extend_BaseMatcher_() { } +} diff --git a/hamcrest-generator/src/test/java-source/test/SubclassOfMatcher.java b/hamcrest-generator/src/test/java-source/test/SubclassOfMatcher.java new file mode 100644 index 00000000..ad9a5d9f --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/SubclassOfMatcher.java @@ -0,0 +1,8 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.generator.SubMatcher; + +public static class SubclassOfMatcher { + @Factory public static SubMatcher subclassMethod() { return null; } +} diff --git a/hamcrest-generator/src/test/java-source/test/WithJavaDoc.java b/hamcrest-generator/src/test/java-source/test/WithJavaDoc.java new file mode 100644 index 00000000..ce55b288 --- /dev/null +++ b/hamcrest-generator/src/test/java-source/test/WithJavaDoc.java @@ -0,0 +1,18 @@ +package test; + +import org.hamcrest.Factory; +import org.hamcrest.Matcher; + +public static class WithJavaDoc { + + /** + * Look at me! + * + * @return something + */ + @Factory + public static Matcher documented() { + return null; + } + +} diff --git a/hamcrest-generator/src/test/java/org/hamcrest/generator/QDoxFactoryReaderTest.java b/hamcrest-generator/src/test/java/org/hamcrest/generator/QDoxFactoryReaderTest.java index bbfe1634..da197874 100644 --- a/hamcrest-generator/src/test/java/org/hamcrest/generator/QDoxFactoryReaderTest.java +++ b/hamcrest-generator/src/test/java/org/hamcrest/generator/QDoxFactoryReaderTest.java @@ -1,9 +1,16 @@ package org.hamcrest.generator; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.StringReader; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -19,7 +26,8 @@ public final class QDoxFactoryReaderTest { String input = "" + "package org;\n" + "class SomeClass {\n" + - " Matcher someMethod(String realParamName) { ... } \n" + + " @org.hamcrest.Factory" + + " public static Matcher someMethod(String realParamName) { ... } \n" + "}\n"; FactoryMethod factoryMethod = wrapUsingQDoxedSource(method, "org.SomeClass", input); @@ -35,14 +43,15 @@ public final class QDoxFactoryReaderTest { String input = "" + "package org;\n" + "class SomeClass {\n" + - " Matcher someMethod(java.util.Collection realParamName) { ... } \n" + + " @org.hamcrest.Factory" + + " public static Matcher someMethod(java.util.Collection realParamName) { ... } \n" + "}\n"; FactoryMethod factoryMethod = wrapUsingQDoxedSource(method, "org.SomeClass", input); assertEquals("java.util.Collection", factoryMethod.getParameters().get(0).getType()); assertEquals("realParamName", factoryMethod.getParameters().get(0).getName()); } - + @Test public void extractsOriginalVarArgParameterNamesFromSource() { FactoryMethod method = new FactoryMethod("org.SomeClass", "someMethod", "unusedReturnType"); @@ -51,7 +60,8 @@ public final class QDoxFactoryReaderTest { String input = "" + "package org;\n" + "class SomeClass {\n" + - " Matcher someMethod(java.lang.String... realParamName) { ... } \n" + + " @org.hamcrest.Factory" + + " public static Matcher someMethod(java.lang.String... realParamName) { ... } \n" + "}\n"; FactoryMethod factoryMethod = wrapUsingQDoxedSource(method, "org.SomeClass", input); @@ -71,7 +81,8 @@ public final class QDoxFactoryReaderTest { " *\n" + " * @return stuff.\n" + " */\n" + - " Matcher someMethod() { ... } \n" + + " @org.hamcrest.Factory" + + " public static Matcher someMethod() { ... } \n" + "}\n"; FactoryMethod factoryMethod = wrapUsingQDoxedSource(method, "org.SomeClass", input); @@ -79,6 +90,164 @@ public final class QDoxFactoryReaderTest { factoryMethod.getJavaDoc()); } + @Test public void + iteratesOverFactoryMethods() throws FileNotFoundException { + final QDoxFactoryReader reader = readerForTestClass("SimpleSetOfMatchers"); + final List methods = methodsReadBy(reader); + + assertEquals(2, methods.size()); + + final String expectedClass = "test.SimpleSetOfMatchers"; + + assertEquals("firstMethod", methods.get(0).getName()); + assertEquals(expectedClass, methods.get(0).getMatcherClass()); + + assertEquals("secondMethod", methods.get(1).getName()); + assertEquals(expectedClass, methods.get(1).getMatcherClass()); + } + + @Test public void + onlyReadsPublicStaticAnnotatedMethodsThatReturnNonVoid() throws FileNotFoundException { + final QDoxFactoryReader reader = readerForTestClass("MatchersWithDodgySignatures"); + final List methods = methodsReadBy(reader); + + assertEquals(2, methods.size()); + assertEquals("anotherGoodMethod", methods.get(0).getName()); + assertEquals("goodMethod", methods.get(1).getName()); + } + + @Test public void + readsFullyQualifiedGenericType() throws FileNotFoundException { + FactoryMethod method = readMethod("GenerifiedMatchers", "generifiedType"); + assertEquals("org.hamcrest.Matcher>", method.getReturnType()); + } + + @Test public void + readsNullGenerifiedTypeIfNotPresent() throws FileNotFoundException { + FactoryMethod method = readMethod("GenerifiedMatchers", "noGenerifiedType"); + assertEquals("org.hamcrest.Matcher", method.getReturnType()); + } + + @Test public void + readsGenericsInGenericType() throws FileNotFoundException { + FactoryMethod method = readMethod("GenerifiedMatchers", "crazyType"); + assertEquals( + "org.hamcrest.Matcher,org.hamcrest.Factory>>", + method.getReturnType()); + } + + @Test public void + readsParameterTypes() throws FileNotFoundException { + FactoryMethod method = readMethod("ParameterizedMatchers", "withParam"); + List params = method.getParameters(); + assertEquals(3, params.size()); + + assertEquals("java.lang.String", params.get(0).getType()); + assertEquals("int[]", params.get(1).getType()); + assertEquals("java.util.Collection", params.get(2).getType()); + } + + @Test public void + readsArrayAndVarArgParameterTypes() throws FileNotFoundException { + FactoryMethod arrayMethod = readMethod("ParameterizedMatchers", "withArray"); + assertEquals("java.lang.String[]", arrayMethod.getParameters().get(0).getType()); + + FactoryMethod varArgsMethod = readMethod("ParameterizedMatchers", "withVarArgs"); + assertEquals("java.lang.String...", varArgsMethod.getParameters().get(0).getType()); + } + + @Test public void + readsGenerifiedParameterTypes() throws FileNotFoundException { + FactoryMethod method = readMethod("ParameterizedMatchers", "withGenerifiedParam"); + + assertEquals("java.util.Collection>", + method.getParameters().get(0).getType()); + + String expected = "java.util.Set[]"; + + assertEquals(expected, method.getParameters().get(1).getType()); + } + + @Test public void + canReadParameterNames() throws FileNotFoundException { + FactoryMethod method = readMethod("ParameterizedMatchers", "withParam"); + List params = method.getParameters(); + + assertEquals("someString", params.get(0).getName()); + assertEquals("numbers", params.get(1).getName()); + assertEquals("things", params.get(2).getName()); + } + + @Test public void + readsExceptions() throws FileNotFoundException { + FactoryMethod method = readMethod("ExceptionalMatchers", "withExceptions"); + List exceptions = method.getExceptions(); + assertEquals(3, exceptions.size()); + + assertEquals("java.lang.Error", exceptions.get(0)); + assertEquals("java.io.IOException", exceptions.get(1)); + assertEquals("java.lang.RuntimeException", exceptions.get(2)); + } + + @Test public void + canReadJavaDoc() throws FileNotFoundException { + FactoryMethod method = readMethod("WithJavaDoc", "documented"); + assertEquals("Look at me!\n\n@return something\n", method.getJavaDoc()); + } + + @Test public void + readsGenericTypeParameters() throws FileNotFoundException { + FactoryMethod method = readMethod("G", "x"); + assertEquals("T", method.getGenericTypeParameters().get(0)); + assertEquals("V extends java.util.List & java.lang.Comparable", + method.getGenericTypeParameters().get(1)); + assertEquals("org.hamcrest.Matcher>", method.getReturnType()); + assertEquals("java.util.Set", method.getParameters().get(0).getType()); + assertEquals("V", method.getParameters().get(1).getType()); + } + + @Test public void + catchesSubclasses() throws FileNotFoundException { + assertNotNull(readMethod("SubclassOfMatcher", "subclassMethod")); + } + + @Test public void + usesCorrectNameForNestedClasses() throws FileNotFoundException { + FactoryMethod method = readMethod("MatcherWithNestedClass", "firstMethod"); + + assertEquals("test.MatcherWithNestedClass.SpecificMatcher", method.getReturnType()); + } + + private static List methodsReadBy(final Iterable reader) { + final List extractedMethods = new ArrayList(); + for (FactoryMethod factoryMethod : reader) { + extractedMethods.add(factoryMethod); + } + Collections.sort(extractedMethods, new Comparator() { + @Override public int compare(FactoryMethod o1, FactoryMethod o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return extractedMethods; + } + + private static QDoxFactoryReader readerForTestClass(String name) + throws FileNotFoundException { + QDox qdox = new QDox(); + qdox.addSource(new FileReader("src/test/java-source/test/" + name + ".java")); + + return new QDoxFactoryReader(qdox, "test." + name); + } + + private static FactoryMethod readMethod(String name, String methodName) throws FileNotFoundException { + for (FactoryMethod method : readerForTestClass(name)) { + if (method.getName().equals(methodName)) { + return method; + } + } + return null; + } + private static FactoryMethod wrapUsingQDoxedSource(FactoryMethod originalMethod, String className, String input) { List originalMethods = new ArrayList(); originalMethods.add(originalMethod); @@ -86,14 +255,13 @@ private static FactoryMethod wrapUsingQDoxedSource(FactoryMethod originalMethod, QDox qdox = new QDox(); qdox.addSource(new StringReader(input)); - QDoxFactoryReader qDoxFactoryReader = new QDoxFactoryReader( - originalMethods, qdox, className); + QDoxFactoryReader qDoxFactoryReader = new QDoxFactoryReader(qdox, className); return getFirstFactoryMethod(qDoxFactoryReader); } private static FactoryMethod getFirstFactoryMethod(QDoxFactoryReader qDoxFactoryReader) { Iterator iterator = qDoxFactoryReader.iterator(); - iterator.hasNext(); + assertTrue(iterator.hasNext()); return iterator.next(); } } diff --git a/hamcrest-generator/src/test/java/org/hamcrest/generator/ReflectiveFactoryReaderTest.java b/hamcrest-generator/src/test/java/org/hamcrest/generator/ReflectiveFactoryReaderTest.java deleted file mode 100644 index a8e95df3..00000000 --- a/hamcrest-generator/src/test/java/org/hamcrest/generator/ReflectiveFactoryReaderTest.java +++ /dev/null @@ -1,302 +0,0 @@ -package org.hamcrest.generator; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.hamcrest.Description; -import org.hamcrest.Factory; -import org.hamcrest.Matcher; -import org.junit.Test; - -@SuppressWarnings("unused") -public final class ReflectiveFactoryReaderTest { - - public static class SimpleSetOfMatchers { - - @Factory - public static Matcher firstMethod() { - return null; - } - - @Factory - public static Matcher secondMethod() { - return null; - } - - } - - @Test public void - iteratesOverFactoryMethods() { - final ReflectiveFactoryReader reader = new ReflectiveFactoryReader(SimpleSetOfMatchers.class); - final List methods = methodsReadBy(reader); - - assertEquals(2, methods.size()); - - final String expectedClass = SimpleSetOfMatchers.class.getName().replace('$', '.'); - - assertEquals("firstMethod", methods.get(0).getName()); - assertEquals(expectedClass, methods.get(0).getMatcherClass()); - - assertEquals("secondMethod", methods.get(1).getName()); - assertEquals(expectedClass, methods.get(1).getMatcherClass()); - } - - public static class MatchersWithDodgySignatures { - - @Factory - public Matcher notStatic() { - return null; - } - - @Factory - static Matcher notPublic() { - return null; - } - - public static Matcher noAnnotation() { - return null; - } - - @Factory - public static Matcher goodMethod() { - return null; - } - - @Factory - public static String anotherGoodMethod() { - return null; - } - - @Factory - public static void wrongReturnType() { - } - - } - - @Test public void - onlyReadsPublicStaticAnnotatedMethodsThatReturnNonVoid() { - final ReflectiveFactoryReader reader = new ReflectiveFactoryReader(MatchersWithDodgySignatures.class); - final List methods = methodsReadBy(reader); - - assertEquals(2, methods.size()); - assertEquals("anotherGoodMethod", methods.get(0).getName()); - assertEquals("goodMethod", methods.get(1).getName()); - } - - public static class GenerifiedMatchers { - - @Factory - public static Matcher> generifiedType() { - return null; - } - - @SuppressWarnings("rawtypes") - @Factory - public static Matcher noGenerifiedType() { - return null; - } - - @Factory - public static Matcher, Factory>> crazyType() { - return null; - } - - } - - @Test public void - readsFullyQualifiedGenericType() { - FactoryMethod method = readMethod(GenerifiedMatchers.class, "generifiedType"); - assertEquals("java.util.Comparator", method.getGenerifiedType()); - } - - @Test public void - readsNullGenerifiedTypeIfNotPresent() { - FactoryMethod method = readMethod(GenerifiedMatchers.class, "noGenerifiedType"); - assertNull(method.getGenerifiedType()); - } - - @Test public void - readsGenericsInGenericType() { - FactoryMethod method = readMethod(GenerifiedMatchers.class, "crazyType"); - assertEquals( - "java.util.Map, org.hamcrest.Factory>", - method.getGenerifiedType()); - } - - public static class ParameterizedMatchers { - - @Factory - public static Matcher withParam(String someString, int[] numbers, Collection things) { - return null; - } - - @Factory - public static Matcher withArray(String[] array) { - return null; - } - - @Factory - public static Matcher withVarArgs(String... things) { - return null; - } - - @Factory - public static Matcher withGenerifiedParam(Collection> things, Set[] x) { - return null; - } - - } - - @Test public void - readsParameterTypes() { - FactoryMethod method = readMethod(ParameterizedMatchers.class, "withParam"); - List params = method.getParameters(); - assertEquals(3, params.size()); - - assertEquals("java.lang.String", params.get(0).getType()); - assertEquals("int[]", params.get(1).getType()); - assertEquals("java.util.Collection", params.get(2).getType()); - } - - @Test public void - readsArrayAndVarArgParameterTypes() { - FactoryMethod arrayMethod = readMethod(ParameterizedMatchers.class, "withArray"); - assertEquals("java.lang.String[]", arrayMethod.getParameters().get(0).getType()); - - FactoryMethod varArgsMethod = readMethod(ParameterizedMatchers.class, "withVarArgs"); - assertEquals("java.lang.String...", varArgsMethod.getParameters().get(0).getType()); - } - - @Test public void - readsGenerifiedParameterTypes() { - FactoryMethod method = readMethod(ParameterizedMatchers.class, "withGenerifiedParam"); - - assertEquals("java.util.Collection>", - method.getParameters().get(0).getType()); - - String expected = System.getProperty("java.version").startsWith("1.7.") - ? "java.util.Set<[Ljava.lang.String;>[]" - : "java.util.Set[]"; - - assertEquals(expected, method.getParameters().get(1).getType()); - } - - @Test public void - cannotReadParameterNamesSoMakesThemUpInstead() { - FactoryMethod method = readMethod(ParameterizedMatchers.class, "withParam"); - List params = method.getParameters(); - - assertEquals("param1", params.get(0).getName()); - assertEquals("param2", params.get(1).getName()); - assertEquals("param3", params.get(2).getName()); - } - - public static class ExceptionalMatchers { - - @Factory - public static Matcher withExceptions() throws Error, IOException, RuntimeException { - return null; - } - - } - - @Test public void - readsExceptions() { - FactoryMethod method = readMethod(ExceptionalMatchers.class, "withExceptions"); - List exceptions = method.getExceptions(); - assertEquals(3, exceptions.size()); - - assertEquals("java.lang.Error", exceptions.get(0)); - assertEquals("java.io.IOException", exceptions.get(1)); - assertEquals("java.lang.RuntimeException", exceptions.get(2)); - } - - public static class WithJavaDoc { - - /** - * Look at me! - * - * @return something - */ - @Factory - public static Matcher documented() { - return null; - } - - } - - @Test public void - cannotReadJavaDoc() { - // JavaDoc information is not available through reflection alone. - FactoryMethod method = readMethod(WithJavaDoc.class, "documented"); - assertEquals(null, method.getJavaDoc()); - } - - public static class G { - - @Factory - public static & Comparable> Matcher> x(Set t, V v) { - return null; - } - - } - - @Test public void - readsGenericTypeParameters() { - FactoryMethod method = readMethod(G.class, "x"); - assertEquals("T", method.getGenericTypeParameters().get(0)); - assertEquals("V extends java.util.List & java.lang.Comparable", - method.getGenericTypeParameters().get(1)); - assertEquals("java.util.Map", method.getGenerifiedType()); - assertEquals("java.util.Set", method.getParameters().get(0).getType()); - assertEquals("V", method.getParameters().get(1).getType()); - } - - public static final class SubMatcher implements Matcher { - @Override public void describeTo(Description description) { } - @Override public boolean matches(Object item) { return false; } - @Override public void describeMismatch(Object item, Description mismatchDescription) { } - @Override @Deprecated public void _dont_implement_Matcher___instead_extend_BaseMatcher_() { } - } - - public static class SubclassOfMatcher { - @Factory public static SubMatcher subclassMethod() { return null; } - } - - @Test public void - catchesSubclasses() { - assertNotNull(readMethod(SubclassOfMatcher.class, "subclassMethod")); - } - - private static List methodsReadBy(final ReflectiveFactoryReader reader) { - final List extractedMethods = new ArrayList(); - for (FactoryMethod factoryMethod : reader) { - extractedMethods.add(factoryMethod); - } - Collections.sort(extractedMethods, new Comparator() { - @Override public int compare(FactoryMethod o1, FactoryMethod o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - return extractedMethods; - } - - private static FactoryMethod readMethod(Class cls, String methodName) { - for (FactoryMethod method : new ReflectiveFactoryReader(cls)) { - if (method.getName().equals(methodName)) { - return method; - } - } - return null; - } -} \ No newline at end of file diff --git a/hamcrest-generator/src/test/java/org/hamcrest/generator/config/XmlConfiguratorTest.java b/hamcrest-generator/src/test/java/org/hamcrest/generator/config/XmlConfiguratorTest.java index ab4ee9ef..a796d07a 100644 --- a/hamcrest-generator/src/test/java/org/hamcrest/generator/config/XmlConfiguratorTest.java +++ b/hamcrest-generator/src/test/java/org/hamcrest/generator/config/XmlConfiguratorTest.java @@ -2,13 +2,11 @@ import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.List; -import org.hamcrest.Description; -import org.hamcrest.Factory; -import org.hamcrest.Matcher; import org.hamcrest.generator.FactoryMethod; import org.hamcrest.generator.FactoryWriter; import org.hamcrest.generator.SugarConfiguration; @@ -18,43 +16,27 @@ public final class XmlConfiguratorTest { private final MockSugarConfiguration sugarConfiguration = new MockSugarConfiguration(); - private final XmlConfigurator config = new XmlConfigurator(sugarConfiguration, getClass().getClassLoader()); + private final XmlConfigurator config = new XmlConfigurator(sugarConfiguration); @Test public void addsMatcherFactoryMethodsToConfiguration() throws Exception { + config.addSourceDir(new File("src/test/java-source")); config.load(createXml("" + "" + - " " + - " " + + " " + + " " + "")); final List result = sugarConfiguration.factoryMethods(); - assertTrue(result.contains(new FactoryMethod(SomeMatcher.class.getName().replace('$', '.'), "matcher1", "org.hamcrest.Matcher"))); - assertTrue(result.contains(new FactoryMethod(SomeMatcher.class.getName().replace('$', '.'), "matcher2", "org.hamcrest.Matcher"))); - assertTrue(result.contains(new FactoryMethod(AnotherMatcher.class.getName().replace('$', '.'), "matcher3", "org.hamcrest.MyMatcher"))); + assertTrue(result.contains(new FactoryMethod("test.SomeMatcher", "matcher1", "org.hamcrest.Matcher"))); + assertTrue(result.contains(new FactoryMethod("test.SomeMatcher", "matcher2", "org.hamcrest.Matcher"))); + assertTrue(result.contains(new FactoryMethod("test.AnotherMatcher", "matcher3", "org.hamcrest.MyMatcher"))); } private static InputSource createXml(String xml) { return new InputSource(new StringReader(xml)); } - // Sample Matchers - - @SuppressWarnings("rawtypes") - public static class SomeMatcher { - @Factory public static Matcher matcher1() { return null; } - @Factory public static Matcher matcher2() { return null; } - } - - @SuppressWarnings("rawtypes") - public static class AnotherMatcher implements Matcher { - @Override public void describeTo(Description description) { } - @Override public boolean matches(Object item) { return false; } - @Override public void describeMismatch(Object item, Description mismatchDescription) { } - @Override @Deprecated public void _dont_implement_Matcher___instead_extend_BaseMatcher_() { } - @Factory public static AnotherMatcher matcher3() { return null; } - } - /** * Simple 'record and check' style mock. Not using a mocking library to avoid * cyclic dependency between mocking library and hamcrest. diff --git a/lib/generator/qdox-1.12.jar b/lib/generator/qdox-1.12.jar deleted file mode 100644 index 3d850e5f..00000000 Binary files a/lib/generator/qdox-1.12.jar and /dev/null differ diff --git a/lib/generator/qdox-2.0-M2.jar b/lib/generator/qdox-2.0-M2.jar new file mode 100644 index 00000000..75570e2b Binary files /dev/null and b/lib/generator/qdox-2.0-M2.jar differ