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