1 /*
2 * Copyright (c) 2018, 2025, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.tools.javac.code;
27
28 import com.sun.tools.javac.code.Lint.LintCategory;
29 import com.sun.tools.javac.code.Source.Feature;
30 import com.sun.tools.javac.code.Symbol.ModuleSymbol;
31 import com.sun.tools.javac.jvm.Target;
32 import com.sun.tools.javac.resources.CompilerProperties.Errors;
33 import com.sun.tools.javac.resources.CompilerProperties.LintWarnings;
34 import com.sun.tools.javac.resources.CompilerProperties.Warnings;
35 import com.sun.tools.javac.util.Assert;
36 import com.sun.tools.javac.util.Context;
37 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
38 import com.sun.tools.javac.util.JCDiagnostic.Error;
39 import com.sun.tools.javac.util.JCDiagnostic.LintWarning;
40 import com.sun.tools.javac.util.JCDiagnostic.SimpleDiagnosticPosition;
41 import com.sun.tools.javac.util.JCDiagnostic.Warning;
42 import com.sun.tools.javac.util.Log;
43 import com.sun.tools.javac.util.Names;
44 import com.sun.tools.javac.util.Options;
45
46 import javax.tools.JavaFileObject;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Map;
50 import java.util.Set;
51
52 import static com.sun.tools.javac.main.Option.PREVIEW;
53 import com.sun.tools.javac.util.JCDiagnostic;
54
55 /**
56 * Helper class to handle preview language features. This class maps certain language features
57 * (see {@link Feature} into 'preview' features; the mapping is completely ad-hoc, so as to allow
58 * for maximum flexibility, which allows to migrate preview feature into supported features with ease.
59 *
60 * This class acts as a centralized point against which usages of preview features are reported by
61 * clients (e.g. other javac classes). Internally, this class collects all such usages and generates
62 * diagnostics to inform the user of such usages. Such diagnostics can be enabled using the
63 * {@link LintCategory#PREVIEW} lint category, and are suppressible by usual means.
64 */
65 public class Preview {
66
67 /** flag: are preview features enabled */
68 private final boolean enabled;
69
70 /** test flag: should all features be considered as preview features? */
71 private final boolean forcePreview;
72
73 /** a mapping from classfile numbers to Java SE versions */
74 private final Map<Integer, Source> majorVersionToSource;
75
76 private final Set<JavaFileObject> sourcesWithPreviewFeatures = new HashSet<>();
77
78 private final Names names;
79 private final Log log;
80 private final Source source;
81
82 protected static final Context.Key<Preview> previewKey = new Context.Key<>();
83
84 public static Preview instance(Context context) {
85 Preview instance = context.get(previewKey);
86 if (instance == null) {
87 instance = new Preview(context);
88 }
89 return instance;
90 }
91
92 @SuppressWarnings("this-escape")
93 protected Preview(Context context) {
94 context.put(previewKey, this);
95 Options options = Options.instance(context);
96 names = Names.instance(context);
97 enabled = options.isSet(PREVIEW);
98 log = Log.instance(context);
99 source = Source.instance(context);
100 forcePreview = options.isSet("forcePreview");
101 majorVersionToSource = initMajorVersionToSourceMap();
102 }
103
104 private Map<Integer, Source> initMajorVersionToSourceMap() {
105 Map<Integer, Source> majorVersionToSource = new HashMap<>();
106 for (Target t : Target.values()) {
107 int major = t.majorVersion;
108 Source source = Source.lookup(t.name);
109 if (source != null) {
110 majorVersionToSource.put(major, source);
111 }
112 }
113 return majorVersionToSource;
114 }
115
116 /**
117 * Returns true if {@code s} is deemed to participate in the preview of {@code previewSymbol}, and
118 * therefore no warnings or errors will be produced.
119 *
120 * @param syms the symbol table
121 * @param s the symbol depending on the preview symbol
122 * @param previewSymbol the preview symbol marked with @Preview
123 * @return true if {@code s} is participating in the preview of {@code previewSymbol}
124 */
125 public boolean participatesInPreview(Symtab syms, Symbol s, Symbol previewSymbol) {
126 // All symbols in the same module as the preview symbol participate in the preview API
127 if (previewSymbol.packge().modle == s.packge().modle) {
128 return true;
129 }
130
131 return participatesInPreview(syms, s.packge().modle);
132 }
133
134 /**
135 * Returns true if module {@code m} is deemed to participate in the preview, and
136 * therefore no warnings or errors will be produced.
137 *
138 * @param syms the symbol table
139 * @param m the module to check
140 * @return true if {@code m} is participating in the preview of {@code previewSymbol}
141 */
142 public boolean participatesInPreview(Symtab syms, ModuleSymbol m) {
143 // If java.base's jdk.internal.javac package is exported to s's module then
144 // s participates in the preview API
145 return syms.java_base.exports.stream()
146 .filter(ed -> ed.packge.fullname == names.jdk_internal_javac)
147 .anyMatch(ed -> ed.modules.contains(m));
148 }
149
150 /**
151 * Report usage of a preview feature. Usages reported through this method will affect the
152 * set of sourcefiles with dependencies on preview features.
153 * @param pos the position at which the preview feature was used.
154 * @param feature the preview feature used.
155 */
156 public void warnPreview(int pos, Feature feature) {
157 warnPreview(new SimpleDiagnosticPosition(pos), feature);
158 }
159
160 /**
161 * Report usage of a preview feature. Usages reported through this method will affect the
162 * set of sourcefiles with dependencies on preview features.
163 * @param pos the position at which the preview feature was used.
164 * @param feature the preview feature used.
165 */
166 public void warnPreview(DiagnosticPosition pos, Feature feature) {
167 Assert.check(isEnabled());
168 Assert.check(isPreview(feature));
169 markUsesPreview(pos);
170 log.warning(pos,
171 feature.isPlural() ?
172 LintWarnings.PreviewFeatureUsePlural(feature.nameFragment()) :
173 LintWarnings.PreviewFeatureUse(feature.nameFragment()));
174 }
175
176 /**
177 * Report usage of a preview feature in classfile.
178 * @param classfile the name of the classfile with preview features enabled
179 * @param majorVersion the major version found in the classfile.
180 */
181 public void warnPreview(JavaFileObject classfile, int majorVersion) {
182 Assert.check(isEnabled());
183 log.warning(LintWarnings.PreviewFeatureUseClassfile(classfile, majorVersionToSource.get(majorVersion).name));
184 }
185
186 /**
187 * Mark the current source file as using a preview feature. The corresponding classfile
188 * will be generated with minor version {@link ClassFile#PREVIEW_MINOR_VERSION}.
189 * @param pos the position at which the preview feature was used.
190 */
191 public void markUsesPreview(DiagnosticPosition pos) {
192 sourcesWithPreviewFeatures.add(log.currentSourceFile());
193 }
194
195 public boolean usesPreview(JavaFileObject file) {
196 return sourcesWithPreviewFeatures.contains(file);
197 }
198
199 /**
200 * Are preview features enabled?
201 * @return true, if preview features are enabled.
202 */
203 public boolean isEnabled() {
204 return enabled;
205 }
206
207 /**
208 * Is given feature a preview feature?
209 * @param feature the feature to be tested.
210 * @return true, if given feature is a preview feature.
211 */
212 public boolean isPreview(Feature feature) {
213 return switch (feature) {
214 case PRIMITIVE_PATTERNS -> true;
215 //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
216 //When real preview features will be added, this method can be implemented to return 'true'
217 //for those selected features, and 'false' for all the others.
218 default -> forcePreview;
219 };
220 }
221
222 /**
223 * Generate an error key which captures the fact that a given preview feature could not be used
224 * due to the preview feature support being disabled.
225 * @param feature the feature for which the diagnostic has to be generated.
226 * @return the diagnostic.
227 */
228 public Error disabledError(Feature feature) {
229 Assert.check(!isEnabled());
230 return feature.isPlural() ?
231 Errors.PreviewFeatureDisabledPlural(feature.nameFragment()) :
232 Errors.PreviewFeatureDisabled(feature.nameFragment());
233 }
234
235 /**
236 * Generate an error key which captures the fact that a preview classfile cannot be loaded
237 * due to the preview feature support being disabled.
238 * @param classfile the name of the classfile with preview features enabled
239 * @param majorVersion the major version found in the classfile.
240 */
241 public Error disabledError(JavaFileObject classfile, int majorVersion) {
242 Assert.check(!isEnabled());
243 return Errors.PreviewFeatureDisabledClassfile(classfile, majorVersionToSource.get(majorVersion).name);
244 }
245
246 /**
247 * Check whether the given symbol has been declared using
248 * a preview language feature.
249 *
250 * @param sym Symbol to check
251 * @return true iff sym has been declared using a preview language feature
252 */
253 public boolean declaredUsingPreviewFeature(Symbol sym) {
254 return false;
255 }
256
257 public void checkSourceLevel(DiagnosticPosition pos, Feature feature) {
258 if (isPreview(feature) && !isEnabled()) {
259 //preview feature without --preview flag, error
260 log.error(pos, disabledError(feature));
261 } else {
262 if (!feature.allowedInSource(source)) {
263 log.error(pos, feature.error(source.name));
264 }
265 if (isEnabled() && isPreview(feature)) {
266 warnPreview(pos, feature);
267 }
268 }
269 }
270
271 }