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 * @bug 8166628
27 * @summary Verify that loading a classfile for a local class that is a member of an anonymous class
28 *          won't break compilation.
29 * @modules jdk.compiler
30 */
31
32import java.io.IOException;
33import java.io.StringWriter;
34import java.net.URI;
35import java.net.URISyntaxException;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.util.Arrays;
39import java.util.List;
40import java.util.Set;
41
42import javax.annotation.processing.AbstractProcessor;
43import javax.annotation.processing.RoundEnvironment;
44import javax.annotation.processing.SupportedAnnotationTypes;
45import javax.lang.model.element.Element;
46import javax.lang.model.element.PackageElement;
47import javax.lang.model.element.TypeElement;
48import javax.lang.model.util.Elements;
49import javax.tools.Diagnostic;
50import javax.tools.DiagnosticListener;
51import javax.tools.JavaCompiler;
52import javax.tools.JavaFileObject;
53import javax.tools.SimpleJavaFileObject;
54import javax.tools.ToolProvider;
55
56import com.sun.source.tree.BlockTree;
57import com.sun.source.tree.ClassTree;
58import com.sun.source.tree.MethodTree;
59import com.sun.source.tree.Tree;
60import com.sun.source.tree.VariableTree;
61import com.sun.source.util.JavacTask;
62import com.sun.source.util.TaskEvent;
63import com.sun.source.util.TaskEvent.Kind;
64import com.sun.source.util.TaskListener;
65import com.sun.source.util.TreePath;
66import com.sun.source.util.TreePathScanner;
67import com.sun.source.util.Trees;
68
69public class LocalInAnonymous {
70
71    public static void main(String[] args) throws Exception {
72        Path base = Paths.get(".").toAbsolutePath();
73        Path classes = base.resolve("classes");
74        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
75        DiagnosticListener<JavaFileObject> noErrors = d -> {
76            if (d.getKind() == Diagnostic.Kind.ERROR) {
77                throw new AssertionError(d.getMessage(null));
78            }
79        };
80        List<TJFO> files = Arrays.asList(new TJFO("Test", CODE));
81        List<String> options = Arrays.asList("-d", classes.toString());
82        StringWriter out = new StringWriter();
83        JavacTask task = (JavacTask) compiler.getTask(out, null, noErrors, options, null, files);
84        task.call();
85        if (!out.toString().isEmpty()) {
86            throw new AssertionError("Unexpected output: " + out);
87        }
88        options = Arrays.asList("-classpath", classes.toString(), "-d", classes.toString());
89        JavacTask task2 = (JavacTask) compiler.getTask(out, null, noErrors, options, null, files);
90        task2.addTaskListener(new TaskListener() {
91            @Override
92            public void started(TaskEvent te) {
93            }
94            @Override
95            public void finished(TaskEvent te) {
96                if (te.getKind() == Kind.ENTER) {
97                    Element pack = task2.getElements().getTypeElement("Test").getEnclosingElement();
98                    System.err.println(pack.getEnclosedElements());
99                }
100                if (te.getKind() == Kind.ANALYZE) {
101                    PackageElement pack = task2.getElements().getPackageOf(te.getTypeElement());
102                    new OwnerCheck(Trees.instance(task2), pack).scan(te.getCompilationUnit(), null);
103                }
104            }
105        });
106        task2.call();
107        if (!out.toString().isEmpty()) {
108            throw new AssertionError("Unexpected output: " + out);
109        }
110        options = Arrays.asList("-classpath", classes.toString(),
111                                "-d", classes.toString(),
112                                "-processorpath", System.getProperty("test.classes"),
113                                "-processor", Processor.class.getName());
114        JavacTask task3 = (JavacTask) compiler.getTask(out, null, noErrors, options, null, files);
115        task3.call();
116        if (!out.toString().isEmpty()) {
117            throw new AssertionError("Unexpected output: " + out);
118        }
119    }
120
121    private static final class TJFO extends SimpleJavaFileObject {
122
123        private final String code;
124
125        public TJFO(String name, String code) throws URISyntaxException {
126            super(new URI("mem:///" + name + ".java"), Kind.SOURCE);
127            this.code = code;
128        }
129
130        @Override
131        public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
132            return code;
133        }
134
135    }
136
137    @SupportedAnnotationTypes("*")
138    public static final class Processor extends AbstractProcessor {
139
140        @Override
141        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
142            Trees trees = Trees.instance(processingEnv);
143            Elements elements = processingEnv.getElementUtils();
144            Element pack = elements.getTypeElement("Test").getEnclosingElement();
145            for (Element root : pack.getEnclosedElements()) {
146                TreePath tp = trees.getPath(root);
147                new OwnerCheck(trees, pack).scan(tp.getCompilationUnit(), null);
148
149            }
150            return false;
151        }
152
153    }
154
155    private static final class OwnerCheck extends TreePathScanner<Void, Void> {
156        private final Trees trees;
157        private Element currentOwner;
158
159        public OwnerCheck(Trees trees, Element pack) {
160            this.trees = trees;
161            this.currentOwner = pack;
162        }
163
164        @Override
165        public Void visitClass(ClassTree node, Void p) {
166            Element prevOwner = currentOwner;
167            try {
168                Element currentElement = trees.getElement(getCurrentPath());
169                if (currentOwner != null && currentElement.getEnclosingElement() != currentOwner) {
170                    throw new AssertionError("Unexpected owner!");
171                }
172                currentOwner = currentElement;
173                return super.visitClass(node, p);
174            } finally {
175                currentOwner = prevOwner;
176            }
177        }
178
179        @Override
180        public Void visitMethod(MethodTree node, Void p) {
181            Element prevOwner = currentOwner;
182            try {
183                Element currentElement = trees.getElement(getCurrentPath());
184                if (currentElement.getEnclosingElement() != currentOwner) {
185                    throw new AssertionError("Unexpected owner!");
186                }
187                currentOwner = currentElement;
188                return super.visitMethod(node, p);
189            } finally {
190                currentOwner = prevOwner;
191            }
192        }
193
194        @Override
195        public Void visitVariable(VariableTree node, Void p) {
196            Element currentElement = trees.getElement(getCurrentPath());
197            if (!currentElement.getKind().isField()) {
198                return super.visitVariable(node, p);
199            }
200            Element prevOwner = currentOwner;
201            try {
202                if (currentElement.getEnclosingElement() != currentOwner) {
203                    throw new AssertionError("Unexpected owner!");
204                }
205                currentOwner = currentElement;
206                return super.visitVariable(node, p);
207            } finally {
208                currentOwner = prevOwner;
209            }
210        }
211
212        @Override
213        public Void visitBlock(BlockTree node, Void p) {
214            if (getCurrentPath().getParentPath().getLeaf().getKind() != Tree.Kind.CLASS) {
215                return super.visitBlock(node, p);
216            }
217            Element prevOwner = currentOwner;
218            try {
219                currentOwner = null;
220                return super.visitBlock(node, p);
221            } finally {
222                currentOwner = prevOwner;
223            }
224        }
225
226    }
227
228    private static final String CODE =
229            "public class Test {\n" +
230            "   void test() {\n" +
231            "       Object o = new Object() {\n" +
232            "           class IC {}\n" +
233            "           public Object get() {\n" +
234            "               return new IC();\n" +
235            "           }\n" +
236            "       };\n" +
237            "   }\n" +
238            "   {\n" +
239            "       Object o = new Object() {\n" +
240            "           class IC {}\n" +
241            "           public Object get() {\n" +
242            "               return new IC();\n" +
243            "           }\n" +
244            "       };\n" +
245            "   }\n" +
246            "   static {\n" +
247            "       Object o = new Object() {\n" +
248            "           class IC {}\n" +
249            "           public Object get() {\n" +
250            "               return new IC();\n" +
251            "           }\n" +
252            "       };\n" +
253            "   }\n" +
254            "   Object o1 = new Object() {\n" +
255            "       class IC {}\n" +
256            "       public Object get() {\n" +
257            "          return new IC();\n" +
258            "       }\n" +
259            "   };\n" +
260            "   static Object o2 = new Object() {\n" +
261            "       class IC {}\n" +
262            "       public Object get() {\n" +
263            "          return new IC();\n" +
264            "       }\n" +
265            "   };\n" +
266            "}";
267}
268