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 extends Annotation>)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