所以我选择创作一个基于 Eclipse JDT 的实用程序。
我花了一段时间来寻找所有依赖库来完成这项工作。对于其他感兴趣的人,这里是 Maven 依赖项:
<!-- Validation API and Impl -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<!-- Hibernate validator impl -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- Required to power classpath scanning for JSR-303 classes within JAR packages -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<!-- Required to employ all Eclipse JDT capabilities -->
<!-- This specific collection of artifact versions is known to work together -->
<!-- Take caution when upgrading versions! -->
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.8.1.v20120502-0834</version>
</dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.8.0.v20120430-1750</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.resources</artifactId>
<version>3.7.100.v20110510-0712</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.7.0.v_B61</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.runtime</artifactId>
<version>3.7.0.v20110110</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.equinox.common</artifactId>
<version>3.6.0.v20110523</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.text</artifactId>
<version>3.5.100.v20110505-0800</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.jobs</artifactId>
<version>3.5.100.v20110404</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.core.contenttype</artifactId>
<version>3.4.100.v20110423-0524</version>
</dependency>
<dependency>
<groupId>org.jibx.config.3rdparty.org.eclipse</groupId>
<artifactId>org.eclipse.equinox.preferences</artifactId>
<version>3.4.0.v20110502</version>
</dependency>
我编写了四个类,一个带有主线束,另一个是外观和实用程序。
线束:_
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.jdt.JDTFacade;
import org.spp.im.mui.commons.util.FileUtil;
import org.spp.im.mui.commons.util.PackageUtil;
import org.springframework.util.CollectionUtils;
/**
* A utility that scans all DTO classes that have
* <code>javax.validation.constrants.*</code> or
* <code>org.hibernate.validation.constraints.*</code> annotated fields, then
* enriches the annotation with a <code>message</code> attribute where its value
* will be <code>constraint-name.class-name.field-name</code>.
*
* @author cphillipson
* @param <T>
* any JSR-303 annotation type
*
*/
public class ConstraintMessageUtil<T extends Annotation> {
private static Logger log = LoggerFactory.getLogger(ConstraintMessageUtil.class);
private static final String JAVAX_PATH = "/javax/validation/constraints/*";
private static final String HIBERNATE_PATH = "/org/hibernate/validator/constraints/*";
private PackageUtil<T> util;
private JDTFacade<T> facade;
public ConstraintMessageUtil() {
util = new PackageUtil<T>();
facade = new JDTFacade<T>();
}
public void process(String sourcePath) throws Exception {
// step #1: build a set of JSR-303 constraint classes
final Set<Class<T>> annotationTypes = new HashSet<Class<T>>();
try {
final List<Class<T>> jxTypes = util.listMatchingClasses(JAVAX_PATH);
final List<Class<T>> hibTypes = util.listMatchingClasses(HIBERNATE_PATH);
annotationTypes.addAll(jxTypes);
annotationTypes.addAll(hibTypes);
// remove @Valid from the mix
annotationTypes.remove(Valid.class);
Assert.isTrue(!annotationTypes.contains(Valid.class));
} catch (final IOException ioe) {
}
// step #2: get all files recursively from source path
final Collection<File> allJavaSourceInDirectory = FileUtil.getAllJavaSourceInDirectory(new File(sourcePath),
true);
// step #3: filter files to just the ones that contain annotations
final List<File> annotatedSources = new ArrayList<File>();
if (!CollectionUtils.isEmpty(allJavaSourceInDirectory)) {
boolean containsJsr303Annotation;
String typeName;
for (final File f : allJavaSourceInDirectory) {
for (final Class<T> annotationType : annotationTypes) {
typeName = annotationType.getName();
containsJsr303Annotation = FileUtil.isContentInFile(f, typeName);
if (containsJsr303Annotation) {
annotatedSources.add(f);
break; // at least one annotation found, move along
}
}
}
}
// step #4: for each annotated source file parse and rewrite with
// enriched message for each JSR-303 annotation
enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(annotatedSources, annotationTypes);
}
// note: probably could have implemented an ASTVisitor, but...
protected void enrichJavaSourceFilesWithMessageAttributesForConstraintTypeAnnotatedFields(
List<File> annotatedSources, Set<Class<T>> constraintTypes) throws IOException, MalformedTreeException,
BadLocationException {
if (!CollectionUtils.isEmpty(annotatedSources)) {
// reusable local variables... a veritable cornucopia
Set<FieldDeclaration> fieldCandidates;
Document document;
String contents;
String constraintName;
String className;
String fieldName;
StringBuilder sb;
AbstractTypeDeclaration td;
IExtendedModifier[] modifiers;
CompilationUnit unit;
AST ast;
MemberValuePair mvp;
Expression exp;
NormalAnnotation na;
// iterate over all java source containing jsr-303 annotated fields
for (final File source : annotatedSources) {
unit = facade.generateCompilationUnitForFile(source);
ast = unit.getAST();
// get the set of fields which are annotated
fieldCandidates = facade.obtainAnnotatedFieldsFromClassInCompilationUnit(unit, constraintTypes);
log.info(source.getName() + " contains " + fieldCandidates.size()
+ " fields with constraint annotations.");
// iterate over each annotated field
for (final FieldDeclaration fd : fieldCandidates) {
modifiers = (IExtendedModifier[]) fd.modifiers().toArray(
new IExtendedModifier[fd.modifiers().size()]);
int i = 0;
// iterate over modifiers for the field
for (final IExtendedModifier modifier : modifiers) {
// interested in Eclipse JDT's DOM form of Annotation
if (modifier instanceof org.eclipse.jdt.core.dom.Annotation) {
// construct the key-value pair
sb = new StringBuilder();
constraintName = ((org.eclipse.jdt.core.dom.Annotation) modifier).getTypeName().toString();
// Ignore @Valid annotations
if (!constraintName.equals(Valid.class.getSimpleName())) {
td = (AbstractTypeDeclaration) fd.getParent();
className = td.getName().toString();
fieldName = fd.fragments().get(0).toString();
// field may have an assignment, so strip it
if (fieldName.contains("=")) {
final int end = fieldName.indexOf("=");
fieldName = fieldName.substring(0, end).trim();
}
sb.append("{");
sb.append(constraintName);
sb.append(".");
sb.append(className);
sb.append(".");
sb.append(fieldName);
sb.append("}");
// construct new properties, and instead of
// updating
// the existing annotation, replace it
mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName("message"));
exp = ast.newStringLiteral();
((StringLiteral) exp).setLiteralValue(sb.toString());
mvp.setValue(exp);
na = ast.newNormalAnnotation();
na.setTypeName(ast.newSimpleName(constraintName));
na.values().add(mvp);
// don't forget to add the original annotation's
// member-value pairs to the new annotation
if (modifier instanceof NormalAnnotation) {
final NormalAnnotation ona = (NormalAnnotation) modifier;
final List<?> values = ona.values();
for (int j = 0; j < values.size(); j++) {
final MemberValuePair omvp = (MemberValuePair) values.get(j);
mvp = ast.newMemberValuePair();
mvp.setName(ast.newSimpleName(omvp.getName().toString()));
// a value can be a String, Number or
// reference to a constant
switch (omvp.getValue().getNodeType()) {
case ASTNode.NUMBER_LITERAL:
mvp.setValue(ast.newNumberLiteral(omvp.getValue().toString()));
break;
case ASTNode.STRING_LITERAL:
exp = ast.newStringLiteral();
((StringLiteral) exp).setLiteralValue(omvp.getValue().toString());
mvp.setValue(exp);
break;
case ASTNode.QUALIFIED_NAME:
final QualifiedName oqn = (QualifiedName) omvp.getValue();
exp = ast.newQualifiedName(ast.newName(oqn.getQualifier().toString()),
ast.newSimpleName(oqn.getName().toString()));
mvp.setValue(exp);
break;
}
na.values().add(mvp);
}
}
fd.modifiers().remove(i);
fd.modifiers().add(i, na);
log.info("@" + constraintName + " on " + fieldName + " in " + className
+ " has been enriched with a 'message' attribute whose value is now '"
+ sb.toString() + "'.");
}
i++;
}
}
}
contents = FileUtil.toString(source);
document = new Document(contents);
facade.saveUpdatesToFile(unit, document, source);
}
}
}
public static void main(String args[]) {
final ConstraintMessageUtil util = new ConstraintMessageUtil();
try {
// e.g., on Windows,
// "D:\\workspaces\\alstom-grid\\SPP-MUI\\spp-im-mui-dto\\src\\main\\java\\org\\spp\\im\\mui\\dto"
util.process(args[0]);
} catch (final Exception e) {
e.printStackTrace();
}
}
}
实用程序:
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.AntPathMatcher;
/**
* Package utility. Provides handy methods for finding classes (of a particular
* type) within a package on the classpath.
*
* @author cphillipson
*
* @param <T>
* types of classes to be found in package
*/
class PackageUtil<T> {
public List<Class<T>> listMatchingClasses(String matchPattern) throws IOException {
final List<Class<T>> classes = new LinkedList<Class<T>>();
final PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
scanner.setPathMatcher(new AntPathMatcher());
final Resource[] resources = scanner.getResources("classpath:" + matchPattern);
for (final Resource resource : resources) {
final Class<T> clazz = getClassFromResource(resource);
classes.add(clazz);
}
return classes;
}
public Class<T> getClassFromResource(Resource resource) {
Class<T> result = null;
try {
String resourceUri = resource.getURI().toString();
resourceUri = resourceUri.substring(0, resourceUri.indexOf(".class")).replace("/", ".");
if (resourceUri.contains("!")) { // class was found in an archive
resourceUri = resourceUri.substring(resourceUri.indexOf("!") + 2);
}
// try printing the resourceUri before calling forName, to see if it
// is OK.
result = (Class<T>) Class.forName(resourceUri);
} catch (final Exception ex) {
ex.printStackTrace();
}
return result;
}
}
/**
* A collection of special-purposed methods for working with files and
* directories. Wraps Apache Commons I/O.
*
* @author cphillipson
*
*/
public class FileUtil {
public static Collection<File> getAllJavaSourceInDirectory(File directory, boolean recursive) {
// scans directory (and sub-directories if recursive flag is true) for
// .java files, returns a collection of files
return FileUtils.listFiles(directory, new String[] { "java" }, recursive);
}
public static boolean isContentInFile(File file, String fragment) throws IOException {
boolean result = false;
final String contents = toString(file);
if (contents.contains(fragment)) { // does file contain fragment?
result = true;
}
return result;
}
public static String toString(File file) throws IOException {
final String result = FileUtils.readFileToString(file, "utf8");
return result;
}
public static void toFile(File file, String content) throws IOException {
FileUtils.writeStringToFile(file, content, "utf8");
}
}
门面:_
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spp.im.mui.commons.util.FileUtil;
/**
* Abstract syntax tree facade. Backed by Eclipse JDT, this facade provides a
* number of conveniences, like the ability to:
* <ul>
* <li>generate an {@link CompilationUnit} from a source {@File}</li>
* <li>save updates in a {@link Document} managed by {@link CompilationUnit} to
* a {@link File}</li>
* </ul>
* and much more. Credit goes to <a href=
* "http://svn.apache.org/repos/asf/openejb/branches/eclipse-plugins-1.0.0.alpha/plugins/org.apache.openejb.devtools.core/src/main/java/org/apache/openejb/devtools/core/JDTFacade.java"
* >Apache OpenEJB DevTools JDTFacade source</a> for providing much of the
* inspiration for this implementation.
*
* @author cphillipson
* @param <T>
* any annotation type
*
*/
public class JDTFacade<T extends java.lang.annotation.Annotation> {
private static Logger log = LoggerFactory.getLogger(JDTFacade.class);
public CompilationUnit generateCompilationUnitForFile(File file) throws IOException {
final String source = FileUtil.toString(file);
final Document document = new Document(source);
final ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setSource(document.get().toCharArray());
final CompilationUnit unit = (CompilationUnit) parser.createAST(null /* no ProgressMonitor */);
unit.recordModifications();
return unit;
}
public void saveUpdatesToFile(CompilationUnit unit, Document document, File file) throws MalformedTreeException,
IOException, BadLocationException {
final TextEdit edits = unit.rewrite(document, null /* no options */);
edits.apply(document);
boolean writeable = true; // should always be able to write to file...
if (!file.canWrite()) { // .. but just in case we cannot...
writeable = file.setWritable(true);
}
if (writeable) {
FileUtil.toFile(file, document.get());
log.info("Successfully wrote updates to " + file.getName());
} else {
log.warn("Unable to write to " + file.getName());
}
}
public Set<FieldDeclaration> obtainAnnotatedFieldsFromClassInCompilationUnit(CompilationUnit unit,
Set<Class<T>> annotationTypes) {
final Set<FieldDeclaration> fields = new HashSet<FieldDeclaration>();
final List<AbstractTypeDeclaration> types = unit.types();
IExtendedModifier[] modifiers;
for (final AbstractTypeDeclaration type : types) {
if (type.getNodeType() == ASTNode.TYPE_DECLARATION) {
// Class def found
final List<BodyDeclaration> bodies = type.bodyDeclarations();
for (final BodyDeclaration body : bodies) {
if (body.getNodeType() == ASTNode.FIELD_DECLARATION) {
final FieldDeclaration field = (FieldDeclaration) body;
modifiers = (IExtendedModifier[]) field.modifiers().toArray(new IExtendedModifier[0]);
for (final IExtendedModifier modifier : modifiers) {
if (!(modifier instanceof Annotation)) {
continue;
}
final Annotation annotationModifer = (Annotation) modifier;
for (final Class<T> clazz : annotationTypes) {
if (annotationModifer.getTypeName().toString().equals(clazz.getCanonicalName())
|| annotationModifer.getTypeName().toString().equals(clazz.getSimpleName())) {
fields.add(field);
break;
}
}
}
}
}
}
}
return fields;
}
}