original_panno was mostly there as a tribute to pfn - but he's already thanked on the webpage, and we no longer need this for reference material. Deleting it.

This commit is contained in:
Reinier Zwitserloot committed 2009-10-11 23:58:58 +02:00
1 parent 310862d94e
commit aa61766dab
3 files changed
-684

No files matched your search

@@ -1,71 +0,0 @@
/*
* Copyright 2007 Perry Nguyen <pfnguyen@hanhuy.com> Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.hanhuy.panno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A simple javabean property annotation. Used to generate get/set pairs for
* field members automatically. Valid options are: 'name', 'readOnly',
* 'writeOnly', 'useGet', 'preCallMethod', and 'preCallThrows'.
* <p>
*'name' may be used to set an alternative name for the property instead of
* defaulting to the field's name.
* <p>
*'readOnly' and 'writeOnly' may be set to true to create only a getter or a
* setter, respectively.
* <p>
*'useGet' is used to generate a getX instead of isX method if the property is
* a boolean.
* <p>
*'preCallMethod' is the name of the method to be invoked prior to setting the
* actual property value. It may be used for validation, or firing off
* PropertyChangeEvents. This method will be called as
* <code>preCallMethod(this, String propertyName, oldValue, newValue)</code>;
* since this occurs at compile time, there is no interface, so the types are
* entirely up to you to choose and make work. The method may also be any
* method, so long as it's accessible to the bean. Thus,
* <code>preCallMethod=anotherObject.validateProperty</code> would be ok, so
* long as <code>anotherObject</code> is a field within the bean. The object
* navigation leading up to the preCallMethod <b>cannot</b> contain any method
* invocations; e.g.
* <code>preCallMethod=someBean.someMethod().myPreCallMethod</code> would be
* illegal and cause an error.
* <p>
*'preCallThrows' specifies what exceptions can possibly be thrown by the
* preCallMethod. Any non-RuntimeExceptions <b>must</b> be specified here, or
* else it will result in a compile-time error. This information cannot be
* determined reflectively, because at compile-time, there is no reflection
* (reflection is for runtime use). All exception names must be imported and
* they may not be referred by their qualified name, e.g. "Exception", not
* "java.lang.Exception"
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface Property {
String name() default "";
boolean readOnly() default false;
boolean writeOnly() default false;
boolean useGet() default false;
String[] preCallThrows() default { /* nothing */};
String preCallMethod() default "";
}
@@ -1,320 +0,0 @@
/*
* Copyright 2007 Perry Nguyen <pfnguyen@hanhuy.com> Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.hanhuy.panno.processing;
import java.util.Map;
import java.util.Stack;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Modifier;
import javax.tools.Diagnostic;
import com.hanhuy.panno.Property;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
/**
* Old attempt to get the annotation working, have a better approach now.
*/
@Deprecated
class ASTTreeToSource extends TreeScanner<Void, StringBuilder> {
public final static String PROPERTY_ANNOTATION_FULL = "com.hanhuy.panno.Property";
public final static String PROPERTY_ANNOTATION = "Property";
private final Stack<String> currentClassNameStack = new Stack<String>();
private final Messager messager;
private final Map<String, Property> fieldMap;
private String currentClassName;
private String currentField;
private String currentType;
ASTTreeToSource(Map<String, Property> fieldMap, Messager messager) {
this.fieldMap = fieldMap;
this.messager = messager;
}
/**
* Handle initializer blocks, both static and non.
*/
@Override
public Void visitBlock(BlockTree node, StringBuilder p) {
p.append("\n");
if ( node.isStatic() ) p.append("static ");
p.append("{\n");
for ( StatementTree st : node.getStatements() ) {
p.append(st);
p.append(";");
}
p.append("\n}\n");
return null;
}
/**
* Detect our annotation and add accessors if necessary.
*/
@Override
public Void visitAnnotation(AnnotationTree node, StringBuilder p) {
String anno = ((IdentifierTree)node.getAnnotationType()).getName().toString();
if ( anno != null && PROPERTY_ANNOTATION.equals(anno.trim()) ) {
Property pAnno = fieldMap.get(currentClassName + "." + currentField);
String propName = currentField;
if ( !"".equals(pAnno.name()) ) propName = pAnno.name();
propName = PropertyProcessor.ucfirst(propName);
if ( pAnno.writeOnly() && pAnno.readOnly() ) {
messager.printMessage(Diagnostic.Kind.ERROR, "@Property's writeOnly and readOnly may not both be true");
}
if ( !"boolean".equals(currentType) && pAnno.useGet() ) {
messager.printMessage(Diagnostic.Kind.WARNING, "@Property's useGet is only for booleans");
}
if ( !pAnno.writeOnly() ) {
// generate a getter
p.append("\n");
p.append("public ");
p.append(currentType);
if ( !"boolean".equals(currentType) || pAnno.useGet() ) p.append(" get");
else p.append(" is");
p.append(propName);
p.append("() { return ");
p.append(currentField);
p.append("; }");
}
if ( !pAnno.readOnly() ) {
// generate a setter
p.append("\n");
p.append("public void set");
p.append(propName);
p.append("(");
p.append(currentType);
p.append(" ");
p.append(currentField);
p.append(") { this.");
p.append(currentField);
p.append(" = ");
p.append(currentField);
p.append("; }");
}
}
return null;
}
/**
* Gather file information. Then continue to visit classes.
*/
@Override
public Void visitCompilationUnit(CompilationUnitTree node, StringBuilder p) {
new Exception("Visit CUT: " + node.getSourceFile()).printStackTrace();
p.append("// Generated from: ");
p.append(node.getSourceFile());
p.append("\n\npackage ");
p.append(node.getPackageName());
p.append(";\n\n");
for ( ImportTree it : node.getImports() ) {
if ( PROPERTY_ANNOTATION_FULL.equals(it.getQualifiedIdentifier().toString().trim()) ) {
continue;
}
p.append("import ");
if ( it.isStatic() ) p.append("static ");
p.append(it.getQualifiedIdentifier());
p.append(";\n");
}
super.visitCompilationUnit(node, p);
return null;
}
private void pushClass(String className) {
currentClassName = className;
currentClassNameStack.push(currentClassName);
}
private void popClass() {
currentClassNameStack.pop();
if ( currentClassNameStack.size() > 0 ) currentClassName = currentClassNameStack.peek();
}
/**
* Reproduce class declarations, and go on to handle methods, blocks and
* variables.
*/
@Override
public Void visitClass(ClassTree node, StringBuilder p) {
pushClass(node.getSimpleName().toString());
// Check to see if this is an enum
// I have no idea what will happen if we encounter an AST
// implementation that is not Sun's (or if this has changed in java7)
if ( node instanceof com.sun.tools.javac.tree.JCTree.JCClassDecl ) {
com.sun.tools.javac.tree.JCTree.JCClassDecl jcd = (com.sun.tools.javac.tree.JCTree.JCClassDecl)node;
if ( (jcd.mods.flags & 16384L) != 0L ) {
p.append(node.getModifiers());
p.append(" enum ");
p.append(currentClassName);
appendEnumBody(jcd.defs, p);
popClass();
return null;
}
}
p.append("\n");
p.append(node.getModifiers());
p.append(" class ");
p.append(currentClassName);
if ( node.getTypeParameters().size() > 0 ) {
p.append("<");
p.append(node.getTypeParameters());
p.append(">");
}
if ( node.getExtendsClause() != null ) {
p.append("\nextends ");
p.append(node.getExtendsClause());
}
if ( node.getImplementsClause().size() > 0 ) {
p.append("\nimplements ");
p.append(node.getImplementsClause());
}
p.append("\n{");
super.visitClass(node, p);
p.append("\n}\n");
popClass();
return null;
}
/**
* Enums appear to be a special case. We must reconstitute its source in a
* different manner from the rest. Need to remove any 'super()' statements
* and print the lines appropriately.
*/
public void appendEnumBody(com.sun.tools.javac.util.List<com.sun.tools.javac.tree.JCTree> list, StringBuilder p) {
p.append("{\n");
boolean flag = true;
for ( com.sun.tools.javac.util.List<com.sun.tools.javac.tree.JCTree> list1 = list; list1.nonEmpty(); list1 = list1.tail ) {
if ( !isEnumerator(list1.head) ) continue;
if ( !flag ) p.append(",\n");
p.append(list1.head);
flag = false;
}
p.append(";\n");
for ( com.sun.tools.javac.util.List<com.sun.tools.javac.tree.JCTree> list2 = list; list2.nonEmpty(); list2 = list2.tail ) {
if ( !isEnumerator(list2.head) ) {
if ( list2.head instanceof MethodTree ) visitMethod((MethodTree)list2.head, p);
else p.append(list2.head);
p.append("\n");
}
}
p.append("}");
}
private boolean isEnumerator(com.sun.tools.javac.tree.JCTree jctree) {
return jctree.tag == 5 && (((com.sun.tools.javac.tree.JCTree.JCVariableDecl)jctree).mods.flags & 16384L) != 0L;
}
/**
* Reconstitute methods. We could just do node.toString() here, but
* constructors need to be renamed from &lt;init&gt; to the class name, so
* handle all methods.
*/
@Override
public Void visitMethod(MethodTree node, StringBuilder p) {
p.append("\n");
p.append(node.getModifiers());
p.append(" ");
if ( node.getReturnType() != null ) p.append(node.getReturnType());
if ( node.getTypeParameters().size() > 0 ) {
p.append(" <");
p.append(node.getTypeParameters());
p.append(">");
}
p.append(" ");
if ( node.getName() != null && "<init>".equals(node.getName().toString().trim()) ) p.append(currentClassName);
else p.append(node.getName());
p.append("(");
p.append(node.getParameters());
p.append(")");
if ( node.getThrows().size() > 0 ) {
p.append("\nthrows ");
p.append(node.getThrows());
}
p.append(" {\n");
// this needs to be done for enums, otherwise we'll get a compile error
// if we didn't need this case, we'd just do p.append(node.getBody())
for ( StatementTree st : node.getBody().getStatements() ) {
if ( "super()".equals(st.toString().trim()) ) continue;
p.append(st);
if ( p.charAt(p.length() - 1) != ';' ) p.append(";\n");
}
p.append("\n}\n");
return null;
}
/**
* Reconstitute class fields. While doing so, search for our annotation to
* perform processing.
*/
@Override
public Void visitVariable(VariableTree node, StringBuilder p) {
currentField = node.getName().toString();
currentType = node.getType().toString();
p.append("\n");
for ( AnnotationTree a : node.getModifiers().getAnnotations() ) {
String anno = ((IdentifierTree)a.getAnnotationType()).getName().toString();
if ( !PROPERTY_ANNOTATION.equals(anno) ) {
p.append(a);
p.append("\n");
}
}
for ( Modifier m : node.getModifiers().getFlags() ) {
p.append(m.toString());
p.append(" ");
}
p.append(" ");
p.append(currentType);
p.append(" ");
p.append(currentField);
if ( node.getInitializer() != null ) {
p.append(" = ");
p.append(node.getInitializer());
}
p.append(";");
super.visitVariable(node, p);
return null;
}
}
@@ -1,293 +0,0 @@
/*
* Copyright 2007 Perry Nguyen <pfnguyen@hanhuy.com> Licensed under the Apache
* License, Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.hanhuy.panno.processing;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.tools.Diagnostic;
import com.hanhuy.panno.Property;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.Trees;
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes( { "com.hanhuy.panno.Property" })
public class PropertyProcessor extends AbstractProcessor {
private Messager messager;
/**
* Annotation processing entry point. Currently, we only support javac 6
* from Sun's JDK. Other IDE support will have to follow.
*/
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
com.sun.tools.javac.util.Context ctx = null;
if ( processingEnv instanceof com.sun.tools.javac.processing.JavacProcessingEnvironment ) {
ctx = ((com.sun.tools.javac.processing.JavacProcessingEnvironment)processingEnv).getContext();
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@Property processing is not supported on this compiler.");
}
Trees trees = Trees.instance(processingEnv);
messager = processingEnv.getMessager();
Set<? extends Element> annoElements = env.getElementsAnnotatedWith(Property.class);
for ( Element e : annoElements ) {
processAnnotation(trees, ctx, e, e.getAnnotation(Property.class));
}
return true;
}
private void processAnnotation(Trees trees, com.sun.tools.javac.util.Context ctx, Element e, Property p) {
TypeElement type = (TypeElement)findEnclosingType(e);
if ( type == null ) {
messager.printMessage(Diagnostic.Kind.ERROR, "@Property annotated element is not a member field.", e);
}
ClassTree cls = trees.getTree(type);
com.sun.tools.javac.tree.JCTree.JCClassDecl cd = (com.sun.tools.javac.tree.JCTree.JCClassDecl)cls;
Object t = trees.getTree(e);
if ( !(t instanceof VariableTree) ) {
messager.printMessage(Diagnostic.Kind.ERROR, "@Property can only be annotated on a member field", e);
return;
}
com.sun.tools.javac.util.Name.Table tab = com.sun.tools.javac.util.Name.Table.instance(ctx);
com.sun.tools.javac.tree.TreeMaker maker = com.sun.tools.javac.tree.TreeMaker.instance(ctx);
MethodTree m;
String[] pcts = p.preCallThrows();
String pcm = p.preCallMethod();
if ( p.readOnly() && ((pcm != null && !"".equals(pcm.trim())) || (pcts != null && pcts.length > 0)) ) {
messager.printMessage(Diagnostic.Kind.ERROR, "preCallMethod and/or preCallThrows can"
+ "not be used with readOnly", e, getAnnotationMirror(e));
}
if ( p.writeOnly() && p.useGet() ) {
messager.printMessage(Diagnostic.Kind.ERROR, "useGet is only for use with readable boolean properties", e,
getAnnotationMirror(e));
}
if ( !p.readOnly() ) {
m = createSetter(e, p, maker, tab, trees);
cd.defs = cd.defs.append((com.sun.tools.javac.tree.JCTree)m);
}
if ( !p.writeOnly() ) {
m = createGetter(e, p, maker, tab);
cd.defs = cd.defs.append((com.sun.tools.javac.tree.JCTree)m);
}
}
private String getPropertyName(Element e, Property p) {
String pname = p.name();
if ( pname == null || "".equals(p.name().trim()) ) pname = e.getSimpleName().toString();
return pname;
}
@SuppressWarnings("unchecked")
private MethodTree createGetter(Element e, Property p, com.sun.tools.javac.tree.TreeMaker maker,
com.sun.tools.javac.util.Name.Table tab) {
String pname = getPropertyName(e, p);
com.sun.tools.javac.util.List nil = com.sun.tools.javac.util.List.nil();
com.sun.tools.javac.util.List statements = com.sun.tools.javac.util.List.of(maker.Return(maker
.Ident((com.sun.tools.javac.code.Symbol.VarSymbol)e)));
String getterPrefix = "get";
// check if boolean, use "is", otherwise "get" if useGet
if ( e.asType().getKind() == TypeKind.BOOLEAN ) {
if ( !p.useGet() ) getterPrefix = "is";
} else {
if ( p.useGet() ) messager.printMessage(Diagnostic.Kind.WARNING,
"@Property(useGet=true) is for boolean properties", e);
}
com.sun.tools.javac.util.Name n = com.sun.tools.javac.util.Name.fromString(tab, getterPrefix + ucfirst(pname));
MethodTree m = maker.MethodDef(maker.Modifiers(1L, nil), n, maker.Type((com.sun.tools.javac.code.Type)e
.asType()), nil, nil, nil, maker.Block(0, statements), null);
return m;
}
@SuppressWarnings("unchecked")
private MethodTree createSetter(Element e, Property p, com.sun.tools.javac.tree.TreeMaker maker,
com.sun.tools.javac.util.Name.Table tab, Trees trees) {
// String pname = getPropertyName(e, p);
int pos = getAnnotationPosition(e, trees);
com.sun.tools.javac.util.List nil = com.sun.tools.javac.util.List.nil();
com.sun.tools.javac.util.Name vn = com.sun.tools.javac.util.Name.fromString(tab, "__panno_Generated_"
+ e.getSimpleName().toString());
com.sun.tools.javac.tree.JCTree.JCVariableDecl param = maker.Param(vn, (com.sun.tools.javac.code.Type)e
.asType(), null);
com.sun.tools.javac.util.List<com.sun.tools.javac.tree.JCTree.JCStatement> statements = com.sun.tools.javac.util.List
.of(maker.Assignment((com.sun.tools.javac.code.Symbol.VarSymbol)e, maker.Ident(param)));
// com.sun.tools.javac.util.Name n = com.sun.tools.javac.util.Name.fromString(tab, "set" + ucfirst(pname));
com.sun.tools.javac.util.List throwList = nil;
MethodInvocationTree mit = createPreCall(e, p, param, maker, tab, pos);
if ( mit != null ) {
statements = statements.prepend(maker.Exec((com.sun.tools.javac.tree.JCTree.JCMethodInvocation)mit));
if ( p.preCallThrows() != null && p.preCallThrows().length > 0 ) {
for ( String thrown : p.preCallThrows() ) {
com.sun.tools.javac.util.Name tn = com.sun.tools.javac.util.Name.fromString(tab, thrown);
com.sun.tools.javac.code.Symbol.ClassSymbol clssym = new com.sun.tools.javac.code.Symbol.ClassSymbol(
0, tn, null);
throwList = throwList.append(maker.Type(clssym.type).setPos(pos));
// setpos to munge line number
}
}
} else if ( p.preCallThrows() != null && p.preCallThrows().length > 0 ) {
messager.printMessage(Diagnostic.Kind.ERROR, "preCallThrows is only to be used with preCallMethod", e,
getAnnotationMirror(e));
}
com.sun.tools.javac.tree.JCTree.JCMethodDecl m = null/*maker.MethodDef(maker.Modifiers(1L, nil), n, maker
.Type(com.sun.tools.javac.code.Symtab.voidType), nil, com.sun.tools.javac.util.List.of(param),
throwList, maker.Block(0, statements), null)*/;
// munge line numbers
// m.mods.pos = pos;
// m.pos = pos;
return m;
}
private MethodInvocationTree createPreCall(Element e, Property p,
com.sun.tools.javac.tree.JCTree.JCVariableDecl newParam, com.sun.tools.javac.tree.TreeMaker maker,
com.sun.tools.javac.util.Name.Table tab, int pos) {
String pcm = p.preCallMethod();
String pname = getPropertyName(e, p);
pname = lcfirst(pname);
com.sun.tools.javac.tree.JCTree.JCMethodInvocation m = null;
if ( pcm == null || "".equals(pcm) ) return m;
String[] parts = pcm.split("\\.");
com.sun.tools.javac.tree.JCTree.JCExpression expr = null;
Element enclosing = findEnclosingType(e);
String lastPart = null;
if ( parts.length > 1 ) {
// A simple object navigation path is named.
// I'm too lazy to support/figure out method invocations here,
// do plain member selection only
for ( int i = 0, j = parts.length; i < j; i++ ) {
if ( parts[i].endsWith(")") ) {
messager.printMessage(Diagnostic.Kind.ERROR, "Cannot call a method in graph to preCallMethod", e,
getAnnotationMirror(e));
}
com.sun.tools.javac.util.Name partName = com.sun.tools.javac.util.Name.fromString(tab, parts[i]);
if ( lastPart != null ) {
expr = maker.Select(expr, partName);
} else {
com.sun.tools.javac.tree.JCTree.JCVariableDecl param = maker.Param(partName, null, null);
expr = maker.Ident(param);
}
// this is how we get the correct line number
expr.pos = pos;
lastPart = parts[i];
}
} else {
// A method is named
// com.sun.tools.javac.util.Name name = com.sun.tools.javac.util.Name.fromString(tab, parts[0]);
com.sun.tools.javac.code.Symbol.MethodSymbol msym = null/*new com.sun.tools.javac.code.Symbol.MethodSymbol(0,
name, com.sun.tools.javac.code.Symtab.voidType, (com.sun.tools.javac.code.Symbol)enclosing)*/;
expr = maker.Ident(msym);
// this is how we get the correct line number when there's an error.
expr.pos = pos;
}
m = maker.Apply(null, expr, com.sun.tools.javac.util.List.of(maker
.This((com.sun.tools.javac.code.Type)enclosing.asType()), maker.Literal(pname), maker
.Ident((com.sun.tools.javac.code.Symbol)e), maker.Ident(newParam)));
// this is required, I guess...
m.setType(new com.sun.tools.javac.code.Type.MethodType(null, null, null, null));
return m;
}
private int getAnnotationPosition(Element e, Trees trees) {
return (int)trees.getSourcePositions().getStartPosition(trees.getPath(e).getCompilationUnit(),
trees.getTree(e, getAnnotationMirror(e)));
}
/**
* Returns the enclosing type if found. (Should return a TypeElement object)
*/
static Element findEnclosingType(Element e) {
return e == null || e instanceof TypeElement ? e : e.getEnclosingElement();
}
/**
* Uppercase the first character of the string. Named after the ucfirst
* function in Perl.
*/
static String ucfirst(String name) {
String first = name.substring(0, 1).toUpperCase();
return first + name.substring(1);
}
static String lcfirst(String name) {
String first = name.substring(0, 1).toLowerCase();
return first + name.substring(1);
}
/**
* Return the Property AnnotationMirror for this element.
*/
static AnnotationMirror getAnnotationMirror(Element e) {
List<? extends AnnotationMirror> annos = e.getAnnotationMirrors();
for ( AnnotationMirror am : annos ) {
DeclaredType type = am.getAnnotationType();
String name = type.asElement().getSimpleName().toString();
if ( name != null && "Property".equals(name.trim()) ) return am;
}
throw new IllegalStateException("Element must have a @Property annotation");
}
}