From a6a8e4c5554ff000bbac43f4f7f530f661e036d2 Mon Sep 17 00:00:00 2001
From: Robbert Jan Grootjans <grootjans@gmail.com>
Date: Tue, 22 May 2012 00:24:59 +0200
Subject: Javac implementation of @ExtensionMethod.

Casual tests show that it is working. Taken from lombok-pg.
---
 src/core/lombok/experimental/ExtensionMethod.java  |  64 +++++++
 src/core/lombok/javac/handlers/HandleData.java     |  11 +-
 .../javac/handlers/HandleExtensionMethod.java      | 208 +++++++++++++++++++++
 .../lombok/javac/handlers/JavacHandlerUtil.java    |  36 +++-
 src/core/lombok/javac/handlers/JavacResolver.java  |  88 +++++++++
 5 files changed, 390 insertions(+), 17 deletions(-)
 create mode 100644 src/core/lombok/experimental/ExtensionMethod.java
 create mode 100644 src/core/lombok/javac/handlers/HandleExtensionMethod.java
 create mode 100644 src/core/lombok/javac/handlers/JavacResolver.java

(limited to 'src')

diff --git a/src/core/lombok/experimental/ExtensionMethod.java b/src/core/lombok/experimental/ExtensionMethod.java
new file mode 100644
index 00000000..b25e535e
--- /dev/null
+++ b/src/core/lombok/experimental/ExtensionMethod.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ * 
+ * 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.experimental;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.*;
+
+/**
+ * Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or
+ * otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as
+ * if they were instance methods on the extended type.
+ * <p>
+ * Before:
+ * 
+ * <pre>
+ * &#064;ExtensionMethod(java.util.Arrays.class)
+ * class Example {
+ * 	private void example() {
+ * 		long[] values = new long[] { 2, 5, 7, 9 };
+ * 		values.copyOf(3).sort();
+ * 	}
+ * }
+ * </pre>
+ * 
+ * After:
+ * 
+ * <pre>
+ * class Example {
+ * 	private void example() {
+ * 		long[] values = new long[] { 2, 5, 7, 9 };
+ * 		java.util.Arrays.sort(java.util.Arrays.copyOf(values, 3));
+ * 	}
+ * }
+ * </pre>
+ */
+@Target(TYPE)
+@Retention(SOURCE)
+public @interface ExtensionMethod {
+	/** All types whose static methods will be exposed as extension methods. */
+	Class<?>[] value();
+
+	boolean suppressBaseMethods() default true;
+}
diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java
index bec98960..62183a15 100644
--- a/src/core/lombok/javac/handlers/HandleData.java
+++ b/src/core/lombok/javac/handlers/HandleData.java
@@ -21,7 +21,7 @@
  */
 package lombok.javac.handlers;
 
-import static lombok.javac.handlers.JavacHandlerUtil.deleteAnnotationIfNeccessary;
+import static lombok.javac.handlers.JavacHandlerUtil.*;
 import lombok.AccessLevel;
 import lombok.Data;
 import lombok.core.AnnotationValues;
@@ -30,9 +30,7 @@ import lombok.javac.JavacNode;
 
 import org.mangosdk.spi.ProviderFor;
 
-import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.tree.JCTree.JCAnnotation;
-import com.sun.tools.javac.tree.JCTree.JCClassDecl;
 
 /**
  * Handles the {@code lombok.Data} annotation for javac.
@@ -42,12 +40,9 @@ public class HandleData extends JavacAnnotationHandler<Data> {
 	@Override public void handle(AnnotationValues<Data> annotation, JCAnnotation ast, JavacNode annotationNode) {
 		deleteAnnotationIfNeccessary(annotationNode, Data.class);
 		JavacNode typeNode = annotationNode.up();
-		JCClassDecl typeDecl = null;
-		if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl)typeNode.get();
-		long flags = typeDecl == null ? 0 : typeDecl.mods.flags;
-		boolean notAClass = (flags & (Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION)) != 0;
+		boolean notAClass = !isClass(typeNode);
 		
-		if (typeDecl == null || notAClass) {
+		if (notAClass) {
 			annotationNode.addError("@Data is only supported on a class.");
 			return;
 		}
diff --git a/src/core/lombok/javac/handlers/HandleExtensionMethod.java b/src/core/lombok/javac/handlers/HandleExtensionMethod.java
new file mode 100644
index 00000000..2df6be45
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleExtensionMethod.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ * 
+ * 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 static com.sun.tools.javac.code.Flags.*;
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+import static lombok.javac.handlers.JavacResolver.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.lang.model.element.ElementKind;
+
+import lombok.core.AnnotationValues;
+import lombok.experimental.ExtensionMethod;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+import lombok.javac.ResolutionBased;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.util.TreeScanner;
+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.ErrorType;
+import com.sun.tools.javac.code.Type.ForAll;
+import com.sun.tools.javac.code.Type.MethodType;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+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.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
+import com.sun.tools.javac.tree.TreeMaker;
+
+/**
+ * Handles the {@link ExtensionMethod} annotation for javac.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+@ResolutionBased
+public class HandleExtensionMethod extends JavacAnnotationHandler<ExtensionMethod> {
+	@Override
+	public void handle(final AnnotationValues<ExtensionMethod> annotation, final JCAnnotation source, final JavacNode annotationNode) {
+		deleteAnnotationIfNeccessary(annotationNode, ExtensionMethod.class);
+		JavacNode typeNode = annotationNode.up();
+		boolean isClassOrEnum = isClassOrEnum(typeNode);
+		
+		if (!isClassOrEnum) {
+			annotationNode.addError("@ExtensionMethod can only be used on a class or an enum");
+			return;
+		}
+		
+		boolean suppressBaseMethods = annotation.getInstance().suppressBaseMethods();
+		
+		List<Object> extensionProviders = annotation.getActualExpressions("value");
+		if (extensionProviders.isEmpty()) {
+			annotationNode.addError(String.format("@%s has no effect since no extension types were specified.", ExtensionMethod.class.getName()));
+			return;
+		}
+		final List<Extension> extensions = getExtensions(annotationNode, extensionProviders);
+		if (extensions.isEmpty()) return;
+		
+		// call HandleVal explicitly to ensure val gets handled before @ExtensionMethdod gets handled.
+		// TODO maybe we should prioritize lombok handler
+		annotationNode.traverse(new HandleVal());
+		new ExtensionMethodReplaceVisitor(annotationNode, extensions, suppressBaseMethods).replace();
+		
+		annotationNode.rebuild();
+	}
+
+
+	private List<Extension> getExtensions(final JavacNode typeNode, final List<Object> extensionProviders) {
+		List<Extension> extensions = new ArrayList<Extension>();
+		for (Object extensionProvider : extensionProviders) {
+			if (!(extensionProvider instanceof JCFieldAccess)) continue;
+			JCFieldAccess provider = (JCFieldAccess) extensionProvider;
+			if (!("class".equals(provider.name.toString()))) continue;
+			Type providerType = CLASS.resolveMember(typeNode, provider.selected);
+			if (providerType == null) continue;
+			if ((providerType.tsym.flags() & (INTERFACE | ANNOTATION)) != 0) continue;
+			
+			extensions.add(getExtension(typeNode, (ClassType) providerType));	
+		}
+		return extensions;
+	}
+	
+	private Extension getExtension(final JavacNode typeNode, final ClassType extensionMethodProviderType) {
+		List<MethodSymbol> extensionMethods = new ArrayList<MethodSymbol>();
+		TypeSymbol tsym = extensionMethodProviderType.asElement();
+		if (tsym != null) for (Symbol member : tsym.getEnclosedElements()) {
+			if (member.getKind() != ElementKind.METHOD) continue;
+			MethodSymbol method = (MethodSymbol) member;
+			if ((method.flags() & (STATIC | PUBLIC)) == 0) continue;
+			if (method.params().isEmpty()) continue;
+			extensionMethods.add(method);
+		}
+		return new Extension(extensionMethods, tsym);
+	}
+	
+	private static class Extension {
+		final List<MethodSymbol> extensionMethods;
+		final TypeSymbol extensionProvider;
+		
+		public Extension(List<MethodSymbol> extensionMethods, TypeSymbol extensionProvider) {
+			this.extensionMethods = extensionMethods;
+			this.extensionProvider = extensionProvider;
+		}
+	}
+	
+	private static class ExtensionMethodReplaceVisitor extends TreeScanner<Void, Void> {
+		final JavacNode annotationNode;
+		final List<Extension> extensions;
+		final boolean suppressBaseMethods;
+		
+		public ExtensionMethodReplaceVisitor(JavacNode annotationNode, List<Extension> extensions, boolean suppressBaseMethods) {
+			this.annotationNode = annotationNode;
+			this.extensions = extensions;
+			this.suppressBaseMethods = suppressBaseMethods;
+		}
+		
+		public void replace() {
+			annotationNode.up().get().accept(this, null);
+		}
+		
+		@Override
+		public Void visitMethodInvocation(final MethodInvocationTree tree, final Void p) {
+			handleMethodCall((JCMethodInvocation) tree);
+			return super.visitMethodInvocation(tree, p);
+		}
+
+		private void handleMethodCall(final JCMethodInvocation methodCall) {
+			JavacNode methodCallNode = annotationNode.getAst().get(methodCall);
+			
+			JavacNode surroundingType = upToTypeNode(methodCallNode);
+			
+			TypeSymbol surroundingTypeSymbol = ((JCClassDecl)surroundingType.get()).sym;
+			JCExpression receiver = receiverOf(methodCall);
+			String methodName = methodNameOf(methodCall);
+			
+			if ("this".equals(methodName) || "super".equals(methodName)) return;
+			Type resolvedMethodCall = CLASS_AND_METHOD.resolveMember(methodCallNode, methodCall);
+			if (resolvedMethodCall == null) return;
+			if (!suppressBaseMethods && !(resolvedMethodCall instanceof ErrorType)) return;
+			Type receiverType = CLASS_AND_METHOD.resolveMember(methodCallNode, receiver);
+			if (receiverType == null) return;
+			if (receiverType.tsym.toString().endsWith(receiver.toString())) return;
+			
+			Types types = Types.instance(annotationNode.getContext());
+			for (Extension extension : extensions) {
+				TypeSymbol extensionProvider = extension.extensionProvider;
+				if (surroundingTypeSymbol == extensionProvider) continue;
+				for (MethodSymbol extensionMethod : extension.extensionMethods) {
+					if (!methodName.equals(extensionMethod.name.toString())) continue;
+					Type extensionMethodType = extensionMethod.type;
+					if (!MethodType.class.isInstance(extensionMethodType) && !ForAll.class.isInstance(extensionMethodType)) continue;
+					Type firstArgType = types.erasure(extensionMethodType.asMethodType().argtypes.get(0));
+					if (!types.isAssignable(receiverType, firstArgType)) continue;
+					methodCall.args = methodCall.args.prepend(receiver);
+					
+					TreeMaker maker = annotationNode.getTreeMaker();
+					JCIdent extensionClassIdent = maker.Ident(annotationNode.toName(extensionProvider.toString()));
+					methodCall.meth = maker.Select(extensionClassIdent, annotationNode.toName(methodName));
+					return;
+				}
+			}
+		}
+		
+		private String methodNameOf(final JCMethodInvocation methodCall) {
+			if (methodCall.meth instanceof JCIdent) {
+				return ((JCIdent) methodCall.meth).name.toString();
+			} else {
+				return ((JCFieldAccess) methodCall.meth).name.toString();
+			}
+		}
+		
+		private JCExpression receiverOf(final JCMethodInvocation methodCall) {
+			if (methodCall.meth instanceof JCIdent) {
+				return annotationNode.getTreeMaker().Ident(annotationNode.toName("this"));
+			} else {
+				return ((JCFieldAccess) methodCall.meth).selected;
+			}
+		}
+	}
+}
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
index fc7666ec..6dc97fd4 100644
--- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -387,9 +387,7 @@ public class JavacHandlerUtil {
 	 * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
 	 */
 	public static MemberExistsResult fieldExists(String fieldName, JavacNode node) {
-		while (node != null && !(node.get() instanceof JCClassDecl)) {
-			node = node.up();
-		}
+		node = upToTypeNode(node);
 		
 		if (node != null && node.get() instanceof JCClassDecl) {
 			for (JCTree def : ((JCClassDecl)node.get()).defs) {
@@ -418,9 +416,7 @@ public class JavacHandlerUtil {
 	 * @param params The number of parameters the method should have; varargs count as 0-*. Set to -1 to find any method with the appropriate name regardless of parameter count.
 	 */
 	public static MemberExistsResult methodExists(String methodName, JavacNode node, boolean caseSensitive, int params) {
-		while (node != null && !(node.get() instanceof JCClassDecl)) {
-			node = node.up();
-		}
+		node = upToTypeNode(node);
 		
 		if (node != null && node.get() instanceof JCClassDecl) {
 			for (JCTree def : ((JCClassDecl)node.get()).defs) {
@@ -461,9 +457,7 @@ public class JavacHandlerUtil {
 	 * @param node Any node that represents the Type (JCClassDecl) to look in, or any child node thereof.
 	 */
 	public static MemberExistsResult constructorExists(JavacNode node) {
-		while (node != null && !(node.get() instanceof JCClassDecl)) {
-			node = node.up();
-		}
+		node = upToTypeNode(node);
 		
 		if (node != null && node.get() instanceof JCClassDecl) {
 			for (JCTree def : ((JCClassDecl)node.get()).defs) {
@@ -871,4 +865,28 @@ public class JavacHandlerUtil {
 		}
 		return out.toList();
 	}
+	
+	static boolean isClass(JavacNode typeNode) {
+		return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION);
+	}
+	
+	static boolean isClassOrEnum(JavacNode typeNode) {
+		return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION);
+	}
+	
+	private static boolean isClassAndDoesNotHaveFlags(JavacNode typeNode, int flags) {
+		JCClassDecl typeDecl = null;
+		if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl)typeNode.get();
+		else return false;
+		
+		long typeDeclflags = typeDecl == null ? 0 : typeDecl.mods.flags;
+		return (typeDeclflags & flags) == 0;
+	}
+	
+	public static JavacNode upToTypeNode(JavacNode node) {
+		if (node == null) throw new NullPointerException("node");
+		while ((node != null) && !(node.get() instanceof JCClassDecl)) node = node.up();
+		
+		return node;
+	}
 }
diff --git a/src/core/lombok/javac/handlers/JavacResolver.java b/src/core/lombok/javac/handlers/JavacResolver.java
new file mode 100644
index 00000000..62f98841
--- /dev/null
+++ b/src/core/lombok/javac/handlers/JavacResolver.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Project Lombok Authors.
+ * 
+ * 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 lombok.javac.JavacNode;
+import lombok.javac.JavacResolution;
+
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+public enum JavacResolver {
+	CLASS {
+		@Override
+		public Type resolveMember(JavacNode node, JCExpression expr) {
+			Type type = expr.type;
+			if (type == null) {
+				try {
+					new JavacResolution(node.getContext()).resolveClassMember(node);
+					type = expr.type;
+				} catch (Exception ignore) {
+				}
+			}
+			return type;
+		}
+	},
+	METHOD {
+		public Type resolveMember(JavacNode node, JCExpression expr) {
+			Type type = expr.type;
+			if (type == null) {
+				try {
+					JCExpression resolvedExpression = ((JCExpression) new JavacResolution(node.getContext()).resolveMethodMember(node).get(expr));
+					if (resolvedExpression != null) type = resolvedExpression.type;
+				} catch (Exception ignore) {
+				}
+			}
+			return type;
+		}
+	},
+	CLASS_AND_METHOD {
+		@Override
+		public Type resolveMember(JavacNode node, JCExpression expr) {
+			Type type = METHOD.resolveMember(node, expr);
+			if (type == null) {
+				JavacNode classNode = node;
+				while (classNode != null && noneOf(classNode.get(), JCBlock.class, JCMethodDecl.class, JCVariableDecl.class)) {
+					classNode = classNode.up();
+				}
+				if (classNode != null) {
+					type = CLASS.resolveMember(classNode, expr);
+				}
+			}
+			return type;
+		}
+		
+		private boolean noneOf(Object o, Class<?>... clazzes) {
+			for (Class<?> clazz : clazzes) {
+				if (clazz.isInstance(o)) return false;
+			}
+			return true;
+		}
+	};
+	
+	
+
+	public abstract Type resolveMember(final JavacNode node, final JCExpression expr);
+}
-- 
cgit