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.MandatoryWarningHandler;
 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     /** flag: is the "preview" lint category enabled? */
 72     private final boolean verbose;
 73 
 74     /** the diag handler to manage preview feature usage diagnostics */
 75     private final MandatoryWarningHandler previewHandler;
 76 
 77     /** test flag: should all features be considered as preview features? */
 78     private final boolean forcePreview;
 79 
 80     /** a mapping from classfile numbers to Java SE versions */
 81     private final Map<Integer, Source> majorVersionToSource;
 82 
 83     private final Set<JavaFileObject> sourcesWithPreviewFeatures = new HashSet<>();
 84 
 85     private final Names names;
 86     private final Log log;
 87     private final Source source;
 88 
 89     protected static final Context.Key<Preview> previewKey = new Context.Key<>();
 90 
 91     public static Preview instance(Context context) {
 92         Preview instance = context.get(previewKey);
 93         if (instance == null) {
 94             instance = new Preview(context);
 95         }
 96         return instance;
 97     }
 98 
 99     @SuppressWarnings("this-escape")
100     protected Preview(Context context) {
101         context.put(previewKey, this);
102         Options options = Options.instance(context);
103         names = Names.instance(context);
104         enabled = options.isSet(PREVIEW);
105         log = Log.instance(context);
106         source = Source.instance(context);
107         verbose = Lint.instance(context).isEnabled(LintCategory.PREVIEW);
108         previewHandler = new MandatoryWarningHandler(log, source, verbose, true, LintCategory.PREVIEW);
109         forcePreview = options.isSet("forcePreview");
110         majorVersionToSource = initMajorVersionToSourceMap();
111     }
112 
113     private Map<Integer, Source> initMajorVersionToSourceMap() {
114         Map<Integer, Source> majorVersionToSource = new HashMap<>();
115         for (Target t : Target.values()) {
116             int major = t.majorVersion;
117             Source source = Source.lookup(t.name);
118             if (source != null) {
119                 majorVersionToSource.put(major, source);
120             }
121         }
122         return majorVersionToSource;
123     }
124 
125     /**
126      * Returns true if {@code s} is deemed to participate in the preview of {@code previewSymbol}, and
127      * therefore no warnings or errors will be produced.
128      *
129      * @param syms the symbol table
130      * @param s the symbol depending on the preview symbol
131      * @param previewSymbol the preview symbol marked with @Preview
132      * @return true if {@code s} is participating in the preview of {@code previewSymbol}
133      */
134     public boolean participatesInPreview(Symtab syms, Symbol s, Symbol previewSymbol) {
135         // All symbols in the same module as the preview symbol participate in the preview API
136         if (previewSymbol.packge().modle == s.packge().modle) {
137             return true;
138         }
139 
140         return participatesInPreview(syms, s.packge().modle);
141     }
142 
143     /**
144      * Returns true if module {@code m} is deemed to participate in the preview, and
145      * therefore no warnings or errors will be produced.
146      *
147      * @param syms the symbol table
148      * @param m the module to check
149      * @return true if {@code m} is participating in the preview of {@code previewSymbol}
150      */
151     public boolean participatesInPreview(Symtab syms, ModuleSymbol m) {
152         // If java.base's jdk.internal.javac package is exported to s's module then
153         // s participates in the preview API
154         return syms.java_base.exports.stream()
155                 .filter(ed -> ed.packge.fullname == names.jdk_internal_javac)
156                 .anyMatch(ed -> ed.modules.contains(m)) ||
157                //the specification lists the java.se module as participating in preview:
158                m.name == names.java_se;
159     }
160 
161     /**
162      * Report usage of a preview feature. Usages reported through this method will affect the
163      * set of sourcefiles with dependencies on preview features.
164      * @param pos the position at which the preview feature was used.
165      * @param feature the preview feature used.
166      */
167     public void warnPreview(int pos, Feature feature) {
168         warnPreview(new SimpleDiagnosticPosition(pos), feature);
169     }
170 
171     /**
172      * Report usage of a preview feature. Usages reported through this method will affect the
173      * set of sourcefiles with dependencies on preview features.
174      * @param pos the position at which the preview feature was used.
175      * @param feature the preview feature used.
176      */
177     public void warnPreview(DiagnosticPosition pos, Feature feature) {
178         Assert.check(isEnabled());
179         Assert.check(isPreview(feature));
180         markUsesPreview(pos);
181         previewHandler.report(pos, feature.isPlural() ?
182                 LintWarnings.PreviewFeatureUsePlural(feature.nameFragment()) :
183                 LintWarnings.PreviewFeatureUse(feature.nameFragment()));
184     }
185 
186     /**
187      * Report usage of a preview feature in classfile.
188      * @param classfile the name of the classfile with preview features enabled
189      * @param majorVersion the major version found in the classfile.
190      */
191     public void warnPreview(JavaFileObject classfile, int majorVersion) {
192         Assert.check(isEnabled());
193         if (verbose) {
194             log.mandatoryWarning(null,
195                     LintWarnings.PreviewFeatureUseClassfile(classfile, majorVersionToSource.get(majorVersion).name));
196         }
197     }
198 
199     /**
200      * Mark the current source file as using a preview feature. The corresponding classfile
201      * will be generated with minor version {@link ClassFile#PREVIEW_MINOR_VERSION}.
202      * @param pos the position at which the preview feature was used.
203      */
204     public void markUsesPreview(DiagnosticPosition pos) {
205         sourcesWithPreviewFeatures.add(log.currentSourceFile());
206     }
207 
208     public void reportPreviewWarning(DiagnosticPosition pos, LintWarning warnKey) {
209         previewHandler.report(pos, warnKey);
210     }
211 
212     public boolean usesPreview(JavaFileObject file) {
213         return sourcesWithPreviewFeatures.contains(file);
214     }
215 
216     /**
217      * Are preview features enabled?
218      * @return true, if preview features are enabled.
219      */
220     public boolean isEnabled() {
221         return enabled;
222     }
223 
224     /**
225      * Is given feature a preview feature?
226      * @param feature the feature to be tested.
227      * @return true, if given feature is a preview feature.
228      */
229     public boolean isPreview(Feature feature) {
230         return switch (feature) {
231             case IMPLICIT_CLASSES -> true;
232             case FLEXIBLE_CONSTRUCTORS -> true;
233             case PRIMITIVE_PATTERNS -> true;
234             case VALUE_CLASSES -> true;
235             case MODULE_IMPORTS -> true;
236             case JAVA_BASE_TRANSITIVE -> true;
237             //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
238             //When real preview features will be added, this method can be implemented to return 'true'
239             //for those selected features, and 'false' for all the others.
240             default -> forcePreview;
241         };
242     }
243 
244     /**
245      * Generate an error key which captures the fact that a given preview feature could not be used
246      * due to the preview feature support being disabled.
247      * @param feature the feature for which the diagnostic has to be generated.
248      * @return the diagnostic.
249      */
250     public Error disabledError(Feature feature) {
251         Assert.check(!isEnabled());
252         return feature.isPlural() ?
253                 Errors.PreviewFeatureDisabledPlural(feature.nameFragment()) :
254                 Errors.PreviewFeatureDisabled(feature.nameFragment());
255     }
256 
257     /**
258      * Generate an error key which captures the fact that a preview classfile cannot be loaded
259      * due to the preview feature support being disabled.
260      * @param classfile the name of the classfile with preview features enabled
261      * @param majorVersion the major version found in the classfile.
262      */
263     public Error disabledError(JavaFileObject classfile, int majorVersion) {
264         Assert.check(!isEnabled());
265         return Errors.PreviewFeatureDisabledClassfile(classfile, majorVersionToSource.get(majorVersion).name);
266     }
267 
268     /**
269      * Check whether the given symbol has been declared using
270      * a preview language feature.
271      *
272      * @param sym Symbol to check
273      * @return true iff sym has been declared using a preview language feature
274      */
275     public boolean declaredUsingPreviewFeature(Symbol sym) {
276         return false;
277     }
278 
279     /**
280      * Report any deferred diagnostics.
281      */
282     public void reportDeferredDiagnostics() {
283         previewHandler.reportDeferredDiagnostic();
284     }
285 
286     public void clear() {
287         previewHandler.clear();
288     }
289 
290     public void checkSourceLevel(DiagnosticPosition pos, Feature feature) {
291         if (isPreview(feature) && !isEnabled()) {
292             //preview feature without --preview flag, error
293             log.error(JCDiagnostic.DiagnosticFlag.SOURCE_LEVEL, pos, disabledError(feature));
294         } else {
295             if (!feature.allowedInSource(source)) {
296                 log.error(JCDiagnostic.DiagnosticFlag.SOURCE_LEVEL, pos,
297                           feature.error(source.name));
298             }
299             if (isEnabled() && isPreview(feature)) {
300                 warnPreview(pos, feature);
301             }
302         }
303     }
304 
305 }