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