1/*
2 * Copyright (c) 2015, 2017, 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
24package combo;
25
26import com.sun.source.tree.ClassTree;
27import com.sun.source.tree.CompilationUnitTree;
28import com.sun.source.util.JavacTask;
29import com.sun.source.util.TaskEvent;
30import com.sun.source.util.TaskEvent.Kind;
31import com.sun.source.util.TaskListener;
32import com.sun.source.util.TreeScanner;
33import com.sun.tools.javac.api.MultiTaskListener;
34import com.sun.tools.javac.code.Kinds;
35import com.sun.tools.javac.code.Symbol;
36import com.sun.tools.javac.code.Symtab;
37import com.sun.tools.javac.code.Type;
38import com.sun.tools.javac.code.Type.ClassType;
39import com.sun.tools.javac.code.TypeTag;
40import com.sun.tools.javac.code.Types;
41import com.sun.tools.javac.comp.Annotate;
42import com.sun.tools.javac.comp.Check;
43import com.sun.tools.javac.comp.CompileStates;
44import com.sun.tools.javac.comp.Enter;
45import com.sun.tools.javac.comp.Modules;
46import com.sun.tools.javac.main.Arguments;
47import com.sun.tools.javac.main.JavaCompiler;
48import com.sun.tools.javac.tree.JCTree.JCClassDecl;
49import com.sun.tools.javac.util.Context;
50import com.sun.tools.javac.util.Log;
51
52import javax.tools.Diagnostic;
53import javax.tools.DiagnosticListener;
54import javax.tools.JavaFileManager;
55import javax.tools.JavaFileObject;
56import java.util.HashSet;
57import java.util.Set;
58
59/**
60 * A reusable context is a context that can be used safely across multiple compilation rounds
61 * arising from execution of a combo test. It achieves reuse by replacing some components
62 * (most notably JavaCompiler and Log) with reusable counterparts, and by exposing a method
63 * to cleanup leftovers from previous compilation.
64 * <p>
65 * There are, however, situations in which reusing the context is not safe: (i) when different
66 * compilations are using different sets of compiler options (as most option values are cached
67 * inside components themselves) and (ii) when the compilation unit happens to redefine classes
68 * in the java.* packages.
69 */
70class ReusableContext extends Context implements TaskListener {
71
72    Set<CompilationUnitTree> roots = new HashSet<>();
73
74    String opts;
75    boolean polluted = false;
76
77    ReusableContext() {
78        super();
79        put(Log.logKey, ReusableLog.factory);
80        put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory);
81    }
82
83    void clear() {
84        drop(Arguments.argsKey);
85        drop(DiagnosticListener.class);
86        drop(Log.outKey);
87        drop(Log.errKey);
88        drop(JavaFileManager.class);
89        drop(JavacTask.class);
90
91        if (ht.get(Log.logKey) instanceof ReusableLog) {
92            //log already inited - not first round
93            ((ReusableLog)Log.instance(this)).clear();
94            Enter.instance(this).newRound();
95            ((ReusableJavaCompiler)ReusableJavaCompiler.instance(this)).clear();
96            Types.instance(this).newRound();
97            Check.instance(this).newRound();
98            Modules.instance(this).newRound();
99            Annotate.instance(this).newRound();
100            CompileStates.instance(this).clear();
101            MultiTaskListener.instance(this).clear();
102
103            //find if any of the roots have redefined java.* classes
104            Symtab syms = Symtab.instance(this);
105            pollutionScanner.scan(roots, syms);
106            roots.clear();
107        }
108    }
109
110    /**
111     * This scanner detects as to whether the shared context has been polluted. This happens
112     * whenever a compiled program redefines a core class (in 'java.*' package) or when
113     * (typically because of cyclic inheritance) the symbol kind of a core class has been touched.
114     */
115    TreeScanner<Void, Symtab> pollutionScanner = new TreeScanner<Void, Symtab>() {
116        @Override
117        public Void visitClass(ClassTree node, Symtab syms) {
118            Symbol sym = ((JCClassDecl)node).sym;
119            if (sym != null) {
120                syms.removeClass(sym.packge().modle, sym.flatName());
121                Type sup = supertype(sym);
122                if (isCoreClass(sym) ||
123                        (sup != null && isCoreClass(sup.tsym) && sup.tsym.kind != Kinds.Kind.TYP)) {
124                    polluted = true;
125                }
126            }
127            return super.visitClass(node, syms);
128        }
129
130        private boolean isCoreClass(Symbol s) {
131            return s.flatName().toString().startsWith("java.");
132        }
133
134        private Type supertype(Symbol s) {
135            if (s.type == null ||
136                    !s.type.hasTag(TypeTag.CLASS)) {
137                return null;
138            } else {
139                ClassType ct = (ClassType)s.type;
140                return ct.supertype_field;
141            }
142        }
143    };
144
145    @Override
146    public void finished(TaskEvent e) {
147        if (e.getKind() == Kind.PARSE) {
148            roots.add(e.getCompilationUnit());
149        }
150    }
151
152    @Override
153    public void started(TaskEvent e) {
154        //do nothing
155    }
156
157    <T> void drop(Key<T> k) {
158        ht.remove(k);
159    }
160
161    <T> void drop(Class<T> c) {
162        ht.remove(key(c));
163    }
164
165    /**
166     * Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with
167     * previous compilations.
168     */
169    static class ReusableJavaCompiler extends JavaCompiler {
170
171        static Factory<JavaCompiler> factory = ReusableJavaCompiler::new;
172
173        ReusableJavaCompiler(Context context) {
174            super(context);
175        }
176
177        @Override
178        public void close() {
179            //do nothing
180        }
181
182        void clear() {
183            newRound();
184        }
185
186        @Override
187        protected void checkReusable() {
188            //do nothing - it's ok to reuse the compiler
189        }
190    }
191
192    /**
193     * Reusable Log; exposes a method to clean up the component from leftovers associated with
194     * previous compilations.
195     */
196    static class ReusableLog extends Log {
197
198        static Factory<Log> factory = ReusableLog::new;
199
200        Context context;
201
202        ReusableLog(Context context) {
203            super(context);
204            this.context = context;
205        }
206
207        void clear() {
208            recorded.clear();
209            sourceMap.clear();
210            nerrors = 0;
211            nwarnings = 0;
212            //Set a fake listener that will lazily lookup the context for the 'real' listener. Since
213            //this field is never updated when a new task is created, we cannot simply reset the field
214            //or keep old value. This is a hack to workaround the limitations in the current infrastructure.
215            diagListener = new DiagnosticListener<JavaFileObject>() {
216                DiagnosticListener<JavaFileObject> cachedListener;
217
218                @Override
219                @SuppressWarnings("unchecked")
220                public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
221                    if (cachedListener == null) {
222                        cachedListener = context.get(DiagnosticListener.class);
223                    }
224                    cachedListener.report(diagnostic);
225                }
226            };
227        }
228    }
229}
230