1 /*
2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /**
25  * @test
26  * @library /tools/lib
27  * @modules jdk.compiler/com.sun.tools.javac.api
28  *          jdk.compiler/com.sun.tools.javac.code
29  *          jdk.compiler/com.sun.tools.javac.comp
30  *          jdk.compiler/com.sun.tools.javac.tree
31  *          jdk.compiler/com.sun.tools.javac.util
32  */
33 
34 import java.io.StringWriter;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.Arrays;
39 import java.util.Comparator;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.TreeSet;
45 
46 import javax.tools.JavaFileObject;
47 import javax.tools.ToolProvider;
48 
49 import com.sun.source.tree.IdentifierTree;
50 import com.sun.source.tree.Tree;
51 import com.sun.source.tree.VariableTree;
52 import com.sun.source.util.JavacTask;
53 import com.sun.tools.javac.api.JavacTool;
54 import com.sun.tools.javac.code.Symbol;
55 import com.sun.tools.javac.comp.AttrContext;
56 import com.sun.tools.javac.comp.Env;
57 import com.sun.tools.javac.comp.Lower;
58 import com.sun.tools.javac.tree.JCTree;
59 import com.sun.tools.javac.tree.JCTree.JCBlock;
60 import com.sun.tools.javac.tree.JCTree.JCExpression;
61 import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
62 import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
63 import com.sun.tools.javac.tree.JCTree.JCModifiers;
64 import com.sun.tools.javac.tree.JCTree.JCStatement;
65 import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
66 import com.sun.tools.javac.tree.JCTree.LetExpr;
67 import com.sun.tools.javac.tree.JCTree.Tag;
68 import com.sun.tools.javac.tree.TreeCopier;
69 import com.sun.tools.javac.tree.TreeInfo;
70 import com.sun.tools.javac.tree.TreeMaker;
71 import com.sun.tools.javac.tree.TreeScanner;
72 import com.sun.tools.javac.util.Context;
73 import com.sun.tools.javac.util.List;
74 import com.sun.tools.javac.util.Log;
75 import com.sun.tools.javac.util.Log.WriterKind;
76 import com.sun.tools.javac.util.Names;
77 
78 import toolbox.ToolBox;
79 
80 public class BoxingAndSuper {
main(String... args)81     public static void main(String... args) throws Exception {
82         new BoxingAndSuper().testSuper();
83         new BoxingAndSuper().testThis();
84     }
85 
testSuper()86     public void testSuper() throws Exception {
87         //super, same package:
88         runTest("package p;\n" +
89                 "class Test extends Parent {\n" +
90                 "     protected Integer i=20;\n" +
91                 "     private Integer dump() {\n" +
92                 "         return super.i++;\n" +
93                 "     }\n" +
94                 "}\n" +
95                 "---" +
96                 "package p;\n" +
97                 "class Parent {\n" +
98                 "     protected Integer i=10;\n" +
99                 "} ",
100                 "p.Test.dump()java.lang.Integer\n" +
101                 "{\n" +
102                 "    return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " +
103                             "in (let super.i = Integer.valueOf((int)(super.i.intValue() + 1)); " +
104                                 "in $le0));\n" +
105                 "}\n");
106         //qualified super, same package:
107         runTest("package p;\n" +
108                 "class Test extends Parent {\n" +
109                 "     protected Integer i=20;\n" +
110                 "     class Inner {\n" +
111                 "         private Integer dump() {\n" +
112                 "             return Test.super.i++;\n" +
113                 "         }\n" +
114                 "     }\n" +
115                 "}\n" +
116                 "---" +
117                 "package p;\n" +
118                 "class Parent {\n" +
119                 "     protected Integer i=10;\n" +
120                 "} ",
121                 "p.Test.Inner.dump()java.lang.Integer\n" +
122                 "{\n" +
123                 "    return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " +
124                             "in (let Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))); " +
125                                 "in $le0));\n" +
126                 "}\n" +
127                 "p.Test.access$001(p.Test)java.lang.Integer\n" +
128                 "{\n" +
129                 "    return x0.i;\n" +
130                 "}\n" +
131                 "p.Test.access$103(p.Test,java.lang.Integer)java.lang.Integer\n" +
132                 "{\n" +
133                 "    return x0.i = x1;\n" +
134                 "}\n" +
135                 "p.Test.access$201(p.Test)java.lang.Integer\n" +
136                 "{\n" +
137                 "    return x0.i;\n" +
138                 "}\n");
139         //super, different packages:
140         runTest("package p1;\n" +
141                 "class Test extends p2.Parent {\n" +
142                 "     protected Integer i=20;\n" +
143                 "     private Integer dump() {\n" +
144                 "         return super.i++;\n" +
145                 "     }\n" +
146                 "}\n" +
147                 "---" +
148                 "package p2;\n" +
149                 "public class Parent {\n" +
150                 "     protected Integer i=10;\n" +
151                 "} ",
152                 "p1.Test.dump()java.lang.Integer\n" +
153                 "{\n" +
154                 "    return (let /*synthetic*/ final Integer $le0 = (Integer)super.i " +
155                             "in (let super.i = Integer.valueOf((int)(super.i.intValue() + 1)); " +
156                                 "in $le0));\n" +
157                 "}\n");
158         //qualified super, different packages:
159         runTest("package p1;\n" +
160                 "class Test extends p2.Parent {\n" +
161                 "     protected Integer i=20;\n" +
162                 "     class Inner {\n" +
163                 "         private Integer dump() {\n" +
164                 "             return Test.super.i++;\n" +
165                 "         }\n" +
166                 "     }\n" +
167                 "}\n" +
168                 "---" +
169                 "package p2;\n" +
170                 "public class Parent {\n" +
171                 "     protected Integer i=10;\n" +
172                 "} ",
173                 "p1.Test.Inner.dump()java.lang.Integer\n" +
174                 "{\n" +
175                 "    return (let /*synthetic*/ final Integer $le0 = (Integer)Test.access$001(this$0) " +
176                             "in (let Test.access$103(this$0, Integer.valueOf((int)(Test.access$201(this$0).intValue() + 1))); " +
177                                 "in $le0));\n" +
178                 "}\n" +
179                 "p1.Test.access$001(p1.Test)java.lang.Integer\n" +
180                 "{\n" +
181                 "    return x0.i;\n" +
182                 "}\n" +
183                 "p1.Test.access$103(p1.Test,java.lang.Integer)java.lang.Integer\n" +
184                 "{\n" +
185                 "    return x0.i = x1;\n" +
186                 "}\n" +
187                 "p1.Test.access$201(p1.Test)java.lang.Integer\n" +
188                 "{\n" +
189                 "    return x0.i;\n" +
190                 "}\n");
191     }
192 
testThis()193     public void testThis() throws Exception {
194         String code = "public class Test {\n" +
195                       "    Integer i;\n" +
196                       "    private void dump() {\n" +
197                       "        i++;\n" +
198                       "        this.i++;\n" +
199                       "    }\n" +
200                       "}";
201         String expected =
202                 "Test.dump()void\n" +
203                 "{\n" +
204                 "    (let /*synthetic*/ final Integer $le0 = i in (let i = Integer.valueOf((int)(i.intValue() + 1)); in $le0));\n" +
205                 "    (let /*synthetic*/ final Integer $le1 = (Integer)this.i in (let this.i = Integer.valueOf((int)(this.i.intValue() + 1)); in $le1));\n" +
206                 "}\n";
207         runTest(code, expected);
208         //qualified this:
209         runTest("public class Test {\n" +
210                 "   Integer i;\n" +
211                 "   class Inner1 {\n" +
212                 "       class Inner2 {\n" +
213                 "           private Integer dump() {\n" +
214                 "               return Test.this.i++;\n" +
215                 "           }\n" +
216                 "       }\n" +
217                 "   }\n" +
218                 "}",
219                 "Test.Inner1.Inner2.dump()java.lang.Integer\n" +
220                 "{\n" +
221                 "    return (let /*synthetic*/ final Integer $le0 = (Integer)this$1.this$0.i" +
222                            " in (let this$1.this$0.i = " +
223                                 "Integer.valueOf((int)(this$1.this$0.i.intValue() + 1)); " +
224                                 "in $le0));\n" +
225                 "}\n"
226         );
227     }
228 
229     private final ToolBox tb = new ToolBox();
230 
runTest(String code, String expectedDesugar)231     private void runTest(String code, String expectedDesugar) throws Exception {
232         List<JavaFileObject> files = List.nil();
233 
234         for (String file : code.split("---")) {
235             files = files.prepend(new ToolBox.JavaSource(file));
236         }
237 
238         Path classes = Paths.get("classes");
239 
240         if (Files.exists(classes)) {
241             tb.cleanDirectory(classes);
242         } else {
243             Files.createDirectories(classes);
244         }
245 
246         JavacTool compiler = (JavacTool) ToolProvider.getSystemJavaCompiler();
247         StringWriter out = new StringWriter();
248         Context context = new Context();
249         TestLower.preRegister(context);
250         Iterable<String> options = Arrays.asList("-d", classes.toString());
251         JavacTask task = (JavacTask) compiler.getTask(out, null, null, options, null, files, context);
252 
253         task.generate();
254 
255         out.flush();
256 
257         String actual = out.toString().replace(System.getProperty("line.separator"), "\n");
258 
259         if (!expectedDesugar.equals(actual)) {
260             throw new IllegalStateException("Actual does not match expected: " + actual);
261         }
262     }
263 
264     private static final class TestLower extends Lower {
265 
preRegister(Context context)266         public static void preRegister(Context context) {
267             context.put(lowerKey, new Context.Factory<Lower>() {
268                    public Lower make(Context c) {
269                        return new TestLower(c);
270                    }
271             });
272         }
273 
274         private final TreeMaker make;
275         private final Names names;
276         private final Log log;
277 
TestLower(Context context)278         public TestLower(Context context) {
279             super(context);
280             make = TreeMaker.instance(context);
281             names = Names.instance(context);
282             log = Log.instance(context);
283         }
284 
285         @Override
translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make)286         public List<JCTree> translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
287             List<JCTree> result = super.translateTopLevelClass(env, cdef, make);
288             Map<Symbol, JCMethodDecl> declarations = new HashMap<>();
289             Set<Symbol> toDump = new TreeSet<>(symbolComparator);
290 
291             new TreeScanner() {
292                 @Override
293                 public void visitMethodDef(JCMethodDecl tree) {
294                     if (tree.name.toString().startsWith("dump")) {
295                         toDump.add(tree.sym);
296                     }
297                     declarations.put(tree.sym, tree);
298                     super.visitMethodDef(tree);
299                 }
300             }.scan(result);
301 
302             for (Symbol d : toDump) {
303                 dump(d, declarations, new HashSet<>());
304             }
305 
306             return result;
307         }
308 
dump(Symbol methodSym, Map<Symbol, JCMethodDecl> declarations, Set<Symbol> alreadyPrinted)309         private void dump(Symbol methodSym, Map<Symbol, JCMethodDecl> declarations, Set<Symbol> alreadyPrinted) {
310             if (!alreadyPrinted.add(methodSym))
311                 return ;
312 
313             JCMethodDecl method = declarations.get(methodSym);
314 
315             if (method == null) {
316                 return ;
317             }
318 
319             log.getWriter(WriterKind.NOTICE).println(symbol2String(methodSym));
320 
321             JCBlock body = new TreeCopier<Void>(make) {
322                 private final Map<String, String> letExprRemap = new HashMap<>();
323                 private int i;
324 
325                 @Override
326                 public JCTree visitOther(Tree node, Void p) {
327                     JCTree tree = (JCTree) node;
328                     if (tree.hasTag(Tag.LETEXPR)) {
329                         LetExpr le = (LetExpr) tree;
330 
331                         for (JCStatement var : le.defs) {
332                             if (var.hasTag(Tag.VARDEF))
333                                 letExprRemap.put(((JCVariableDecl) var).name.toString(), "$le" + i++);
334                         }
335                     }
336                     return super.visitOther(node, p);
337                 }
338 
339                 @Override
340                 public JCTree visitVariable(VariableTree node, Void p) {
341                     String newName = letExprRemap.get(node.getName().toString());
342                     if (newName != null) {
343                         node = make.VarDef((JCModifiers) node.getModifiers(), names.fromString(newName), (JCExpression) node.getType(), (JCExpression) node.getInitializer());
344                     }
345                     return super.visitVariable(node, p);
346                 }
347 
348                 @Override
349                 public JCTree visitIdentifier(IdentifierTree node, Void p) {
350                     String newName = letExprRemap.get(node.getName().toString());
351                     if (newName != null) {
352                         node = make.Ident(names.fromString(newName));
353                     }
354                     return super.visitIdentifier(node, p);
355                 }
356 
357                 @Override
358                 public <T extends JCTree> T copy(T tree, Void p) {
359                     if (tree.hasTag(Tag.LETEXPR)) {
360                         return (T) visitOther(tree, p);
361                     }
362                     return super.copy(tree, p);
363                 }
364 
365             }.copy(method.body);
366             log.getWriter(WriterKind.NOTICE).println(body.toString());
367 
368             Set<Symbol> invoked = new TreeSet<>(symbolComparator);
369 
370             new TreeScanner() {
371                 @Override
372                 public void visitApply(JCMethodInvocation tree) {
373                     invoked.add(TreeInfo.symbol(tree.meth));
374                     super.visitApply(tree);
375                 }
376             }.scan(method);
377 
378             for (Symbol search : invoked) {
379                 dump(search, declarations, alreadyPrinted);
380             }
381         }
382 
symbol2String(Symbol sym)383         private String symbol2String(Symbol sym) {
384             switch (sym.kind) {
385                 case TYP:
386                     return sym.getQualifiedName().toString();
387                 case MTH:
388                     return symbol2String(sym.owner) + "." + sym.name + sym.type.toString();
389                 default:
390                     throw new UnsupportedOperationException();
391             }
392         }
393 
394         private final Comparator<Symbol> symbolComparator = (s1, s2) -> symbol2String(s1).compareTo(symbol2String(s2));
395     }
396 
397 }
398