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