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.JavaType; import com.thoughtworks.qdox.model.JavaTypeVariable; /** * Wraps an existing sequence of FactoryMethods, and attempts to pull in * parameter names and JavaDoc (which aren't available using reflection) using * QDox. * * @see <a href="http://qdox.codehaus.org/">QDox</a> * @author Joe Walnes */ public class QDoxFactoryReader implements Iterable<FactoryMethod> { private final JavaClass classSource; private JavaClass factoryAnnotation; private static final Pattern GENERIC_REGEX = Pattern.compile("^<(.*)>$"); public QDoxFactoryReader(QDox qdox, String className) { this.classSource = qdox.getClassByName(className); this.factoryAnnotation = qdox.getClassByName("org.hamcrest.Factory"); } @Override public Iterator<FactoryMethod> iterator() { List<FactoryMethod> methods = new ArrayList<FactoryMethod>(); for (JavaMethod jm : classSource.getMethods()) { if (!isFactoryMethod(jm)) { continue; } 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); } 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); } Collections.sort(methods, new Comparator<FactoryMethod>() { @Override public int compare(FactoryMethod o1, FactoryMethod o2) { return o1.getName().compareTo(o2.getName()); } }); return methods.iterator(); } /** * Determine whether a particular method is classified as a matcher factory method. * <p/> * <p>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. * <p/> * <p>To use another set of rules, override this method. */ 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; } } return false; } private static String typeToString(JavaType type) { return type.getGenericFullyQualifiedName().replace('$', '.'); } /** * Reconstructs the JavaDoc as a string for a particular method. */ private static String createJavaDocComment(JavaMethod methodSource) { String comment = methodSource.getComment(); List<DocletTag> tags = methodSource.getTags(); if ((comment == null || comment.trim().length() == 0) && tags.isEmpty()) { return null; } StringBuilder result = new StringBuilder(); result.append(comment); result.append("\n\n"); for (DocletTag tag : tags) { result.append('@').append(tag.getName()) .append(' ').append(tag.getValue()) .append('\n'); } return result.toString(); } }