From c340c9c24f45b7007ed04fb5ce9e1f1819e8e0ba Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 22 Nov 2010 23:01:21 +0100 Subject: @Delegate, at least without value=, seems to work great in javac now! --- src/core/lombok/javac/JavacResolution.java | 12 +- src/core/lombok/javac/handlers/HandleDelegate.java | 264 +++++++++++++++++++++ src/core/lombok/javac/handlers/HandleVal.java | 4 +- .../lombok/eclipse/agent/PatchDelegate.java | 12 +- 4 files changed, 278 insertions(+), 14 deletions(-) create mode 100644 src/core/lombok/javac/handlers/HandleDelegate.java diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 730cca31..b746e673 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -317,8 +317,8 @@ public class JavacResolution { return iterableParams.isEmpty() ? syms.objectType : types.upperBound(iterableParams.head); } - public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast) throws TypeNotConvertibleException { - return typeToJCTree(type, maker, ast, false); + public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowVoid) throws TypeNotConvertibleException { + return typeToJCTree(type, maker, ast, false, allowVoid); } public static JCExpression createJavaLangObject(TreeMaker maker, JavacAST ast) { @@ -328,7 +328,7 @@ public class JavacResolution { return out; } - private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException { int dims = 0; Type type0 = type; while (type0 instanceof ArrayType) { @@ -336,7 +336,7 @@ public class JavacResolution { type0 = ((ArrayType)type0).elemtype; } - JCExpression result = typeToJCTree0(type0, maker, ast, allowCompound); + JCExpression result = typeToJCTree0(type0, maker, ast, allowCompound, allowVoid); while (dims > 0) { result = maker.TypeArray(result); dims--; @@ -344,12 +344,12 @@ public class JavacResolution { return result; } - private static JCExpression typeToJCTree0(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) throws TypeNotConvertibleException { + private static JCExpression typeToJCTree0(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound, boolean allowVoid) throws TypeNotConvertibleException { // NB: There's such a thing as maker.Type(type), but this doesn't work very well; it screws up anonymous classes, captures, and adds an extra prefix dot for some reason too. // -- so we write our own take on that here. if (type.tag == TypeTags.BOT) return createJavaLangObject(maker, ast); - + if (type.tag == TypeTags.VOID) return allowVoid ? primitiveToJCTree(type.getKind(), maker) : createJavaLangObject(maker, ast); if (type.isPrimitive()) return primitiveToJCTree(type.getKind(), maker); if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved"); diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java new file mode 100644 index 00000000..ad5eea16 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -0,0 +1,264 @@ +/* + * Copyright © 2010 Reinier Zwitserloot, Roel Spilker and Robbert Jan Grootjans. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.javac.handlers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import lombok.Delegate; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.JavacResolution; +import lombok.javac.JavacResolution.TypeNotConvertibleException; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.MethodSymbol; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type.TypeVar; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.model.JavacTypes; +import com.sun.tools.javac.tree.JCTree; +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.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +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.tree.TreeMaker; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacAnnotationHandler.class) +public class HandleDelegate implements JavacAnnotationHandler { + @Override public boolean isResolutionBased() { + return true; + } + + private static final List METHODS_IN_OBJECT = Collections.unmodifiableList(Arrays.asList( + "hashCode()", + "equals(java.lang.Object)", + "wait()", + "wait(long)", + "wait(long, int)", + "notify()", + "notifyAll()", + "toString()", + "getClass()", + "clone()", + "finalize()")); + + @Override public boolean handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { + if (annotationNode.up().getKind() != Kind.FIELD) return false; // TODO error + + List delegateTypes = annotation.getActualExpressions("value"); + JavacResolution reso = new JavacResolution(annotationNode.getContext()); + List resolved = new ArrayList(); + + if (delegateTypes.isEmpty()) { + Type type = ((JCVariableDecl)annotationNode.up().get()).type; + if (type == null) reso.resolveClassMember(annotationNode.up()); + //TODO I'm fairly sure the above line (and that entire method) does effectively bupkis! + type = ((JCVariableDecl)annotationNode.up().get()).type; + if (type != null) resolved.add(type); + } else { + for (Object dt : delegateTypes) { + if (dt instanceof JCFieldAccess && ((JCFieldAccess)dt).name.toString().equals("class")) { + Type type = ((JCFieldAccess)dt).selected.type; + if (type == null) reso.resolveClassMember(annotationNode); + type = ((JCFieldAccess)dt).selected.type; + if (type != null) resolved.add(type); + } + } + } + + List signatures = new ArrayList(); + Set banList = new HashSet(); + for (Type t : resolved) { + banList.addAll(METHODS_IN_OBJECT); + JavacNode typeNode = annotationNode.up().up(); + for (Symbol member : ((JCClassDecl)typeNode.get()).sym.getEnclosedElements()) { + if (member instanceof MethodSymbol) { + MethodSymbol method = (MethodSymbol) member; + banList.add(printSig((ExecutableType) method.asType(), method.name, annotationNode.getTypesUtil())); + } + } + if (t instanceof ClassType) { + ClassType ct = (ClassType) t; + addMethodBindings(signatures, ct, annotationNode, banList); + } else { + annotationNode.addError("@Delegate can only use concrete class types, not wildcards, arrays, type variables, or primitives."); + return false; + } + } + + Name delegateFieldName = annotationNode.toName(annotationNode.up().getName()); + + for (MethodSig sig : signatures) generateAndAdd(sig, annotationNode, delegateFieldName); + + return false; + } + + private void generateAndAdd(MethodSig sig, JavacNode annotation, Name delegateFieldName) { + try { + JCMethodDecl method = createDelegateMethod(sig, annotation, delegateFieldName); + JavacHandlerUtil.injectMethod(annotation.up().up(), method); + } catch (TypeNotConvertibleException e) { + annotation.addError("Can't create delegate method for " + sig.name + ": " + e.getMessage()); + } + } + + private JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateFieldName) throws TypeNotConvertibleException { + /** public ReturnType methodName(ParamType1 $p1, ParamType2 $p2, ...) throws T1, T2, ... { + * (return) delegate.methodName($p1, $p2); + * } + */ + + TreeMaker maker = annotation.getTreeMaker(); + + com.sun.tools.javac.util.List annotations; + if (sig.isDeprecated) { + annotations = com.sun.tools.javac.util.List.of(maker.Annotation( + JavacHandlerUtil.chainDots(maker, annotation, "java", "lang", "Deprecated"), + com.sun.tools.javac.util.List.nil())); + } else { + annotations = com.sun.tools.javac.util.List.nil(); + } + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annotations); + JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), maker, annotation.getAst(), true); + boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID; + ListBuffer params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); + ListBuffer args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); + ListBuffer thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer(); + ListBuffer typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer(); + ListBuffer typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer(); + Types types = Types.instance(annotation.getContext()); + + int nameCounter = 0; + for (TypeMirror param : sig.type.getTypeVariables()) { + Name name = annotation.toName("P" + (nameCounter++)); + typeParams.append(maker.TypeParameter(name, maker.Types(types.getBounds((TypeVar) param)))); + typeArgs.append(maker.Ident(name)); + } + + for (TypeMirror ex : sig.type.getThrownTypes()) { + thrown.append(JavacResolution.typeToJCTree((Type) ex, maker, annotation.getAst(), true)); + } + + nameCounter = 0; + for (TypeMirror param : sig.type.getParameterTypes()) { + Name name = annotation.toName("$a" + (nameCounter++)); + JCModifiers paramMods = maker.Modifiers(Flags.FINAL); + params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, maker, annotation.getAst(), true), null)); + args.append(maker.Ident(name)); + } + + JCExpression delegateFieldRef = maker.Select(maker.Ident(annotation.toName("this")), delegateFieldName); + + JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(delegateFieldRef, sig.name), toList(args)); + JCStatement body = useReturn ? maker.Return(delegateCall) : maker.Exec(delegateCall); + JCBlock bodyBlock = maker.Block(0, com.sun.tools.javac.util.List.of(body)); + + return maker.MethodDef(mods, sig.name, returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null); + } + + private static com.sun.tools.javac.util.List toList(ListBuffer collection) { + return collection == null ? com.sun.tools.javac.util.List.nil() : collection.toList(); + } + + private void addMethodBindings(List signatures, ClassType ct, JavacNode node, Set banList) { + TypeSymbol tsym = ct.asElement(); + if (tsym == null) return; + for (Symbol member : tsym.getEnclosedElements()) { + if (member.getKind() != ElementKind.METHOD) continue; + if (member.isStatic()) continue; + if (member.isConstructor()) continue; + ExecutableElement exElem = (ExecutableElement)member; + if (!exElem.getModifiers().contains(Modifier.PUBLIC)) continue; + ExecutableType methodType = (ExecutableType) node.getTypesUtil().asMemberOf(ct, member); + String sig = printSig(methodType, member.name, node.getTypesUtil()); + if (!banList.add(sig)) continue; //If add returns false, it was already in there + boolean isDeprecated = exElem.getAnnotation(Deprecated.class) != null; + signatures.add(new MethodSig(member.name, methodType, isDeprecated)); + } + + if (ct.supertype_field instanceof ClassType) addMethodBindings(signatures, (ClassType) ct.supertype_field, node, banList); + if (ct.interfaces_field != null) for (Type iface : ct.interfaces_field) { + if (iface instanceof ClassType) addMethodBindings(signatures, (ClassType) iface, node, banList); + } + } + + private static class MethodSig { + final Name name; + final ExecutableType type; + final boolean isDeprecated; + + MethodSig(Name name, ExecutableType type, boolean isDeprecated) { + this.name = name; + this.type = type; + this.isDeprecated = isDeprecated; + } + + @Override public String toString() { + return (isDeprecated ? "@Deprecated " : "") + name + " " + type; + } + } + + private static String printSig(ExecutableType method, Name name, JavacTypes types) { + StringBuilder sb = new StringBuilder(); + sb.append(name.toString()).append("("); + boolean first = true; + for (TypeMirror param : method.getParameterTypes()) { + if (!first) sb.append(", "); + first = false; + sb.append(typeBindingToSignature(param, types)); + } + return sb.append(")").toString(); + } + + private static String typeBindingToSignature(TypeMirror binding, JavacTypes types) { + binding = types.erasure(binding); + return binding.toString(); + } +} diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 3aa695ea..98142a3b 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -90,9 +90,9 @@ public class HandleVal extends JavacASTAdapter { if (rhsOfEnhancedForLoop != null) { Type componentType = JavacResolution.ifTypeIsIterableToComponent(type, localNode.getAst()); if (componentType == null) replacement = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); - else replacement = JavacResolution.typeToJCTree(componentType, localNode.getTreeMaker(), localNode.getAst()); + else replacement = JavacResolution.typeToJCTree(componentType, localNode.getTreeMaker(), localNode.getAst(), false); } else { - replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst()); + replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst(), false); } if (replacement != null) { diff --git a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java index 8f7213f0..c7da502e 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java +++ b/src/eclipseAgent/lombok/eclipse/agent/PatchDelegate.java @@ -72,6 +72,7 @@ import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; public class PatchDelegate { static void addPatches(ScriptManager sm, boolean ecj) { @@ -287,7 +288,7 @@ public class PatchDelegate { addAllMethodBindings(list, binding, banList); } - private static void addAllMethodBindings(List list, TypeBinding binding, Collection banList) { + private static void addAllMethodBindings(List list, TypeBinding binding, Set banList) { if (binding == null) return; if (binding instanceof MemberTypeBinding) { ClassScope cs = ((SourceTypeBinding)binding).scope; @@ -310,9 +311,8 @@ public class PatchDelegate { if (mb.isDefaultAbstract()) continue; if (!mb.isPublic()) continue; if (mb.isSynthetic()) continue; - if (banList.contains(sig)) continue; + if (!banList.add(sig)) continue; // If add returns false, it was already in there. list.add(mb); - banList.add(sig); } addAllMethodBindings(list, rb.superclass(), banList); ReferenceBinding[] interfaces = rb.superInterfaces(); @@ -344,14 +344,14 @@ public class PatchDelegate { if (binding.parameters != null) for (TypeBinding param : binding.parameters) { if (!first) signature.append(", "); first = false; - signature.append(simpleTypeBindingToString(param)); + signature.append(typeBindingToSignature(param)); } signature.append(")"); return signature.toString(); } - private static String simpleTypeBindingToString(TypeBinding binding) { + private static String typeBindingToSignature(TypeBinding binding) { binding = binding.erasure(); if (binding != null && binding.isBaseType()) { return new String (binding.sourceName()); @@ -361,7 +361,7 @@ public class PatchDelegate { return pkg.isEmpty() ? qsn : (pkg + "." + qsn); } else if (binding instanceof ArrayBinding) { StringBuilder out = new StringBuilder(); - out.append(simpleTypeBindingToString(binding.leafComponentType())); + out.append(typeBindingToSignature(binding.leafComponentType())); for (int i = 0; i < binding.dimensions(); i++) out.append("[]"); return out.toString(); } -- cgit