diff options
29 files changed, 1254 insertions, 220 deletions
diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 6d0f3147..4a57e080 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -78,6 +78,10 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, this.isStructurallySignificant = calculateIsStructurallySignificant(null); } + public A getAst() { + return ast; + } + /** {@inheritDoc} */ @Override public String toString() { return String.format("NODE %s (%s) %s%s", diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index bbbdacd0..5b792874 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -183,7 +183,13 @@ public class HandlerLibrary { */ public void callASTVisitors(JavacAST ast) { for (JavacASTVisitor visitor : visitorHandlers) try { - ast.traverse(visitor); + if (!visitor.isResolutionBased()) ast.traverse(visitor); + } catch (Throwable t) { + javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t); + } + + for (JavacASTVisitor visitor : visitorHandlers) try { + if (visitor.isResolutionBased()) ast.traverse(visitor); } catch (Throwable t) { javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t); } diff --git a/src/core/lombok/javac/JavacASTAdapter.java b/src/core/lombok/javac/JavacASTAdapter.java index 41bc46d3..bbdb6876 100644 --- a/src/core/lombok/javac/JavacASTAdapter.java +++ b/src/core/lombok/javac/JavacASTAdapter.java @@ -35,6 +35,11 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; */ public class JavacASTAdapter implements JavacASTVisitor { /** {@inheritDoc} */ + @Override public boolean isResolutionBased() { + return false; + } + + /** {@inheritDoc} */ @Override public void visitCompilationUnit(JavacNode top, JCCompilationUnit unit) {} /** {@inheritDoc} */ diff --git a/src/core/lombok/javac/JavacASTVisitor.java b/src/core/lombok/javac/JavacASTVisitor.java index 3c5887a7..18376037 100644 --- a/src/core/lombok/javac/JavacASTVisitor.java +++ b/src/core/lombok/javac/JavacASTVisitor.java @@ -38,6 +38,12 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; */ public interface JavacASTVisitor { /** + * If true, you'll be called after all the non-resolution based visitors. + * NB: Temporary solution - will be rewritten to a different style altogether in a future release. + */ + boolean isResolutionBased(); + + /** * Called at the very beginning and end. */ void visitCompilationUnit(JavacNode top, JCCompilationUnit unit); @@ -101,6 +107,10 @@ public interface JavacASTVisitor { private int disablePrinting = 0; private int indent = 0; + @Override public boolean isResolutionBased() { + return false; + } + /** * @param printContent if true, bodies are printed directly, as java code, * instead of a tree listing of every AST node inside it. diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java new file mode 100644 index 00000000..6c60b11e --- /dev/null +++ b/src/core/lombok/javac/JavacResolution.java @@ -0,0 +1,480 @@ +package lombok.javac; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.util.ArrayDeque; +import java.util.Map; + +import javax.lang.model.type.TypeKind; +import javax.tools.DiagnosticListener; + +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.code.Symbol.TypeSymbol; +import com.sun.tools.javac.code.Type.CapturedType; +import com.sun.tools.javac.code.Type.ClassType; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.comp.MemberEnter; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; + +public class JavacResolution { + private final Attr attr; + private final LogDisabler logDisabler; + + public JavacResolution(Context context) { + attr = Attr.instance(context); + logDisabler = new LogDisabler(context); + } + /** + * During resolution, the resolver will emit resolution errors, but without appropriate file names and line numbers. If these resolution errors stick around + * then they will be generated AGAIN, this time with proper names and line numbers, at the end. Therefore, we want to suppress the logger. + */ + private static final class LogDisabler { + private final Log log; + private static final Field errWriterField, warnWriterField, noticeWriterField, dumpOnErrorField, promptOnErrorField, diagnosticListenerField; + private PrintWriter errWriter, warnWriter, noticeWriter; + private Boolean dumpOnError, promptOnError; + private DiagnosticListener<?> contextDiagnosticListener, logDiagnosticListener; + private final Context context; + + // If this is true, the fields changed. Better to print weird error messages than to fail outright. + private static final boolean dontBother; + + static { + boolean z; + Field a = null, b = null, c = null, d = null, e = null, f = null; + try { + a = Log.class.getDeclaredField("errWriter"); + b = Log.class.getDeclaredField("warnWriter"); + c = Log.class.getDeclaredField("noticeWriter"); + d = Log.class.getDeclaredField("dumpOnError"); + e = Log.class.getDeclaredField("promptOnError"); + f = Log.class.getDeclaredField("diagListener"); + z = false; + a.setAccessible(true); + b.setAccessible(true); + c.setAccessible(true); + d.setAccessible(true); + e.setAccessible(true); + f.setAccessible(true); + } catch (Exception x) { + z = true; + } + + errWriterField = a; + warnWriterField = b; + noticeWriterField = c; + dumpOnErrorField = d; + promptOnErrorField = e; + diagnosticListenerField = f; + dontBother = z; + } + + LogDisabler(Context context) { + this.log = Log.instance(context); + this.context = context; + } + + boolean disableLoggers() { + contextDiagnosticListener = context.get(DiagnosticListener.class); + context.put(DiagnosticListener.class, (DiagnosticListener<?>) null); + if (dontBother) return false; + boolean dontBotherInstance = false; + + PrintWriter dummyWriter = new PrintWriter(new OutputStream() { + @Override public void write(int b) throws IOException { + // Do nothing on purpose + } + }); + + if (!dontBotherInstance) try { + errWriter = (PrintWriter) errWriterField.get(log); + errWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + warnWriter = (PrintWriter) warnWriterField.get(log); + warnWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + noticeWriter = (PrintWriter) noticeWriterField.get(log); + noticeWriterField.set(log, dummyWriter); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + dumpOnError = (Boolean) dumpOnErrorField.get(log); + dumpOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + promptOnError = (Boolean) promptOnErrorField.get(log); + promptOnErrorField.set(log, false); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (!dontBotherInstance) try { + logDiagnosticListener = (DiagnosticListener<?>) diagnosticListenerField.get(log); + diagnosticListenerField.set(log, null); + } catch (Exception e) { + dontBotherInstance = true; + } + + if (dontBotherInstance) enableLoggers(); + return !dontBotherInstance; + } + + void enableLoggers() { + if (contextDiagnosticListener != null) { + context.put(DiagnosticListener.class, contextDiagnosticListener); + contextDiagnosticListener = null; + } + if (errWriter != null) try { + errWriterField.set(log, errWriter); + errWriter = null; + } catch (Exception e) {} + + if (warnWriter != null) try { + warnWriterField.set(log, warnWriter); + warnWriter = null; + } catch (Exception e) {} + + if (noticeWriter != null) try { + noticeWriterField.set(log, noticeWriter); + noticeWriter = null; + } catch (Exception e) {} + + if (dumpOnError != null) try { + dumpOnErrorField.set(log, dumpOnError); + dumpOnError = null; + } catch (Exception e) {} + + if (promptOnError != null) try { + promptOnErrorField.set(log, promptOnError); + promptOnError = null; + } catch (Exception e) {} + + if (logDiagnosticListener != null) try { + diagnosticListenerField.set(log, logDiagnosticListener); + logDiagnosticListener = null; + } catch (Exception e) {} + } + } + + private static final class EnvFinder extends JCTree.Visitor { + private Env<AttrContext> env = null; + private Enter enter; + private MemberEnter memberEnter; + private JCTree copyAt = null; + + EnvFinder(Context context) { + this.enter = Enter.instance(context); + this.memberEnter = MemberEnter.instance(context); + } + + Env<AttrContext> get() { + return env; + } + + JCTree copyAt() { + return copyAt; + } + + @Override public void visitTopLevel(JCCompilationUnit tree) { + if (copyAt != null) return; + env = enter.getTopLevelEnv(tree); + } + + @Override public void visitClassDef(JCClassDecl tree) { + if (copyAt != null) return; + // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. + //env = enter.classEnv((JCClassDecl) tree, env); + env = enter.getClassEnv(tree.sym); + } + + @Override public void visitMethodDef(JCMethodDecl tree) { + if (copyAt != null) return; + env = memberEnter.getMethodEnv(tree, env); + copyAt = tree; + } + + public void visitVarDef(JCVariableDecl tree) { + if (copyAt != null) return; + env = memberEnter.getInitEnv(tree, env); + copyAt = tree; + } + + @Override public void visitBlock(JCBlock tree) { + if (copyAt != null) return; + copyAt = tree; + } + } + +// /** +// * The {@code Env} object primarily tracks legal symbol names. i.e. its the lexical scope. To build it, we need to go from the top and drill down to the current node, +// * updating the {@code Env} object at each step. This TreeVisitor does that. Requires {@code enterTrees} to be called first (this is done before processors run, normally). +// */ +// private static final class EnvChainer extends JCTree.Visitor { +// private Env<AttrContext> env = null; +// private Enter enter; +// private MemberEnter memberEnter; +// private Attr attr; +// private JCTree target; +// private boolean blocksAreInitializers; +// +// EnvChainer(Context context) { +// this.enter = Enter.instance(context); +// this.memberEnter = MemberEnter.instance(context); +// this.attr = Attr.instance(context); +// } +// +// Env<AttrContext> get() { +// return env; +// } +// +// @Override public void visitTopLevel(JCCompilationUnit tree) { +// env = enter.getTopLevelEnv(tree); +// } +// +// @Override public void visitClassDef(JCClassDecl tree) { +// // The commented out one leaves the 'lint' field unset, which causes NPEs during attrib. So, we use the other one. +// //env = enter.classEnv((JCClassDecl) tree, env); +// env = enter.getClassEnv(tree.sym); +// blocksAreInitializers = true; +// } +// +// @Override public void visitMethodDef(JCMethodDecl tree) { +// env = memberEnter.getMethodEnv(tree, env); +// blocksAreInitializers = false; +// if (tree.body != null) visitBlock(tree.body); +// } +// +// @Override public void visitBlock(JCBlock tree) { +// if (blocksAreInitializers) attr.attribStat(tree, env); +// for (JCStatement stat : tree.stats) { +// if (stat == target) return; +// attr.attribStat(stat, env); +// } +// } +// +// @Override public void visitTree(JCTree tree) { +// // Do nothing +// } +// +// public void setTarget(JCTree target) { +// this.target = target; +// } +// }; + + public Map<JCTree, JCTree> resolve(JavacNode node) { + ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); + + { + JavacNode n = node; + while (n != null) { + stack.push(n.get()); + n = n.up(); + } + } + + logDisabler.disableLoggers(); + try { + EnvFinder finder = new EnvFinder(node.getContext()); + while (!stack.isEmpty()) stack.pop().accept(finder); + + TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node); + JCTree copy = mirrorMaker.copy(finder.copyAt()); + + attrib(copy, finder.get()); + return mirrorMaker.getOriginalToCopyMap(); + } finally { + logDisabler.enableLoggers(); + } + } + + private void attrib(JCTree tree, Env<AttrContext> env) { + if (tree instanceof JCBlock) attr.attribStat(tree, env); + else if (tree instanceof JCMethodDecl) attr.attribStat(((JCMethodDecl)tree).body, env); + else if (tree instanceof JCVariableDecl) attr.attribStat(tree, env); + else throw new IllegalStateException("Called with something that isn't a block, method decl, or variable decl"); + } + +// public void resolveUpTo(JavacNode statementNode) { +// ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); +// +// { +// JavacNode n = statementNode; +// while (n != null) { +// stack.push(n.get()); +// n = n.up(); +// } +// } +// +// logDisabler.disableLoggers(); +// try { +// JCTree tree = stack.isEmpty() ? null : stack.pop(); +// while (!stack.isEmpty()) { +// JCTree target = stack.pop(); +// envChainer.setTarget(target); +// tree.accept(envChainer); +// tree = target; +// } +// if (tree != null) { +// envChainer.setTarget(null); +// tree.accept(envChainer); +// } +// +//// System.out.println("ATTRIBSTAT: " + attr.attribStat(statementNode.get(), envChainer.get())); +//// if (statementNode.get() instanceof JCVariableDecl) { +//// System.out.println("Force-tribbing expr"); +//// JCExpression init = ((JCVariableDecl)statementNode.get()).init; +//// System.out.println("ATTRIBEXPR: " + attr.attribExpr(init, envChainer.get(), Type.noType)); +//// System.out.println("TYPE: " + ((JCVariableDecl)statementNode.get()).init.type); +//// } +// } finally { +// logDisabler.enableLoggers(); +// } +// } +// +// public void resolveExpr(JCExpression expr) { +// logDisabler.disableLoggers(); +// try { +// attr.attribExpr(expr, envChainer.get(), Type.noType); +// } finally { +// logDisabler.enableLoggers(); +// } +// } + + public static class TypeNotConvertibleException extends Exception { + public TypeNotConvertibleException(String msg) { + super(msg); + } + } + + public static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast) throws TypeNotConvertibleException { + return typeToJCTree(type, maker, ast, false); + } + + public static JCExpression createJavaLangObject(TreeMaker maker, JavacAST ast) { + JCExpression out = maker.Ident(ast.toName("java")); + out = maker.Select(out, ast.toName("lang")); + out = maker.Select(out, ast.toName("Object")); + return out; + } + + private static JCExpression typeToJCTree(Type type, TreeMaker maker, JavacAST ast, boolean allowCompound) 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.isPrimitive()) return primitiveToJCTree(type.getKind(), maker); + if (type.isErroneous()) throw new TypeNotConvertibleException("Type cannot be resolved"); + + TypeSymbol symbol = type.asElement(); + List<Type> generics = type.getTypeArguments(); + + JCExpression replacement = null; + + if (symbol == null) throw new TypeNotConvertibleException("Null or compound type"); + + if (symbol.name.len == 0) { + // Anonymous inner class + if (type instanceof ClassType) { + List<Type> ifaces = ((ClassType)type).interfaces_field; + Type supertype = ((ClassType)type).supertype_field; + if (ifaces != null && ifaces.length() == 1) { + return typeToJCTree(ifaces.get(0), maker, ast, allowCompound); + } + if (supertype != null) return typeToJCTree(supertype, maker, ast, allowCompound); + } + throw new TypeNotConvertibleException("Anonymous inner class"); + } + + if (type instanceof CapturedType) { + if (allowCompound) { + if (type.getLowerBound() == null || type.getLowerBound().tag == TypeTags.BOT) { + if (type.getUpperBound().toString().equals("java.lang.Object")) { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); + } + return maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), typeToJCTree(type.getUpperBound(), maker, ast, false)); + } else { + return maker.Wildcard(maker.TypeBoundKind(BoundKind.SUPER), typeToJCTree(type.getLowerBound(), maker, ast, false)); + } + } + if (type.getUpperBound() != null) { + return typeToJCTree(type.getUpperBound(), maker, ast, allowCompound); + } + + return createJavaLangObject(maker, ast); + } + + String qName = symbol.getQualifiedName().toString(); + if (qName.isEmpty()) throw new TypeNotConvertibleException("unknown type"); + if (qName.startsWith("<")) throw new TypeNotConvertibleException(qName); + String[] baseNames = symbol.getQualifiedName().toString().split("\\."); + replacement = maker.Ident(ast.toName(baseNames[0])); + for (int i = 1; i < baseNames.length; i++) { + replacement = maker.Select(replacement, ast.toName(baseNames[i])); + } + + if (generics != null && !generics.isEmpty()) { + List<JCExpression> args = List.nil(); + for (Type t : generics) args = args.append(typeToJCTree(t, maker, ast, true)); + replacement = maker.TypeApply(replacement, args); + } + + return replacement; + } + + private static JCExpression primitiveToJCTree(TypeKind kind, TreeMaker maker) throws TypeNotConvertibleException { + switch (kind) { + case BYTE: + return maker.TypeIdent(TypeTags.BYTE); + case CHAR: + return maker.TypeIdent(TypeTags.CHAR); + case SHORT: + return maker.TypeIdent(TypeTags.SHORT); + case INT: + return maker.TypeIdent(TypeTags.INT); + case LONG: + return maker.TypeIdent(TypeTags.LONG); + case FLOAT: + return maker.TypeIdent(TypeTags.FLOAT); + case DOUBLE: + return maker.TypeIdent(TypeTags.DOUBLE); + case BOOLEAN: + return maker.TypeIdent(TypeTags.BOOLEAN); + case VOID: + return maker.TypeIdent(TypeTags.VOID); + case NULL: + case NONE: + case OTHER: + default: + throw new TypeNotConvertibleException("Nulltype"); + } + } +} diff --git a/src/core/lombok/javac/TreeMirrorMaker.java b/src/core/lombok/javac/TreeMirrorMaker.java new file mode 100644 index 00000000..1c0b9311 --- /dev/null +++ b/src/core/lombok/javac/TreeMirrorMaker.java @@ -0,0 +1,50 @@ +package lombok.javac; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeCopier; +import com.sun.tools.javac.util.List; + +public class TreeMirrorMaker extends TreeCopier<Void> { + private final IdentityHashMap<JCTree, JCTree> originalToCopy = new IdentityHashMap<JCTree, JCTree>(); + + public TreeMirrorMaker(JavacNode node) { + super(node.getTreeMaker()); + } + + @Override public <T extends JCTree> T copy(T original) { + T copy = super.copy(original); + originalToCopy.put(original, copy); + return copy; + } + + @Override public <T extends JCTree> T copy(T original, Void p) { + T copy = super.copy(original, p); + originalToCopy.put(original, copy); + return copy; + } + + @Override public <T extends JCTree> List<T> copy(List<T> originals) { + List<T> copies = super.copy(originals); + Iterator<T> it1 = originals.iterator(); + Iterator<T> it2 = copies.iterator(); + while (it1.hasNext()) originalToCopy.put(it1.next(), it2.next()); + return copies; + } + + @Override public <T extends JCTree> List<T> copy(List<T> originals, Void p) { + List<T> copies = super.copy(originals, p); + Iterator<T> it1 = originals.iterator(); + Iterator<T> it2 = copies.iterator(); + while (it1.hasNext()) originalToCopy.put(it1.next(), it2.next()); + return copies; + } + + public Map<JCTree, JCTree> getOriginalToCopyMap() { + return Collections.unmodifiableMap(originalToCopy); + } +} diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java new file mode 100644 index 00000000..a6f22093 --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2010 Reinier Zwitserloot and Roel Spilker. + * + * 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.JavacASTAdapter; +import lombok.javac.JavacASTVisitor; +import lombok.javac.JavacNode; +import lombok.javac.JavacResolution; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; + +@ProviderFor(JavacASTVisitor.class) +public class HandleVal extends JavacASTAdapter { + @Override public boolean isResolutionBased() { + return true; + } + + @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) { + if (local.vartype != null && local.vartype.toString().equals("val")) { + JCExpression rhsOfEnhancedForLoop = null; + if (local.init == null) { + JCTree parentRaw = localNode.directUp().get(); + if (parentRaw instanceof JCEnhancedForLoop) { + JCEnhancedForLoop efl = (JCEnhancedForLoop) parentRaw; + if (efl.var == local) rhsOfEnhancedForLoop = efl.expr; + } + } + + if (rhsOfEnhancedForLoop == null && local.init == null) { + localNode.addError("'val' on a local variable requires an initializer expression"); + return; + } + + local.mods.flags |= Flags.FINAL; + JCExpression oldVarType = local.vartype; + local.vartype = JavacResolution.createJavaLangObject(localNode.getTreeMaker(), localNode.getAst()); + + Type type; + try { + if (rhsOfEnhancedForLoop == null) { + if (local.init.type == null) { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + type = ((JCExpression) resolver.resolve(localNode).get(local.init)).type; + } else { + type = local.init.type; + } + } else { + if (rhsOfEnhancedForLoop.type == null) { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + type = ((JCExpression) resolver.resolve(localNode.directUp()).get(rhsOfEnhancedForLoop)).type; + } else { + type = rhsOfEnhancedForLoop.type; + } + } + + try { + JCExpression replacement; + + if (rhsOfEnhancedForLoop != null) { + localNode.addError("For now 'val' cannot be used in enhanced for loops. Fixing this is a high priority lombok issue though!"); + return; + // TODO: Fix enhanced for loops - then uncomment a bunch of lines in test/transform/resource/*/ValInFor.java + } + replacement = JavacResolution.typeToJCTree(type, localNode.getTreeMaker(), localNode.getAst()); + if (replacement != null) local.vartype = replacement; + else local.vartype = oldVarType; + } catch (JavacResolution.TypeNotConvertibleException e) { + localNode.addError("Cannot use 'val' here because initializer expression does not have a representable type: " + e.getMessage()); + local.vartype = oldVarType; + } + } catch (RuntimeException e) { + local.vartype = oldVarType; + throw e; + } + } + // TODO search for val decls in either kind of for. + } +} diff --git a/src/delombok/lombok/delombok/CommentCollectingScanner.java b/src/delombok/lombok/delombok/CommentCollectingScanner.java index 2552cbf7..c930bc62 100644 --- a/src/delombok/lombok/delombok/CommentCollectingScanner.java +++ b/src/delombok/lombok/delombok/CommentCollectingScanner.java @@ -25,7 +25,7 @@ import java.nio.CharBuffer; import lombok.delombok.Comment.EndConnection; import lombok.delombok.Comment.StartConnection; -import lombok.delombok.CommentPreservingParser.Comments; +import lombok.delombok.Delombok.Comments; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.Context; diff --git a/src/delombok/lombok/delombok/CommentPreservingParser.java b/src/delombok/lombok/delombok/CommentPreservingParser.java deleted file mode 100644 index 87c02dcd..00000000 --- a/src/delombok/lombok/delombok/CommentPreservingParser.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. - * - * 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.delombok; - -import java.io.IOException; -import java.io.Writer; -import java.util.Collections; -import java.util.Date; - -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.tools.DiagnosticListener; -import javax.tools.JavaFileObject; -import javax.tools.Diagnostic.Kind; - -import lombok.javac.DeleteLombokAnnotations; -import lombok.javac.JavacTransformer; - -import com.sun.tools.javac.main.JavaCompiler; -import com.sun.tools.javac.main.OptionName; -import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; -import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.List; -import com.sun.tools.javac.util.Options; - -public class CommentPreservingParser { - private final String encoding; - private boolean deleteLombokAnnotations = false; - private DiagnosticListener<JavaFileObject> diagnostics = null; - - public CommentPreservingParser() { - this("utf-8"); - } - - public CommentPreservingParser(String encoding) { - this.encoding = encoding; - } - - public void setDeleteLombokAnnotations(boolean deleteLombokAnnotations) { - this.deleteLombokAnnotations = deleteLombokAnnotations; - } - - public void setDiagnosticsListener(DiagnosticListener<JavaFileObject> diagnostics) { - this.diagnostics = diagnostics; - } - - public ParseResult parse(JavaFileObject source, boolean forceProcessing) throws IOException { - return doParse(source, forceProcessing); - } - - public ParseResult parse(String fileName, boolean forceProcessing) throws IOException { - return doParse(fileName, forceProcessing); - } - - private ParseResult doParse(Object source, boolean forceProcessing) throws IOException { - Context context = new Context(); - - Options.instance(context).put(OptionName.ENCODING, encoding); - - if (diagnostics != null) context.put(DiagnosticListener.class, diagnostics); - - CommentCollectingScanner.Factory.preRegister(context); - - JavaCompiler compiler = new JavaCompiler(context) { - @Override - protected boolean keepComments() { - return true; - } - }; - - compiler.genEndPos = true; - - Comments comments = new Comments(); - context.put(Comments.class, comments); - if (deleteLombokAnnotations) context.put(DeleteLombokAnnotations.class, new DeleteLombokAnnotations(true)); - - comments.comments = List.nil(); - - JCCompilationUnit cu; - if (source instanceof JavaFileObject) { - cu = compiler.parse((JavaFileObject) source); - } else { - @SuppressWarnings("deprecation") - JCCompilationUnit unit = compiler.parse((String)source); - cu = unit; - } - - boolean changed = new JavacTransformer(messager).transform(context, Collections.singleton(cu)); - return new ParseResult(comments.comments, cu, forceProcessing || changed); - } - - private static final Messager messager = new Messager() { - @Override public void printMessage(Kind kind, CharSequence msg) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a) { - System.out.printf("%s: %s\n", kind, msg); - } - - @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) { - System.out.printf("%s: %s\n", kind, msg); - } - }; - - static class Comments { - List<Comment> comments = List.nil(); - - void add(Comment comment) { - comments = comments.append(comment); - } - } - - public static class ParseResult { - private final List<Comment> comments; - private final JCCompilationUnit compilationUnit; - private final boolean changed; - - private ParseResult(List<Comment> comments, JCCompilationUnit compilationUnit, boolean changed) { - this.comments = comments; - this.compilationUnit = compilationUnit; - this.changed = changed; - } - - public void print(Writer out) throws IOException { - if (!changed) { - JavaFileObject sourceFile = compilationUnit.getSourceFile(); - if (sourceFile != null) { - out.write(sourceFile.getCharContent(true).toString()); - return; - } - } - - out.write("// Generated by delombok at "); - out.write(String.valueOf(new Date())); - out.write(System.getProperty("line.separator")); - - compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments)); - } - - public boolean isChanged() { - return changed; - } - } -} diff --git a/src/delombok/lombok/delombok/Delombok.java b/src/delombok/lombok/delombok/Delombok.java index 56994f78..6866d97f 100644 --- a/src/delombok/lombok/delombok/Delombok.java +++ b/src/delombok/lombok/delombok/Delombok.java @@ -30,16 +30,33 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.Writer; +import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; import javax.tools.DiagnosticListener; import javax.tools.JavaFileObject; +import javax.tools.Diagnostic.Kind; -import lombok.delombok.CommentPreservingParser.ParseResult; +import lombok.javac.DeleteLombokAnnotations; +import lombok.javac.JavacTransformer; +import com.sun.tools.javac.main.JavaCompiler; +import com.sun.tools.javac.main.OptionName; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Options; import com.zwitserloot.cmdreader.CmdReader; import com.zwitserloot.cmdreader.Description; import com.zwitserloot.cmdreader.Excludes; @@ -50,16 +67,24 @@ import com.zwitserloot.cmdreader.Shorthand; public class Delombok { private Charset charset = Charset.defaultCharset(); - private CommentPreservingParser parser = new CommentPreservingParser(); + private Context context = new Context(); + private Writer presetWriter; - { - parser.setDeleteLombokAnnotations(true); + public void setWriter(Writer writer) { + this.presetWriter = writer; + } + + public Delombok() { + context.put(DeleteLombokAnnotations.class, new DeleteLombokAnnotations(true)); } private PrintStream feedback = System.err; private boolean verbose; private boolean noCopy; private boolean force = false; + private String classpath, sourcepath; + private LinkedHashMap<File, File> fileToBase = new LinkedHashMap<File, File>(); + private List<File> filesToParse = new ArrayList<File>(); /** If null, output to standard out. */ private File output = null; @@ -88,6 +113,14 @@ public class Delombok { @Mandatory(onlyIfNot={"print", "help"}) private String target; + @Shorthand("c") + @Description("Classpath (analogous to javac -cp option)") + private String classpath; + + @Shorthand("s") + @Description("Sourcepath (analogous to javac -sourcepath option)") + private String sourcepath; + @Description("Files to delombok. Provide either a file, or a directory. If you use a directory, all files in it (recursive) are delombok-ed") @Sequential private List<String> input = new ArrayList<String>(); @@ -138,21 +171,29 @@ public class Delombok { if (args.verbose) delombok.setVerbose(true); if (args.nocopy) delombok.setNoCopy(true); - if (args.print) delombok.setOutputToStandardOut(); - else delombok.setOutput(new File(args.target)); + if (args.print) { + delombok.setOutputToStandardOut(); + } else { + delombok.setOutput(new File(args.target)); + } + + if (args.classpath != null) delombok.setClasspath(args.classpath); + if (args.sourcepath != null) delombok.setSourcepath(args.sourcepath); for (String in : args.input) { try { File f = new File(in); if (f.isFile()) { - delombok.delombok(f.getParentFile(), f.getName()); + delombok.addFile(f.getParentFile(), f.getName()); } else if (f.isDirectory()) { - delombok.delombok(f); + delombok.addDirectory(f); } else if (!f.exists()) { if (!args.quiet) System.err.println("WARNING: does not exist - skipping: " + f); } else { if (!args.quiet) System.err.println("WARNING: not a standard file or directory - skipping: " + f); } + + delombok.delombok(); } catch (Exception e) { if (!args.quiet) { String msg = e.getMessage(); @@ -168,12 +209,12 @@ public class Delombok { } public void setCharset(String charsetName) throws UnsupportedCharsetException { + charset = Charset.forName(charsetName); } - public void setDiagnosticsListener(DiagnosticListener<JavaFileObject> diagnostics) { - parser.setDiagnosticsListener(diagnostics); + if (diagnostics != null) context.put(DiagnosticListener.class, diagnostics); } public void setForceProcess(boolean force) { @@ -184,6 +225,14 @@ public class Delombok { this.feedback = feedback; } + public void setClasspath(String classpath) { + this.classpath = classpath; + } + + public void setSourcepath(String sourcepath) { + this.sourcepath = sourcepath; + } + public void setVerbose(boolean verbose) { this.verbose = verbose; } @@ -204,15 +253,15 @@ public class Delombok { this.output = null; } - public void delombok(File base) throws IOException { - delombok0(false, base, "", 0); + public void addDirectory(File base) throws IOException { + addDirectory0(false, base, "", 0); } - public void process(boolean copy, File base, String name) throws IOException { + public void addDirectory1(boolean copy, File base, String name) throws IOException { File f = new File(base, name); if (f.isFile()) { String extension = getExtension(f); - if (extension.equals("java")) delombok(base, name); + if (extension.equals("java")) addFile(base, name); else if (extension.equals("class")) skipClass(name); else copy(copy, base, name); } else if (!f.exists()) { @@ -222,7 +271,7 @@ public class Delombok { } } - private void delombok0(boolean inHiddenDir, File base, String suffix, int loop) throws IOException { + private void addDirectory0(boolean inHiddenDir, File base, String suffix, int loop) throws IOException { File dir = suffix.isEmpty() ? base : new File(base, suffix); if (dir.isDirectory()) { @@ -236,7 +285,7 @@ public class Delombok { feedback.printf("Only processing java files (not copying non-java files) in %s because it's a hidden directory.\n", canonical(dir)); } for (File f : list) { - delombok0(inHiddenDir || thisDirIsHidden, base, suffix + (suffix.isEmpty() ? "" : File.separator) + f.getName(), loop + 1); + addDirectory0(inHiddenDir || thisDirIsHidden, base, suffix + (suffix.isEmpty() ? "" : File.separator) + f.getName(), loop + 1); } } else { if (!thisDirIsHidden && !noCopy && !inHiddenDir && output != null && !suffix.isEmpty()) { @@ -247,7 +296,7 @@ public class Delombok { } } } else { - process(!inHiddenDir && !noCopy, base, suffix); + addDirectory1(!inHiddenDir && !noCopy, base, suffix); } } @@ -288,34 +337,96 @@ public class Delombok { } } - public void delombok(JavaFileObject file, Writer writer) throws IOException { - ParseResult result = parser.parse(file, force); - result.print(writer); + public void addFile(File base, String fileName) throws IOException { + if (output != null && canonical(base).equals(canonical(output))) throw new IOException( + "DELOMBOK: Output file and input file refer to the same filesystem location. Specify a separate path for output."); + + File f = new File(base, fileName); + filesToParse.add(f); + fileToBase.put(f, base); } - public void delombok(String file, Writer writer) throws IOException { - ParseResult result = parser.parse(file, force); - result.print(writer); + private static <T> com.sun.tools.javac.util.List<T> toJavacList(List<T> list) { + com.sun.tools.javac.util.List<T> out = com.sun.tools.javac.util.List.nil(); + ListIterator<T> li = list.listIterator(list.size()); + while (li.hasPrevious()) out = out.prepend(li.previous()); + return out; } - public void delombok(File base, String fileName) throws IOException { - if (output != null && canonical(base).equals(canonical(output))) throw new IOException( - "DELOMBOK: Output file and input file refer to the same filesystem location. Specify a separate path for output."); + public boolean delombok() throws IOException { + Options options = Options.instance(context); + options.put(OptionName.ENCODING, charset.name()); + if (classpath != null) options.put(OptionName.CLASSPATH, classpath); + if (sourcepath != null) options.put(OptionName.SOURCEPATH, sourcepath); + CommentCollectingScanner.Factory.preRegister(context); - ParseResult result = parser.parse(new File(base, fileName).getAbsolutePath(), force); + JavaCompiler compiler = new JavaCompiler(context); + compiler.keepComments = true; + compiler.genEndPos = true; - if (verbose) feedback.printf("File: %s [%s]\n", fileName, result.isChanged() ? "delombok-ed" : "unchanged"); + List<JCCompilationUnit> roots = new ArrayList<JCCompilationUnit>(); + Map<JCCompilationUnit, Comments> commentsMap = new IdentityHashMap<JCCompilationUnit, Comments>(); + Map<JCCompilationUnit, File> baseMap = new IdentityHashMap<JCCompilationUnit, File>(); + for (File fileToParse : filesToParse) { + Comments comments = new Comments(); + context.put(Comments.class, comments); + + @SuppressWarnings("deprecation") + JCCompilationUnit unit = compiler.parse(fileToParse.getAbsolutePath()); + + commentsMap.put(unit, comments); + baseMap.put(unit, fileToBase.get(fileToParse)); + roots.add(unit); + } - Writer rawWriter = output == null ? createStandardOutWriter() : createFileWriter(output, fileName); - BufferedWriter writer = new BufferedWriter(rawWriter); + if (compiler.errorCount() > 0) return false; + compiler.enterTrees(toJavacList(roots)); - try { - result.print(writer); - } finally { - writer.close(); + for (JCCompilationUnit unit : roots) { + boolean changed = new JavacTransformer(messager).transform(context, Collections.singleton(unit)); + DelombokResult result = new DelombokResult(commentsMap.get(unit).comments, unit, force || changed); + if (verbose) feedback.printf("File: %s [%s]\n", unit.sourcefile.getName(), result.isChanged() ? "delomboked" : "unchanged"); + Writer rawWriter; + if (presetWriter != null) rawWriter = presetWriter; + else if (output == null) rawWriter = createStandardOutWriter(); + else rawWriter = createFileWriter(output, baseMap.get(unit), unit.sourcefile.toUri()); + BufferedWriter writer = new BufferedWriter(rawWriter); + try { + result.print(writer); + } finally { + writer.close(); + } + } + + return true; + } + + public static class Comments { + public com.sun.tools.javac.util.List<Comment> comments = com.sun.tools.javac.util.List.nil(); + + void add(Comment comment) { + comments = comments.append(comment); } } + private static final Messager messager = new Messager() { + @Override public void printMessage(Kind kind, CharSequence msg) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a) { + System.out.printf("%s: %s\n", kind, msg); + } + + @Override public void printMessage(Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) { + System.out.printf("%s: %s\n", kind, msg); + } + }; + private static String canonical(File dir) { try { return dir.getCanonicalPath(); @@ -330,8 +441,15 @@ public class Delombok { return idx == -1 ? "" : name.substring(idx+1); } - private Writer createFileWriter(File base, String fileName) throws IOException { - File outFile = new File(base, fileName); + private Writer createFileWriter(File outBase, File inBase, URI file) throws IOException { + URI relative = inBase.toURI().relativize(file); + File outFile; + if (relative.isAbsolute()) { + outFile = new File(outBase, new File(relative).getName()); + } else { + outFile = new File(outBase, relative.getPath()); + } + outFile.getParentFile().mkdirs(); FileOutputStream out = new FileOutputStream(outFile); return createUnicodeEscapeWriter(out); diff --git a/src/delombok/lombok/delombok/DelombokResult.java b/src/delombok/lombok/delombok/DelombokResult.java new file mode 100644 index 00000000..717a3bf1 --- /dev/null +++ b/src/delombok/lombok/delombok/DelombokResult.java @@ -0,0 +1,62 @@ +/* + * Copyright © 2009-2010 Reinier Zwitserloot and Roel Spilker. + * + * 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.delombok; + +import java.io.IOException; +import java.io.Writer; +import java.util.Date; + +import javax.tools.JavaFileObject; + +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.util.List; + +public class DelombokResult { + private final List<Comment> comments; + private final JCCompilationUnit compilationUnit; + private final boolean changed; + + public DelombokResult(List<Comment> comments, JCCompilationUnit compilationUnit, boolean changed) { + this.comments = comments; + this.compilationUnit = compilationUnit; + this.changed = changed; + } + + public void print(Writer out) throws IOException { + if (!changed) { + JavaFileObject sourceFile = compilationUnit.getSourceFile(); + if (sourceFile != null) { + out.write(sourceFile.getCharContent(true).toString()); + return; + } + } + + out.write("// Generated by delombok at "); + out.write(String.valueOf(new Date())); + out.write(System.getProperty("line.separator")); + + compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments)); + } + + public boolean isChanged() { + return changed; + } +}
\ No newline at end of file diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 55c0e1bc..16c06f50 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -670,9 +670,11 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitMethodDef(JCMethodDecl tree) { try { + boolean isConstructor = tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6); // when producing source output, omit anonymous constructors - if (tree.name == tree.name.table.fromChars("<init>".toCharArray(), 0, 6) && - enclClassName == null) return; + if (isConstructor && enclClassName == null) return; + boolean isGeneratedConstructor = isConstructor && ((tree.mods.flags & Flags.GENERATEDCONSTR) != 0); + if (isGeneratedConstructor) return; println(); align(); printDocComment(tree); printExpr(tree.mods); @@ -1442,9 +1444,13 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { print("@"); printExpr(tree.annotationType); if (tree.args.nonEmpty()) { - print("("); - printExprs(tree.args); - print(")"); + print("("); + if (tree.args.length() == 1 && tree.args.get(0) instanceof JCAssign) { + JCExpression lhs = ((JCAssign)tree.args.get(0)).lhs; + if (lhs instanceof JCIdent && ((JCIdent)lhs).name.toString().equals("value")) tree.args = List.of(((JCAssign)tree.args.get(0)).rhs); + } + printExprs(tree.args); + print(")"); } } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/src/delombok/lombok/delombok/ant/DelombokTask.java b/src/delombok/lombok/delombok/ant/DelombokTask.java index 4a40b874..40c3c63e 100644 --- a/src/delombok/lombok/delombok/ant/DelombokTask.java +++ b/src/delombok/lombok/delombok/ant/DelombokTask.java @@ -32,14 +32,55 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; import org.apache.tools.ant.types.resources.FileResource; public class DelombokTask extends Task { private File fromDir, toDir; + private Path classpath; + private Path sourcepath; private boolean verbose; private String encoding; private Path path; + public void setClasspath(Path classpath) { + if (this.classpath == null) { + this.classpath = classpath; + } else { + this.classpath.append(classpath); + } + } + + public Path createClasspath() { + if (classpath == null) { + classpath = new Path(getProject()); + } + return classpath.createPath(); + } + + public void setClasspathRef(Reference r) { + createClasspath().setRefid(r); + } + + public void setSourcepath(Path sourcepath) { + if (this.sourcepath == null) { + this.sourcepath = sourcepath; + } else { + this.sourcepath.append(sourcepath); + } + } + + public Path createSourcepath() { + if (sourcepath == null) { + sourcepath = new Path(getProject()); + } + return sourcepath.createPath(); + } + + public void setSourcepathRef(Reference r) { + createSourcepath().setRefid(r); + } + public void setFrom(File dir) { this.fromDir = dir; } @@ -75,9 +116,12 @@ public class DelombokTask extends Task { throw new BuildException("Unknown charset: " + encoding, getLocation()); } + if (classpath != null) delombok.setClasspath(classpath.toString()); + if (sourcepath != null) delombok.setSourcepath(sourcepath.toString()); + delombok.setOutput(toDir); try { - if (fromDir != null) delombok.delombok(fromDir); + if (fromDir != null) delombok.addDirectory(fromDir); else { Iterator<?> it = path.iterator(); while (it.hasNext()) { @@ -85,12 +129,13 @@ public class DelombokTask extends Task { File baseDir = fileResource.getBaseDir(); if (baseDir == null) { File file = fileResource.getFile(); - delombok.process(false, file.getParentFile(), file.getName()); + delombok.addFile(file.getParentFile(), file.getName()); } else { - delombok.process(false, baseDir, fileResource.getName()); + delombok.addFile(baseDir, fileResource.getName()); } } } + delombok.delombok(); } catch (IOException e) { throw new BuildException("I/O problem during delombok", e, getLocation()); } diff --git a/test/core/src/lombok/AbstractRunTests.java b/test/core/src/lombok/AbstractRunTests.java index 4241646b..7d5992be 100644 --- a/test/core/src/lombok/AbstractRunTests.java +++ b/test/core/src/lombok/AbstractRunTests.java @@ -37,12 +37,12 @@ public abstract class AbstractRunTests { public void compareFile(DirectoryRunner.TestParams params, File file) throws Throwable { StringBuilder messages = new StringBuilder(); - StringWriter result = new StringWriter(); - transformCode(messages, result, file); + StringWriter writer = new StringWriter(); + transformCode(messages, writer, file); compare( file.getName(), readFile(params.getAfterDirectory(), file, false), - result.toString(), + writer.toString(), readFile(params.getMessagesDirectory(), file, true), messages.toString(), params.printErrors()); @@ -111,6 +111,9 @@ public abstract class AbstractRunTests { private static void compareContent(String name, String expectedFile, String actualFile) { String[] expectedLines = expectedFile.split("(\\r?\\n)"); String[] actualLines = actualFile.split("(\\r?\\n)"); + if (expectedLines[0].startsWith("// Generated by delombok at ")) { + expectedLines[0] = ""; + } if (actualLines[0].startsWith("// Generated by delombok at ")) { actualLines[0] = ""; } diff --git a/test/core/src/lombok/RunTestsViaDelombok.java b/test/core/src/lombok/RunTestsViaDelombok.java index 50fad33e..59a0ee89 100644 --- a/test/core/src/lombok/RunTestsViaDelombok.java +++ b/test/core/src/lombok/RunTestsViaDelombok.java @@ -51,6 +51,9 @@ public class RunTestsViaDelombok extends AbstractRunTests { } }); - delombok.delombok(file.getAbsolutePath(), result); + delombok.addFile(file.getParentFile(), file.getName()); + delombok.setSourcepath(file.getParentFile().getAbsolutePath()); + delombok.setWriter(result); + delombok.delombok(); } } diff --git a/test/pretty/resource/before/Annotation.java b/test/pretty/resource/before/Annotation.java index edd1a5e7..24868acd 100644 --- a/test/pretty/resource/before/Annotation.java +++ b/test/pretty/resource/before/Annotation.java @@ -1,3 +1,4 @@ +//ignore @SuppressWarnings("all") class Annotation { @Override diff --git a/test/transform/resource/after-delombok/ValComplex.java b/test/transform/resource/after-delombok/ValComplex.java new file mode 100644 index 00000000..54ef5c78 --- /dev/null +++ b/test/transform/resource/after-delombok/ValComplex.java @@ -0,0 +1,20 @@ +public class ValComplex { + private ValSimple field = new ValSimple(); + private static final int CONSTANT = 20; + public void testReferencingOtherFiles() { + final java.lang.String shouldBeString = field.method(); + final int shouldBeInt = CONSTANT; + final java.lang.Object lock = new Object(); + synchronized (lock) { + final int field = 20; //Shadowing + final int inner = 10; + switch (field) { + case 5: + final java.lang.String shouldBeString2 = shouldBeString; + final int innerInner = inner; + + } + } + final ValSimple shouldBeValSimple = field; //Unshadowing + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValErrors.java b/test/transform/resource/after-delombok/ValErrors.java new file mode 100644 index 00000000..5ac785ab --- /dev/null +++ b/test/transform/resource/after-delombok/ValErrors.java @@ -0,0 +1,8 @@ +public class ValErrors { + public void nullType() { + final val a = null; + } + public void unresolvableExpression() { + final val c = d; + } +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValInFor.java b/test/transform/resource/after-delombok/ValInFor.java new file mode 100644 index 00000000..d97ce732 --- /dev/null +++ b/test/transform/resource/after-delombok/ValInFor.java @@ -0,0 +1,20 @@ +public class ValInFor { + { + final int x = 10; + final int x2 = -1; + final java.lang.String a = "Hello"; + for (final int y = x, z = x2; y < 20; y++) { + final int q = y; + final int w = z; + final java.lang.String v = a; + } + } +/* public void enhancedFor() { + java.util.List<String> list = java.util.Arrays.asList("Hello, World!"); + for (val shouldBeString : list) { + System.out.println(shouldBeString.toLowerCase()); + val shouldBeString2 = shouldBeString; + } + } +*/ +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValLessSimple.java b/test/transform/resource/after-delombok/ValLessSimple.java new file mode 100644 index 00000000..678b419e --- /dev/null +++ b/test/transform/resource/after-delombok/ValLessSimple.java @@ -0,0 +1,26 @@ +public class ValLessSimple { + private short field2 = 5; + private String method() { + return "method"; + } + private double method2() { + return 2.0; + } + { + System.out.println("Hello"); + final int z = 20; + final int x = 10; + final int a = z; + final short y = field2; + } + private void testVal(String param) { + final java.lang.String fieldV = field; + final int a = 10; + final int b = 20; + { + final java.lang.String methodV = method(); + final java.lang.String foo = fieldV + methodV; + } + } + private String field = "field"; +}
\ No newline at end of file diff --git a/test/transform/resource/after-delombok/ValSimple.java b/test/transform/resource/after-delombok/ValSimple.java new file mode 100644 index 00000000..b2783eac --- /dev/null +++ b/test/transform/resource/after-delombok/ValSimple.java @@ -0,0 +1,24 @@ +public class ValSimple { + private String field = "field"; + private short field2 = 5; + + private String method() { + return "method"; + } + + private double method2() { + return 2.0; + } + + private void testVal(String param) { + final java.lang.String fieldV = field; + final java.lang.String methodV = method(); + final java.lang.String paramV = param; + final java.lang.String valOfVal = fieldV; + final java.lang.String operatorV = fieldV + valOfVal; + final short fieldW = field2; + final double methodW = method2(); + byte localVar = 3; + final int operatorW = fieldW + localVar; + } +} diff --git a/test/transform/resource/after-delombok/ValWeirdTypes.java b/test/transform/resource/after-delombok/ValWeirdTypes.java new file mode 100644 index 00000000..66212906 --- /dev/null +++ b/test/transform/resource/after-delombok/ValWeirdTypes.java @@ -0,0 +1,47 @@ +import java.util.*; +public class ValWeirdTypes<Z> { + private final List<Z> fieldList; + public void testGenerics() { + List<String> list = new ArrayList<String>(); + list.add("Hello, World!"); + final java.lang.String shouldBeString = list.get(0); + final java.util.List<java.lang.String> shouldBeListOfString = list; + final java.util.List<java.lang.String> shouldBeListOfStringToo = Arrays.asList("hello", "world"); + final java.lang.String shouldBeString2 = shouldBeListOfString.get(0); + } + public void testGenericsInference() { + final java.util.List<java.lang.Object> huh = Collections.emptyList(); + final java.util.List<java.lang.Number> huh2 = Collections.<Number>emptyList(); + } + public void testPrimitives() { + final int x = 10; + final long y = 5 + 3L; + } + public void testAnonymousInnerClass() { + final java.lang.Runnable y = new Runnable(){ + public void run() { + } + }; + } + public <T extends Number>void testTypeParams(List<T> param) { + final T t = param.get(0); + final Z z = fieldList.get(0); + final java.util.List<T> k = param; + final java.util.List<Z> y = fieldList; + } + public void testBounds(List<? extends Number> lower, List<? super Number> upper) { + final java.lang.Number a = lower.get(0); + final java.lang.Object b = upper.get(0); + final java.util.List<? extends java.lang.Number> c = lower; + final java.util.List<? super java.lang.Number> d = upper; + List<?> unbound = lower; + final java.util.List<?> e = unbound; + } + public void testCompound() { + final java.util.ArrayList<java.lang.String> a = new ArrayList<String>(); + final java.util.Vector<java.lang.String> b = new Vector<String>(); + final boolean c = 1 < System.currentTimeMillis(); + final java.util.AbstractList<java.lang.String> d = c ? a : b; + java.util.RandomAccess confirm = c ? a : b; + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValComplex.java b/test/transform/resource/before/ValComplex.java new file mode 100644 index 00000000..5f718003 --- /dev/null +++ b/test/transform/resource/before/ValComplex.java @@ -0,0 +1,20 @@ +public class ValComplex { + private ValSimple field = new ValSimple(); + private static final int CONSTANT = 20; + + public void testReferencingOtherFiles() { + val shouldBeString = field.method(); + val shouldBeInt = CONSTANT; + val lock = new Object(); + synchronized (lock) { + val field = 20; //Shadowing + val inner = 10; + switch (field) { + case 5: + val shouldBeString2 = shouldBeString; + val innerInner = inner; + } + } + val shouldBeValSimple = field; //Unshadowing + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValErrors.java b/test/transform/resource/before/ValErrors.java new file mode 100644 index 00000000..742bca6d --- /dev/null +++ b/test/transform/resource/before/ValErrors.java @@ -0,0 +1,9 @@ +public class ValErrors { + public void nullType() { + val a = null; + } + + public void unresolvableExpression() { + val c = d; + } +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValInFor.java b/test/transform/resource/before/ValInFor.java new file mode 100644 index 00000000..af13540e --- /dev/null +++ b/test/transform/resource/before/ValInFor.java @@ -0,0 +1,20 @@ +public class ValInFor { + { + val x = 10; + val x2 = -1; + val a = "Hello"; + for (val y = x, z = x2; y < 20; y++) { + val q = y; + val w = z; + val v = a; + } + } + +/* public void enhancedFor() { + java.util.List<String> list = java.util.Arrays.asList("Hello, World!"); + for (val shouldBeString : list) { + System.out.println(shouldBeString.toLowerCase()); + val shouldBeString2 = shouldBeString; + } + }*/ +}
\ No newline at end of file diff --git a/test/transform/resource/before/ValLessSimple.java b/test/transform/resource/before/ValLessSimple.java new file mode 100644 index 00000000..bae7b73b --- /dev/null +++ b/test/transform/resource/before/ValLessSimple.java @@ -0,0 +1,31 @@ +public class ValLessSimple { + private short field2 = 5; + + private String method() { + return "method"; + } + + private double method2() { + return 2.0; + } + + { + System.out.println("Hello"); + val z = 20; + val x = 10; + val a = z; + val y = field2; + } + + private void testVal(String param) { + val fieldV = field; + val a = 10; + val b = 20; + { + val methodV = method(); + val foo = fieldV + methodV; + } + } + + private String field = "field"; +} diff --git a/test/transform/resource/before/ValSimple.java b/test/transform/resource/before/ValSimple.java new file mode 100644 index 00000000..15508bbc --- /dev/null +++ b/test/transform/resource/before/ValSimple.java @@ -0,0 +1,26 @@ +public class ValSimple { + private String field = "field"; + private short field2 = 5; + + private String method() { + return "method"; + } + + private double method2() { + return 2.0; + } + + private void testVal(String param) { + val fieldV = field; + val methodV = method(); + val paramV = param; + + val valOfVal = fieldV; + val operatorV = fieldV + valOfVal; + + val fieldW = field2; + val methodW = method2(); + byte localVar = 3; + val operatorW = fieldW + localVar; + } +} diff --git a/test/transform/resource/before/ValWeirdTypes.java b/test/transform/resource/before/ValWeirdTypes.java new file mode 100644 index 00000000..6f6eb9db --- /dev/null +++ b/test/transform/resource/before/ValWeirdTypes.java @@ -0,0 +1,54 @@ +import java.util.*; + +public class ValWeirdTypes<Z> { + private final List<Z> fieldList; + + public void testGenerics() { + List<String> list = new ArrayList<String>(); + list.add("Hello, World!"); + val shouldBeString = list.get(0); + val shouldBeListOfString = list; + val shouldBeListOfStringToo = Arrays.asList("hello", "world"); + val shouldBeString2 = shouldBeListOfString.get(0); + } + + public void testGenericsInference() { + val huh = Collections.emptyList(); + val huh2 = Collections.<Number>emptyList(); + } + + public void testPrimitives() { + val x = 10; + val y = 5 + 3L; + } + + public void testAnonymousInnerClass() { + val y = new Runnable() { + public void run() {} + }; + } + + public <T extends Number> void testTypeParams(List<T> param) { + val t = param.get(0); + val z = fieldList.get(0); + val k = param; + val y = fieldList; + } + + public void testBounds(List<? extends Number> lower, List<? super Number> upper) { + val a = lower.get(0); + val b = upper.get(0); + val c = lower; + val d = upper; + List<?> unbound = lower; + val e = unbound; + } + + public void testCompound() { + val a = new ArrayList<String>(); + val b = new Vector<String>(); + val c = 1 < System.currentTimeMillis(); + val d = c ? a : b; + java.util.RandomAccess confirm = c ? a : b; + } +}
\ No newline at end of file diff --git a/test/transform/resource/messages-delombok/ValErrors.java.messages b/test/transform/resource/messages-delombok/ValErrors.java.messages new file mode 100644 index 00000000..feba6912 --- /dev/null +++ b/test/transform/resource/messages-delombok/ValErrors.java.messages @@ -0,0 +1,2 @@ +3:21 ERROR Cannot use 'val' here because initializer expression does not have a representable type: <nulltype> +7:21 ERROR Cannot use 'val' here because initializer expression does not have a representable type: Type cannot be resolved |