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 {
  81     public static void main(String... args) throws Exception {
  82         new BoxingAndSuper().testSuper();
  83         new BoxingAndSuper().testThis();
  84     }
  85 
  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 /*synthetic*/ final Integer $le1 = 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 /*synthetic*/ final Integer $le1 = 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 /*synthetic*/ final Integer $le1 = 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 /*synthetic*/ final Integer $le1 = 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 
 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 /*synthetic*/ final Integer $le1 = i = Integer.valueOf((int)(i.intValue() + 1)) in $le0));\n" +
 205                 "    (let /*synthetic*/ final Integer $le2 = (Integer)this.i in (let /*synthetic*/ final Integer $le3 = this.i = Integer.valueOf((int)(this.i.intValue() + 1)) in $le2));\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 /*synthetic*/ final Integer $le1 = 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 
 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 
 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 
 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
 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 
 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                             letExprRemap.put(((JCVariableDecl) var).name.toString(), "$le" + i++);
 333                         }
 334                     }
 335                     return super.visitOther(node, p);
 336                 }
 337 
 338                 @Override
 339                 public JCTree visitVariable(VariableTree node, Void p) {
 340                     String newName = letExprRemap.get(node.getName().toString());
 341                     if (newName != null) {
 342                         node = make.VarDef((JCModifiers) node.getModifiers(), names.fromString(newName), (JCExpression) node.getType(), (JCExpression) node.getInitializer());
 343                     }
 344                     return super.visitVariable(node, p);
 345                 }
 346 
 347                 @Override
 348                 public JCTree visitIdentifier(IdentifierTree node, Void p) {
 349                     String newName = letExprRemap.get(node.getName().toString());
 350                     if (newName != null) {
 351                         node = make.Ident(names.fromString(newName));
 352                     }
 353                     return super.visitIdentifier(node, p);
 354                 }
 355 
 356                 @Override
 357                 public <T extends JCTree> T copy(T tree, Void p) {
 358                     if (tree.hasTag(Tag.LETEXPR)) {
 359                         return (T) visitOther(tree, p);
 360                     }
 361                     return super.copy(tree, p);
 362                 }
 363 
 364             }.copy(method.body);
 365             log.getWriter(WriterKind.NOTICE).println(body.toString());
 366 
 367             Set<Symbol> invoked = new TreeSet<>(symbolComparator);
 368 
 369             new TreeScanner() {
 370                 @Override
 371                 public void visitApply(JCMethodInvocation tree) {
 372                     invoked.add(TreeInfo.symbol(tree.meth));
 373                     super.visitApply(tree);
 374                 }
 375             }.scan(method);
 376 
 377             for (Symbol search : invoked) {
 378                 dump(search, declarations, alreadyPrinted);
 379             }
 380         }
 381 
 382         private String symbol2String(Symbol sym) {
 383             switch (sym.kind) {
 384                 case TYP:
 385                     return sym.getQualifiedName().toString();
 386                 case MTH:
 387                     return symbol2String(sym.owner) + "." + sym.name + sym.type.toString();
 388                 default:
 389                     throw new UnsupportedOperationException();
 390             }
 391         }
 392 
 393         private final Comparator<Symbol> symbolComparator = (s1, s2) -> symbol2String(s1).compareTo(symbol2String(s2));
 394     }
 395 
 396 }