diff --git a/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java b/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java index 1790f18fd..0d950679e 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java +++ b/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java @@ -33,11 +33,14 @@ package org.antlr.v4.runtime; import org.antlr.v4.runtime.misc.NotNull; import org.antlr.v4.runtime.misc.Nullable; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArraySet; + /** * This class provides access to the current version of the ANTLR 4 runtime * library as compile-time and runtime constants, along with methods for - * verifying a minimum level compatibility before executing code which depends - * on the ANTLR 4 runtime library. + * checking for matching version numbers and notifying listeners in the case + * where a version mismatch is detected. * *

* The runtime version information is provided by {@link #VERSION} and @@ -45,23 +48,30 @@ import org.antlr.v4.runtime.misc.Nullable; * provided in the documentation for each member.

* *

- * The runtime compatibility check is implemented by {@link #checkVersion}. - * Detailed information about incorporating this call into user code, as well as - * its use in generated code, is provided in the documentation for the - * method.

+ * The runtime version check is implemented by {@link #checkVersion}. Detailed + * information about incorporating this call into user code, as well as its use + * in generated code, is provided in the documentation for the method.

+ * + *

+ * By default, the {@link ConsoleListener#INSTANCE} listener is + * automatically registered, and reports mismatched versions to + * {@link System#err}. This default listener may be removed by calling + * {@link #removeListener} or {@link #clearListeners}, and may be + * re-registered by calling {@link #addListener}.

*/ public class RuntimeMetaData { /** - * This exception is thrown to indicate that the version of ANTLR used to - * generate and/or compile a parser is not compatible with the currently - * executing version of the runtime library. + * This class provides detailed information about a mismatch between the + * version of the tool a parser was generated with, the version of the + * runtime a parser was compiled against, and/or the currently executing + * version of the runtime. * * @see #checkVersion */ - public static class ANTLRVersionMismatchException extends Exception { + public static class VersionMismatchDetails { /** * The version of the ANTLR 4 Tool a parser was generated with. This - * value may be {@code null} if the version check was called from + * value may be {@code null} if {@link #checkVersion} was called from * user-defined code instead of a call automatically included in the * generated parser. */ @@ -75,41 +85,138 @@ public class RuntimeMetaData { public final String compileTimeRuntimeVersion; /** - * Constructs a new instance of the - * {@link ANTLRVersionMismatchException} class with the specified - * detailed information about a runtime library version compatibility - * error. + * Constructs a new instance of the {@link VersionMismatchDetails} class + * with the specified detailed information about a mismatch between + * ANTLR tool and runtime versions used by a parser. * - * @param message A description of the incompatibility between the Tool - * version, the compile-time runtime version, and/or the currently - * executing runtime version * @param generatingToolVersion The version of the ANTLR 4 Tool a parser * was generated with, or {@code null} if the version check was not part * of the automatically-generated parser code * @param compileTimeRuntimeVersion The version of the ANTLR 4 Runtime * library the code was compiled against */ - public ANTLRVersionMismatchException(String message, String generatingToolVersion, String compileTimeRuntimeVersion) { - super(message); + VersionMismatchDetails(@Nullable String generatingToolVersion, @NotNull String compileTimeRuntimeVersion) { this.generatingToolVersion = generatingToolVersion; this.compileTimeRuntimeVersion = compileTimeRuntimeVersion; } + } + /** + * This interface defines a listener which handles notifications about + * mismatched ANTLR Tool and/or Runtime versions. + */ + public interface Listener { + /** + * Report a version mismatch which was detected by + * {@link #checkDetails}. + * + *

Note that if a registered listener throws an exception during the + * handling of this event, the following will be impacted:

+ * + * + * + * @param details a {@link VersionMismatchDetails} instance containing + * detailed information about the specific version mismatch detected + */ + void reportVersionMismatch(@NotNull VersionMismatchDetails details); + } + + /** + * This class provides a basic implementation of {@link Listener} which + * writes information about mismatched versions to {@link System#err}. + */ + public static class ConsoleListener implements Listener { + /** + * A default instance of {@link ConsoleListener} which is automatically + * registered to receive version mismatch events. + */ + public static final ConsoleListener INSTANCE = new ConsoleListener(); + + /** + * {@inheritDoc} + */ @Override - public String getMessage() { - return super.getMessage() + ": Tool version " + generatingToolVersion + "; compile-time runtime version " + compileTimeRuntimeVersion; + public void reportVersionMismatch(@NotNull VersionMismatchDetails details) { + String message; + String referenceVersion; + if (details.generatingToolVersion != null && !VERSION.equals(details.generatingToolVersion)) { + referenceVersion = details.generatingToolVersion; + message = "ANTLR Tool version %s used for code generation does not match the current runtime version %s"; + } + else if (!VERSION.equals(details.compileTimeRuntimeVersion)) { + referenceVersion = details.compileTimeRuntimeVersion; + message = "ANTLR Runtime version %s used for parser compilation does not match the current runtime version %s"; + } + else { + referenceVersion = ""; + message = "The ANTLR Runtime reported a version mismatch against the current runtime version %s%s"; + } + + String formatted = String.format(message, referenceVersion, VERSION); + System.err.println(formatted); } } + /** + * The list of listeners registered to receive notifications of mismatched + * ANTLR versions. + */ + private static final Collection listeners = new CopyOnWriteArraySet(); + static { + listeners.add(ConsoleListener.INSTANCE); + } + + /** + * Register a listener to receive notifications of mismatched ANTLR + * versions. + * + * @param listener the listener to notify if mismatched ANTLR versions are + * detected + * + * @see #checkVersion + */ + public static void addListener(@NotNull Listener listener) { + listeners.add(listener); + } + + /** + * Remove a specific listener registered to receive notifications of + * mismatched ANTLR versions. + * + * @param listener the listener to remove + * @return {@code true} if the listener was removed; otherwise, + * {@code false} if the specified listener was not found in the list of + * registered listeners + */ + public static boolean removeListener(@NotNull Listener listener) { + return listeners.remove(listener); + } + + /** + * Remove all listeners registered to receive notifications of mismatched + * ANTLR versions. + */ + public static void clearListeners() { + listeners.clear(); + } + /** * A compile-time constant containing the current version of the ANTLR 4 * runtime library. * - *

This compile-time constant value allows generated parsers and other + *

+ * This compile-time constant value allows generated parsers and other * libraries to include a literal reference to the version of the ANTLR 4 * runtime library the code was compiled against.

* - *

During development (between releases), this value contains the + *

+ * During development (between releases), this value contains the * expected next release version. For official releases, the value * will be the actual published version of the library.

*/ @@ -118,7 +225,8 @@ public class RuntimeMetaData { /** * Gets the currently executing version of the ANTLR 4 runtime library. * - *

This method provides runtime access to the {@link #VERSION} field, as + *

+ * This method provides runtime access to the {@link #VERSION} field, as * opposed to directly referencing the field as a compile-time constant.

* * @return The currently executing version of the ANTLR 4 library @@ -129,59 +237,44 @@ public class RuntimeMetaData { } /** - * This method provides the ability to verify that the version of the ANTLR - * 4 used to execute a parser is compatible with the Tool used to generate - * the parser code and/or the version of the runtime the parser was compiled - * against. + * This method provides the ability to detect mismatches between the version + * of ANTLR 4 used to generate a parser, the version of the ANTLR runtime a + * parser was compiled against, and the version of the ANTLR runtime which + * is currently executing. * *

- * Starting with ANTLR 4.2.3, the code generator emits two constants in each - * generated parser: a hard-coded constant indicating the version of the - * tool used to generate the parser and a reference to the compile-time - * constant {@link #VERSION}. At runtime, this method is called to ensure - * that, according to the version numbers, the semantics of the generated - * code are known to be compatible with the version of the ANTLR runtime - * used to execute code.

+ * Starting with ANTLR 4.2.3, the code generator emits a call to this method + * using two constants in each generated lexer and parser: a hard-coded + * constant indicating the version of the tool used to generate the parser + * and a reference to the compile-time constant {@link #VERSION}. At + * runtime, this method is called during the initialization of the generated + * parser to detect mismatched versions, and notify the registered listeners + * prior to creating instances of the parser.

* *

- * The definition of "semantic changes" which are considered by this method - * depend on the value of {@code extendedSemantics}. When this value is - * {@code false}, as is the case for calls to this method in generated code, - * "semantic changes" means changes in the runtime that alter the behavior - * of parsers that use only standardized language features, which does not - * include the behavior of target-language-specific features such as - * embedded actions, custom semantic predicates, and runtime methods that - * are never called by the generated code but may be overridden in separate - * user code. When this value is {@code true}, "semantic changes" indicates - * a wider check across frequently used aspects of the runtime. The latter - * case may or may not consider all breaking changes across releases; for - * details on the specific methods in place see the release notes for the - * affected version(s).

+ * This method does not perform any detection or filtering of semantic + * changes between tool and runtime versions. It simply checks for a simple + * version match and notifies the registered listeners any time a difference + * is detected.

* *

- * Note that if this method throws an exception when - * {@code extendedSemantics} is {@code false}, the lexer or parser which - * resulted in the exception will be prevented from executing, so this - * behavior is reserved for changes that are true breaking changes in the - * behavior. These changes, if any, are mentioned in the release notes for - * the affected release.

+ * Note that some breaking changes between releases could result in other + * types of runtime exceptions, such as a {@link LinkageError}, prior to + * calling this method. In these cases, the underlying version mismatch will + * not be reported to the listeners. This method is primarily intended to + * notify users of potential semantic changes between releases that do not + * result in binary compatibility problems which would be detected by the + * class loader. As with semantic changes, changes which break binary + * compatibility between releases are mentioned in the release notes + * accompanying the affected release.

* *

- * Also note that some breaking changes between releases could result in - * other types of runtime exceptions, such as a {@link LinkageError}, prior - * to calling this method. This method is primarily intended to catch - * semantic changes that do not result in binary compatibility problems - * which would be detected by the class loader. As with semantic changes, - * changes which break binary compatibility between releases are mentioned - * in the release notes accompanying the affected release.

- * - *

- * Additional note for target developers: The compatibility - * check implemented by this class is designed to address specific - * compatibility issues that may arise during the execution of Java - * applications. Other targets should consider the implementation of this - * method in the context of that target's known execution environment, which - * may or may not resemble the design provided for the Java target.

+ * Additional note for target developers: The version check + * implemented by this class is designed to address specific compatibility + * concerns that may arise during the execution of Java applications. Other + * targets should consider the implementation of this method in the context + * of that target's known execution environment, which may or may not + * resemble the design provided for the Java target.

* * @param toolVersion The version of the tool used to generate a parser. * This value may be null when called from user code that was not generated @@ -189,26 +282,21 @@ public class RuntimeMetaData { * @param compileTimeVersion The version of the runtime the parser was * compiled against. This should always be passed using a direct reference * to {@link #VERSION}. - * @param extendedSemantics {@code false} to only check compatibility for - * the API used in generated code, not counting embedded actions and - * semantic predicates; otherwise, {@code true} to check compatibility - * across a wider range of frequently-used features that are commonly used - * in user-defined embedded actions and/or semantic predicates. - * - * @exception ANTLRVersionMismatchException if the version of the ANTLR - * runtime used to execute a compiled parser contains semantic changes which - * would alter the behavior of the generated code */ - public static void checkVersion(@Nullable String toolVersion, @NotNull String compileTimeVersion, boolean extendedSemantics) throws ANTLRVersionMismatchException { - /* Currently there are no versions of the ANTLR runtime library which - * are incompatible with respect to this method. - * - * * Prior to ANTLR 4.2.3, the ANTLR tool did not emit calls to this - * method in the generated code, and did not expose the VERSION - * field. For the specific purposes of this compatibility check, the - * "first public release" of ANTLR 4 can be treated as 4.2.3. - * * If an incompatibility is introduced in a version after 4.2.3, - * specific handling for the affected versions can be added. - */ + public static void checkVersion(@Nullable String toolVersion, @NotNull String compileTimeVersion) { + boolean report = false; + if (toolVersion != null && !VERSION.equals(toolVersion)) { + report = true; + } + else if (!VERSION.equals(compileTimeVersion)) { + report = true; + } + + if (report) { + VersionMismatchDetails details = new VersionMismatchDetails(toolVersion, compileTimeVersion); + for (Listener listener : listeners) { + listener.reportVersionMismatch(details); + } + } } } diff --git a/tool/resources/org/antlr/v4/tool/templates/codegen/Java/Java.stg b/tool/resources/org/antlr/v4/tool/templates/codegen/Java/Java.stg index db26dcf43..88befe9af 100644 --- a/tool/resources/org/antlr/v4/tool/templates/codegen/Java/Java.stg +++ b/tool/resources/org/antlr/v4/tool/templates/codegen/Java/Java.stg @@ -214,14 +214,7 @@ Parser(parser, funcs, atn, sempredFuncs, superClass) ::= << Parser_(parser, funcs, atn, sempredFuncs, ctor, superClass) ::= << @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class extends { - static { - try { - RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION, false); - } - catch (RuntimeMetaData.ANTLRVersionMismatchException ex) { - throw new ExceptionInInitializerError(ex); - } - } + static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = @@ -857,14 +850,7 @@ import org.antlr.v4.runtime.misc.*; Lexer(lexer, atn, actionFuncs, sempredFuncs, superClass) ::= << @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class extends { - static { - try { - RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION, false); - } - catch (RuntimeMetaData.ANTLRVersionMismatchException ex) { - throw new ExceptionInInitializerError(ex); - } - } + static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache =