mirror of
https://github.com/tiennm99/lombok.git
synced 2026-10-15 14:16:39 +00:00
Initial commit. As a proof of concept, it already works in javac and eclipse!
This commit is contained in:
commit
1677c4a52a
29 files changed
+1466
No files matched your search
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="lib" path="lib/asm-3.1.jar" sourcepath="contrib/asm-3.1-src.zip">
|
||||
<attributes>
|
||||
<attribute name="javadoc_location" value="jar:platform:/resource/lombok/contrib/asm-3.1-javadoc.zip!/"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="lib" path="deps/org.eclipse.jdt.apt.core_3.3.200.v20090528-1135.jar"/>
|
||||
<classpathentry kind="lib" path="deps/org.eclipse.jdt.apt.pluggable.core_1.0.200.v20090526-2130.jar"/>
|
||||
<classpathentry kind="lib" path="deps/org.eclipse.jdt.compiler.apt_1.0.200.v20090528-1135.jar"/>
|
||||
<classpathentry kind="lib" path="deps/org.eclipse.jdt.core_3.5.0.v_963.jar"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,4 @@
|
||||
bin
|
||||
build
|
||||
dist
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>lombok</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -0,0 +1,12 @@
|
||||
#Thu Jun 04 03:58:11 CEST 2009
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
@@ -0,0 +1,49 @@
|
||||
<project name="lombok" default="dist">
|
||||
<property name="build.compiler" value="javac1.6" />
|
||||
<path id="deps.path">
|
||||
<fileset dir="deps">
|
||||
<include name="**/*.jar" />
|
||||
</fileset>
|
||||
</path>
|
||||
<path id="libs.path">
|
||||
<fileset dir="lib">
|
||||
<include name="**/*.jar" />
|
||||
</fileset>
|
||||
</path>
|
||||
|
||||
<target name="clean">
|
||||
<delete dir="build" quiet="true" />
|
||||
<delete dir="dist" quiet="true" />
|
||||
</target>
|
||||
|
||||
<target name="compile">
|
||||
<mkdir dir="build" />
|
||||
<javac srcdir="src" debug="on" destdir="build">
|
||||
<classpath refid="deps.path" />
|
||||
<classpath refid="libs.path" />
|
||||
</javac>
|
||||
<mkdir dir="build/META-INF" />
|
||||
<mkdir dir="build/META-INF/services" />
|
||||
<echo file="build/META-INF/services/javax.annotation.processing.Processor">lombok.apt.Processor</echo>
|
||||
</target>
|
||||
|
||||
<target name="unpackLibs">
|
||||
<unjar dest="build">
|
||||
<path refid="libs.path" />
|
||||
</unjar>
|
||||
</target>
|
||||
|
||||
<target name="dist" depends="clean, compile, unpackLibs">
|
||||
<mkdir dir="dist" />
|
||||
<tstamp>
|
||||
<format property="distTime" pattern="yyyyMMdd'T'hhmmss" locale="en,US" />
|
||||
</tstamp>
|
||||
<jar basedir="build" destfile="dist/lombok-${distTime}.jar">
|
||||
<manifest>
|
||||
<attribute name="Premain-Class" value="lombok.agent.eclipse.EclipseParserPatcher" />
|
||||
<attribute name="Can-Redefine-Classes" value="true" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<copy file="dist/lombok-${distTime}.jar" tofile="dist/lombok.jar" />
|
||||
</target>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,45 @@
|
||||
Planned lombok features
|
||||
=======================
|
||||
|
||||
## @Getter
|
||||
|
||||
Put on any field; like so:
|
||||
|
||||
private @Getter AnyType foo;
|
||||
|
||||
This will generate the following method:
|
||||
|
||||
public AnyType getFoo() {
|
||||
return foo;
|
||||
}
|
||||
|
||||
Optionally you can generate a different access level by specifying the `AccessLevel` in the annotation, like so:
|
||||
|
||||
private @Getter(AccessLevel.PROTECTED) AnyType foo;
|
||||
|
||||
## @Setter
|
||||
|
||||
Like @Getter, but creates setters.
|
||||
|
||||
## @Data
|
||||
|
||||
Creates getters, setters (for non-final fields), toString, equals, and hashCode, as well as a constructor, or, if you wish,
|
||||
a 'static factory method'.
|
||||
|
||||
## @Property
|
||||
|
||||
## @AutoClose
|
||||
|
||||
## @Synchronized
|
||||
|
||||
## @Generator
|
||||
|
||||
## @SneakyThrows
|
||||
|
||||
# Maybes:
|
||||
|
||||
## @RunInEDT
|
||||
|
||||
## @SaneEquals
|
||||
|
||||
## List Comprehensions
|
||||
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 "";
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* 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 <init> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package java.lombok;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Allows you to load a class off of any place that is injected into a class loader (which doesn't know how to load the class you're injecting).
|
||||
*
|
||||
* Example: Injecting lombok's Eclipse Parser patching code into eclipse's OSGi BundleLoader.
|
||||
*
|
||||
* @author rzwitserloot
|
||||
*/
|
||||
public class ClassLoaderWorkaround {
|
||||
private static boolean initialized;
|
||||
private static Method m;
|
||||
|
||||
public static void transformCompilationUnitDeclaration(Object cud) throws Exception {
|
||||
if ( !initialized ) initialize(cud);
|
||||
if ( m == null ) throw new ClassNotFoundException("lombok.agent.eclipse.TransformCompilationUnitDeclaration");
|
||||
m.invoke(null, cud);
|
||||
}
|
||||
|
||||
private static void initialize(Object cud) {
|
||||
final ClassLoader parent = cud.getClass().getClassLoader();
|
||||
ClassLoader loader = new ClassLoader() {
|
||||
@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
if ( name.startsWith("lombok.") ) {
|
||||
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(name.replace(".", "/") + ".class");
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
byte[] b = new byte[65536];
|
||||
try {
|
||||
while ( true ) {
|
||||
int r = in.read(b);
|
||||
if ( r == -1 ) break;
|
||||
if ( r > 0 ) out.write(b, 0, r);
|
||||
}
|
||||
|
||||
in.close();
|
||||
byte[] data = out.toByteArray();
|
||||
Class<?> result = defineClass(name, data, 0, data.length);
|
||||
if ( resolve ) resolveClass(result);
|
||||
return result;
|
||||
} catch ( IOException e ) {
|
||||
throw new ClassNotFoundException();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Class<?> result = ClassLoader.getSystemClassLoader().loadClass(name);
|
||||
if ( resolve ) resolveClass(result);
|
||||
return result;
|
||||
} catch ( ClassNotFoundException e ) {
|
||||
Class<?> result = parent.loadClass(name);
|
||||
if ( resolve ) resolveClass(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
Class<?> c = loader.loadClass("lombok.agent.eclipse.TransformCompilationUnitDeclaration");
|
||||
for ( Method m : c.getMethods() ) {
|
||||
if ( m.getName().equals("transform") ) {
|
||||
ClassLoaderWorkaround.m = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch ( ClassNotFoundException ignore ) {}
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package lombok;
|
||||
|
||||
public enum AccessLevel {
|
||||
PUBLIC, MODULE, PROTECTED, PACKAGE, PRIVATE;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package lombok;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface Getter {
|
||||
AccessLevel value() default AccessLevel.PUBLIC;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package lombok;
|
||||
|
||||
public class Lombok {
|
||||
/**
|
||||
* Throws any throwable 'sneakily' - you don't need to catch it, nor declare that you throw it onwards.
|
||||
* The exception is still thrown - javac will just stop whining about it.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>public void run() {
|
||||
* throw sneakyThrow(new IOException("You don't need to catch me!"));
|
||||
* }</pre>
|
||||
*
|
||||
* NB: The exception is not wrapped, ignored, swallowed, or redefined. The JVM actually does not know or care
|
||||
* about the concept of a 'checked exception'. All this method does is hide the act of throwing a checked exception
|
||||
* from the java compiler.
|
||||
*
|
||||
* @param t The throwable to throw without requiring you to catch its type.
|
||||
* @return A dummy RuntimeException; this method never returns normally, it <em>always</em> throws an exception!
|
||||
*/
|
||||
public static RuntimeException sneakyThrow(Throwable t) {
|
||||
if ( t == null ) throw new NullPointerException("t");
|
||||
Lombok.<RuntimeException>sneakyThrow0(t);
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T extends Throwable> void sneakyThrow0(Throwable t) throws T {
|
||||
throw (T)t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package lombok.agent.eclipse;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
import java.security.ProtectionDomain;
|
||||
|
||||
public class EclipseParserPatcher {
|
||||
private static class Patcher implements ClassFileTransformer {
|
||||
@Override public byte[] transform(ClassLoader loader, String className,
|
||||
Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] classfileBuffer)
|
||||
throws IllegalClassFormatException {
|
||||
|
||||
if ( !ECLIPSE_PARSER_CLASS_NAME.equals(className) ) return null;
|
||||
EclipseParserTransformer transformer = new EclipseParserTransformer(classfileBuffer);
|
||||
return transformer.transform();
|
||||
}
|
||||
}
|
||||
|
||||
static final String ECLIPSE_PARSER_CLASS_NAME = "org/eclipse/jdt/internal/compiler/parser/Parser";
|
||||
|
||||
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
|
||||
registerPatcher(instrumentation, true);
|
||||
}
|
||||
|
||||
public static void premain(String agentArgs, Instrumentation instrumentation) {
|
||||
registerPatcher(instrumentation, false);
|
||||
}
|
||||
|
||||
private static void registerPatcher(Instrumentation instrumentation, boolean transformExisting) {
|
||||
instrumentation.addTransformer(new Patcher(), true);
|
||||
|
||||
if ( transformExisting ) for ( Class<?> c : instrumentation.getAllLoadedClasses() ) {
|
||||
if ( c.getName().equals(ECLIPSE_PARSER_CLASS_NAME) ) {
|
||||
try {
|
||||
instrumentation.retransformClasses(c);
|
||||
} catch ( UnmodifiableClassException ex ) {
|
||||
throw new UnsupportedOperationException(
|
||||
"The eclipse parser class is already loaded and cannot be modified. " +
|
||||
"You'll have to restart eclipse in order to use Lombok in eclipse.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package lombok.agent.eclipse;
|
||||
|
||||
import org.objectweb.asm.ClassAdapter;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodAdapter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
class EclipseParserTransformer {
|
||||
private static final String COMPILATION_UNIT_DECLARATION_SIG =
|
||||
"Lorg/eclipse/jdt/internal/compiler/ast/CompilationUnitDeclaration;";
|
||||
|
||||
private static final String TOPATCH_METHOD_NAME = "endParse";
|
||||
private static final String TOPATCH_METHOD_DESC = "(I)" + COMPILATION_UNIT_DECLARATION_SIG;
|
||||
|
||||
private final byte[] in;
|
||||
|
||||
EclipseParserTransformer(byte[] classfileBuffer) {
|
||||
in = classfileBuffer;
|
||||
}
|
||||
|
||||
byte[] transform() {
|
||||
ClassReader reader = new ClassReader(in);
|
||||
ClassWriter writer = new ClassWriter(reader, 0);
|
||||
ClassAdapter adapter = new ParserPatcherAdapter(writer);
|
||||
reader.accept(adapter, 0);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
|
||||
private static class ParserPatcherAdapter extends ClassAdapter {
|
||||
public ParserPatcherAdapter(ClassVisitor cv) {
|
||||
super(cv);
|
||||
}
|
||||
|
||||
@Override public MethodVisitor visitMethod(int access, String name, String desc,
|
||||
String signature, String[] exceptions) {
|
||||
MethodVisitor writerVisitor = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
if ( !TOPATCH_METHOD_NAME.equals(name) || !TOPATCH_METHOD_DESC.equals(desc) ) return writerVisitor;
|
||||
|
||||
return new PatcherMethodVisitor(writerVisitor);
|
||||
}
|
||||
}
|
||||
|
||||
static class PatcherMethodVisitor extends MethodAdapter {
|
||||
private static final String TARGET_STATIC_CLASS = "java/lombok/ClassLoaderWorkaround";
|
||||
private static final String TARGET_STATIC_METHOD_NAME = "transformCompilationUnitDeclaration";
|
||||
private static final String TARGET_STATIC_METHOD_DESC = "(Ljava/lang/Object;)V";
|
||||
|
||||
private boolean alreadyCalled = false;
|
||||
|
||||
PatcherMethodVisitor(MethodVisitor mv) {
|
||||
super(mv);
|
||||
}
|
||||
|
||||
@Override public void visitInsn(int opcode) {
|
||||
if ( opcode == Opcodes.ARETURN ) insertHookCall();
|
||||
|
||||
super.visitInsn(opcode);
|
||||
}
|
||||
|
||||
@Override public void visitMethodInsn(int opcode, String owner, String name,
|
||||
String desc) {
|
||||
if ( opcode == Opcodes.INVOKESTATIC &&
|
||||
TARGET_STATIC_CLASS.equals(owner) && TARGET_STATIC_METHOD_NAME.equals(name) ) alreadyCalled = true;
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
|
||||
/** When this method is called, the stack should hold the reference to the
|
||||
* just-parsed CompilationUnitDeclaration object that is about to be returned
|
||||
* to whomever wants it. We will put a call to a method of our choosing in,
|
||||
* which will transform the CUD. The stack is not modified (that is, that method
|
||||
* returns a CUD).
|
||||
*/
|
||||
private void insertHookCall() {
|
||||
if ( alreadyCalled ) return;
|
||||
super.visitInsn(Opcodes.DUP);
|
||||
super.visitMethodInsn(Opcodes.INVOKESTATIC, TARGET_STATIC_CLASS,
|
||||
TARGET_STATIC_METHOD_NAME, TARGET_STATIC_METHOD_DESC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package lombok.agent.eclipse;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.Annotation;
|
||||
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.Expression;
|
||||
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
|
||||
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
|
||||
import org.eclipse.jdt.internal.compiler.ast.Statement;
|
||||
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
|
||||
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
|
||||
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
|
||||
|
||||
public class TransformCompilationUnitDeclaration {
|
||||
/** This is a 'magic' method signature - it is this one that will be called. Don't rename anything! */
|
||||
|
||||
public static void transform(CompilationUnitDeclaration ast) {
|
||||
if ( ast.types != null ) for ( TypeDeclaration type : ast.types ) {
|
||||
if ( type.fields != null ) for ( FieldDeclaration field : type.fields ) {
|
||||
if ( field.annotations != null ) for ( Annotation annotation : field.annotations ) {
|
||||
if ( annotation.type.toString().equals("Getter") ) addGetter(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addGetter(TypeDeclaration type) {
|
||||
for ( AbstractMethodDeclaration method : type.methods ) {
|
||||
if ( method.selector != null && new String(method.selector).equals("getFoo") ) return;
|
||||
}
|
||||
|
||||
MethodDeclaration method = new MethodDeclaration(type.compilationResult);
|
||||
method.modifiers = Modifier.PUBLIC;
|
||||
method.returnType = TypeReference.baseTypeReference(TypeReference.T_int, 0);
|
||||
method.annotations = null;
|
||||
method.arguments = null;
|
||||
method.selector = "getFoo".toCharArray();
|
||||
method.binding = null;
|
||||
method.thrownExceptions = null;
|
||||
method.typeParameters = null;
|
||||
method.scope = new MethodScope(type.scope, method, false);
|
||||
Expression fieldExpression = new SingleNameReference("foo".toCharArray(), 10);
|
||||
Statement returnStatement = new ReturnStatement(fieldExpression, 1, 2);
|
||||
method.statements = new Statement[] { returnStatement };
|
||||
AbstractMethodDeclaration[] newArray = new AbstractMethodDeclaration[type.methods.length + 1];
|
||||
System.arraycopy(type.methods, 0, newArray, 0, type.methods.length);
|
||||
newArray[type.methods.length] = method;
|
||||
type.methods = newArray;
|
||||
System.out.println("Generated getFoo method");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package lombok.apt;
|
||||
|
||||
import static lombok.apt.PKG.CURRENT_SUPPORT;
|
||||
import static lombok.apt.PKG.isInstanceOf;
|
||||
import static lombok.apt.PKG.readResource;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
/**
|
||||
* Responsible for redirecting the need to handle an annotation to a class that knows how to handle a given annotation type in a given compiler environment.
|
||||
* Will dynamically locate a class in this package using the naming pattern: "HandleFoo_compilerType", e.g. "HandleGetter_ecj".
|
||||
* Responsible for injecting the proper class into the right classloader so that it has open access to the classes required to inspect the live AST and
|
||||
* modify it so that annotations can cause changes to the live AST.
|
||||
*
|
||||
* @author rzwitserloot
|
||||
*
|
||||
* @param <T> The annotation class that this transponder should handle (example: Getter.class).
|
||||
*/
|
||||
public class AnnotationTransponder<T extends Annotation> {
|
||||
private HandlerForCompiler<T> impl;
|
||||
private final ProcessingEnvironment processEnv;
|
||||
private final RoundEnvironment roundEnv;
|
||||
private String error;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void createInstance(Class<T> annotation, ClassLoader loader, String compilerType) {
|
||||
try {
|
||||
if ( loader == null ) loader = AnnotationTransponder.class.getClassLoader();
|
||||
Class<?> implClass;
|
||||
try {
|
||||
implClass = loader.loadClass(String.format(
|
||||
"org.javanext.apt.Handle%s_%s", annotation.getSimpleName(), compilerType));
|
||||
} catch ( ClassNotFoundException e ) {
|
||||
implClass = loader.loadClass(String.format("lombok.apt.HandleANY_%s", compilerType));
|
||||
}
|
||||
|
||||
Constructor<?> constructor;
|
||||
|
||||
constructor = implClass.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
impl = (HandlerForCompiler<T>)constructor.newInstance();
|
||||
impl.processEnv = processEnv;
|
||||
impl.roundEnv = roundEnv;
|
||||
try {
|
||||
impl.init();
|
||||
} catch ( Exception e ) {
|
||||
error = "Exception initializing handler: " + e;
|
||||
impl = null;
|
||||
}
|
||||
} catch ( Exception e ) {
|
||||
e.printStackTrace();
|
||||
error = "You are using " + compilerType + " but a version that's changed the compiler internals. I can't work with it.";
|
||||
}
|
||||
}
|
||||
|
||||
public AnnotationTransponder(Class<T> annotation, RoundEnvironment roundEnv, ProcessingEnvironment processEnv) {
|
||||
this.processEnv = processEnv;
|
||||
this.roundEnv = roundEnv;
|
||||
if ( isInstanceOf(processEnv, "com.sun.tools.javac.processing.JavacProcessingEnvironment") ) {
|
||||
createInstance(annotation, null, "javac");
|
||||
} else if ( isInstanceOf(processEnv, "org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl") ) {
|
||||
final ClassLoader[] parentLoaders =
|
||||
new ClassLoader[] { processEnv.getClass().getClassLoader(), AnnotationTransponder.class.getClassLoader() };
|
||||
|
||||
ClassLoader loader = new ClassLoader() {
|
||||
@Override public Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
if ( name.equals(HandlerForCompiler.class.getName()) ) return HandlerForCompiler.class;
|
||||
if ( name.startsWith(AnnotationTransponder.class.getPackage().getName()) ) {
|
||||
byte[] data = readResource(name.replace(".", "/") + ".class");
|
||||
return defineClass(name, data, 0, data.length);
|
||||
}
|
||||
for ( int i = 0 ; i < parentLoaders.length ; i++ ) {
|
||||
try {
|
||||
return parentLoaders[i].loadClass(name);
|
||||
} catch ( ClassNotFoundException e ) {
|
||||
if ( i == parentLoaders.length -1 ) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
createInstance(annotation, loader, "ecj");
|
||||
} else {
|
||||
impl = null;
|
||||
this.error = "I cannot work with your compiler. I currently only support " + CURRENT_SUPPORT + ".\n" +
|
||||
"This is a: " + processEnv.getClass();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void handle(Element element, T annotation) {
|
||||
if ( impl == null ) {
|
||||
processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, error, element);
|
||||
} else {
|
||||
try {
|
||||
impl.handle(element, annotation);
|
||||
} catch ( Exception e ) {
|
||||
e.printStackTrace();
|
||||
processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Exception in JavaNext: " + e, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package lombok.apt;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
public class HandleANY_ecj extends HandlerForCompiler<Annotation> {
|
||||
@Override public void handle(Element element, Annotation annotation) throws Exception {
|
||||
//TODO: We should find eclipse's eclipse.ini file and patch us in as a javaagent and bootclasspath/a.
|
||||
//Though, we should probably use reflection to find eclipse's SWT system and generate a popup dialog for
|
||||
//confirmation.
|
||||
|
||||
String msg = "You'll need to install the eclipse patch. See http://lombok.github.org/ for more info.";
|
||||
processEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, msg, element);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package lombok.apt;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.TreeMaker;
|
||||
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
|
||||
import com.sun.tools.javac.tree.JCTree.JCBlock;
|
||||
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCExpression;
|
||||
import com.sun.tools.javac.tree.JCTree.JCStatement;
|
||||
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
|
||||
class HandleGetter_javac extends HandlerForCompiler<Getter> {
|
||||
private final Trees trees;
|
||||
private final Messager messager;
|
||||
private JavacProcessingEnvironment env;
|
||||
|
||||
HandleGetter_javac() {
|
||||
this.messager = processEnv.getMessager();
|
||||
this.trees = Trees.instance(processEnv);
|
||||
}
|
||||
|
||||
@Override public void init() {
|
||||
this.env = (JavacProcessingEnvironment)processEnv;
|
||||
}
|
||||
|
||||
@Override public void handle(Element element, Getter getter) {
|
||||
if ( !element.getKind().isField() ) {
|
||||
messager.printMessage(Diagnostic.Kind.ERROR, "@Getter is only supported on a field.");
|
||||
return;
|
||||
}
|
||||
|
||||
TypeElement classElement = (TypeElement)element.getEnclosingElement();
|
||||
JCClassDecl javacClassTree = (JCClassDecl)trees.getTree(classElement);
|
||||
|
||||
Name.Table nameTable = Name.Table.instance(env.getContext());
|
||||
TreeMaker treeMaker = TreeMaker.instance(env.getContext());
|
||||
|
||||
MethodTree getterMethod = createGetter(element, treeMaker, nameTable);
|
||||
javacClassTree.defs = javacClassTree.defs.append((JCTree)getterMethod);
|
||||
|
||||
messager.printMessage(Diagnostic.Kind.WARNING, "Generated a getter for this field", element);
|
||||
}
|
||||
|
||||
private MethodTree createGetter(Element field, TreeMaker treeMaker, Name.Table nameTable) {
|
||||
JCStatement returnStatement = treeMaker.Return(treeMaker.Ident((Symbol)field));
|
||||
JCBlock methodBody = treeMaker.Block(0, List.of(returnStatement));
|
||||
Name methodName = Name.fromString(nameTable, PKG.toGetterName(field));
|
||||
JCExpression methodType = treeMaker.Type((Type)field.asType());
|
||||
|
||||
List<JCTypeParameter> methodGenericParams = List.nil();
|
||||
List<JCVariableDecl> parameters = List.nil();
|
||||
List<JCExpression> throwsClauses = List.nil();
|
||||
JCExpression annotationMethodDefaultValue = null;
|
||||
|
||||
return treeMaker.MethodDef(treeMaker.Modifiers(Modifier.PUBLIC, List.<JCAnnotation>nil()), methodName, methodType,
|
||||
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package lombok.apt;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
|
||||
public abstract class HandlerForCompiler<T extends Annotation> {
|
||||
protected ProcessingEnvironment processEnv;
|
||||
protected RoundEnvironment roundEnv;
|
||||
|
||||
public void init() throws Exception {}
|
||||
|
||||
public abstract void handle(Element element, T annotation) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package lombok.apt;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
|
||||
import lombok.Lombok;
|
||||
|
||||
|
||||
class PKG {
|
||||
static final String CURRENT_SUPPORT = "javac 1.6 and eclipse (ecj).";
|
||||
|
||||
private PKG() {}
|
||||
|
||||
static boolean isInstanceOf(Object o, String className) {
|
||||
if ( o == null ) return false;
|
||||
return isInstanceOf(o.getClass(), className);
|
||||
}
|
||||
|
||||
static boolean isInstanceOf(Class<?> c, String className) {
|
||||
if ( c == Object.class || c == null ) return false;
|
||||
|
||||
if ( c.getName().equals(className) ) return true;
|
||||
|
||||
if ( isInstanceOf(c.getSuperclass(), className) ) return true;
|
||||
for ( Class<?> iface : c.getInterfaces() ) {
|
||||
if ( isInstanceOf(iface, className) ) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static byte[] readResource(String name) {
|
||||
return readResource(PKG.class.getClassLoader(), name);
|
||||
}
|
||||
|
||||
static byte[] readResource(ClassLoader loader, String name) {
|
||||
InputStream in = loader.getResourceAsStream(name);
|
||||
if ( in == null ) throw Lombok.sneakyThrow(new IOException("Not found: " + name));
|
||||
|
||||
try {
|
||||
return readStream(in);
|
||||
} catch (IOException e) {
|
||||
throw Lombok.sneakyThrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
static String toGetterName(Element element) {
|
||||
CharSequence fieldName = element.getSimpleName();
|
||||
if ( fieldName.length() == 0 ) return "get";
|
||||
|
||||
final String prefix, suffix;
|
||||
|
||||
if ( element.asType().getKind() == TypeKind.BOOLEAN || "java.lang.Boolean".equals(element.asType().toString()) ) prefix = "is";
|
||||
else prefix = "get";
|
||||
|
||||
char first = fieldName.charAt(0);
|
||||
if ( Character.isLowerCase(first) )
|
||||
suffix = String.format("%s%s", Character.toTitleCase(first), fieldName.subSequence(1, fieldName.length()));
|
||||
else suffix = fieldName.toString();
|
||||
return String.format("%s%s", prefix, suffix);
|
||||
}
|
||||
|
||||
static byte[] readStream(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] b = new byte[65536];
|
||||
while ( true ) {
|
||||
int r = in.read(b);
|
||||
if ( r == -1 ) break;
|
||||
if ( r > 0 ) baos.write(b, 0, r);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package lombok.apt;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
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.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
@SupportedAnnotationTypes("lombok.*")
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_6)
|
||||
public class Processor extends AbstractProcessor {
|
||||
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
for ( TypeElement typeElement : annotations ) {
|
||||
if ( typeElement.getQualifiedName().contentEquals(Getter.class.getName()) )
|
||||
return handle(roundEnv, Getter.class, typeElement);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private <T extends Annotation> boolean handle(RoundEnvironment roundEnv, Class<T> annotation, TypeElement typeElement) {
|
||||
for ( Element element : roundEnv.getElementsAnnotatedWith(typeElement) ) {
|
||||
new AnnotationTransponder<T>(annotation, roundEnv, processingEnv).handle(element, element.getAnnotation(annotation));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user