diff --git a/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java b/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java index bf87b56be..9adc7dee2 100644 --- a/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java +++ b/runtime/Java/src/org/antlr/v4/runtime/RuntimeMetaData.java @@ -1,55 +1,320 @@ +/* + * [The "BSD license"] + * Copyright (c) 2014 Terence Parr + * Copyright (c) 2014 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package org.antlr.v4.runtime; -/** Because targets can be updated at different times than the core tool, which includes this Java target, - * we created this runtime information object. The goal is to test compatibility of the generated parser and - * the runtime library for that target it executes with. +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 + * checking for matching version numbers and notifying listeners in the case + * where a version mismatch is detected. * - * Targets generate a tiny bit of code that executes upon loading of - * a parser or lexer, which should check its version against what is contained in this object. For example, - * here is something from the Java templates: + *

+ * The runtime version information is provided by {@link #VERSION} and + * {@link #getRuntimeVersion()}. Detailed information about these values is + * provided in the documentation for each member.

* - * public class extends { - * static { RuntimeMetaData.checkVersion(""); } - * ... + *

+ * 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.

* - * This way, previous versions of target X that are incompatible (e.g., 4.1 and 4.2) will result in - * a warning emitted through the appropriate channel for that target. This can be a msg to stderr or - * an exception; it's up to the target developer. I have decided to throw an exception for Java target - * as a message might not be seen if the parser were embedded in a server or something. + *

+ * By default, the {@link DefaultListener#INSTANCE} listener is automatically + * registered. As long as the default listener is registered, it will always be + * the last listener notified in the event of a version mismatch. This behavior + * ensures the exception it throws will not prevent {@link #checkVersion} from + * notifying custom listeners registered by a user. This default listener may be + * removed by calling {@link #removeListener} for + * {@link DefaultListener#INSTANCE} or {@link #clearListeners}. If required, it + * may be re-registered by calling {@link #addListener}.

*/ public class RuntimeMetaData { - public static class ANTLRVersionMismatchException extends RuntimeException { - public String generatedCodeVersion; - public String runtimeLibVersion; + /** + * 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 VersionMismatchException extends RuntimeException { + /** + * The version of the ANTLR 4 Tool a parser was generated with. This + * value may be {@code null} if {@link #checkVersion} was called from + * user-defined code instead of a call automatically included in the + * generated parser. + */ + @Nullable + public final String generatingToolVersion; + /** + * The version of the ANTLR 4 Runtime library the parser and/or user + * code was compiled against. + */ + @NotNull + public final String compileTimeRuntimeVersion; - public ANTLRVersionMismatchException(String message, String generatedCodeVersion, String runtimeLibVersion) { - super(message); - this.generatedCodeVersion = generatedCodeVersion; - this.runtimeLibVersion = runtimeLibVersion; - } + /** + * Constructs a new instance of the {@link VersionMismatchException} + * class with the specified detailed information about a mismatch + * between ANTLR tool and runtime versions used by a parser. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + * @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 + */ + VersionMismatchException(@NotNull String message, @Nullable String generatingToolVersion, @NotNull String compileTimeRuntimeVersion) { + super(message); + this.generatingToolVersion = generatingToolVersion; + this.compileTimeRuntimeVersion = compileTimeRuntimeVersion; + } + } - @Override - public String getMessage() { - return super.getMessage()+": code version "+generatedCodeVersion+" != runtime version "+runtimeLibVersion; - } - } + /** + * 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}. + * + *

+ * Implementations of this method may, but are not required to, throw + * the provided exception. Note that if a registered listener throws the + * provided exception during the handling of this event, the following + * will be impacted:

+ * + *
    + *
  • The lexer or parser which called {@link #checkVersion} will be + * unusable due to throwing an exception in a static initializer + * block.
  • + *
  • No additional registered listeners will be notified about the + * version mismatch. Since the default {@link DefaultListener} instance + * is always the last listener called (unless it is unregistered), the + * exception it throws will not affect the execution of any other + * registered listeners.
  • + *
+ * + * @param ex a {@link VersionMismatchException} instance containing + * detailed information about the specific version mismatch detected + */ + void reportVersionMismatch(@NotNull VersionMismatchException ex) + throws VersionMismatchException; + } - /** Must match version of tool that generated recognizers */ - public static final String VERSION = "4.3"; // use just "x.y" and don't include bug fix release number + /** + * This class provides a default implementation of {@link Listener} which + * responds to mismatched versions by throwing the provided + * {@link VersionMismatchException}. + */ + public static class DefaultListener implements Listener { + /** + * A default instance of {@link ConsoleListener} which is automatically + * registered to receive version mismatch events. + */ + public static final DefaultListener INSTANCE = new DefaultListener(); - /** As parser or lexer class is loaded, it checks that the version used to generate the code - * is compatible with the runtime version. ANTLR tool generates recognizers with a hardcoded string created by - * the tool during code gen. That version is passed to checkVersion(). - */ - public static void checkVersion(String toolVersion) { - // I believe that 4.2-generated parsers are compatible with the runtime for 4.3 so no exception in this case. - // Technically, we don't need this check because 4.2 it doesn't actually check the version. ;) I am just - // being explicit. Later we can always build a more sophisticated versioning check. - if ( (toolVersion.startsWith("4.2.") || toolVersion.equals("4.2")) ) { - return; - } - if ( !VERSION.equals(toolVersion) ) { - throw new ANTLRVersionMismatchException("ANTLR runtime and generated code versions disagree",toolVersion,VERSION); - } - } + /** + * {@inheritDoc} + */ + @Override + public void reportVersionMismatch(@NotNull VersionMismatchException ex) throws VersionMismatchException { + throw ex; + } + } + + /** + * The list of listeners registered to receive notifications of mismatched + * ANTLR versions. + */ + private static final Collection listeners = new CopyOnWriteArraySet(); + static { + listeners.add(DefaultListener.INSTANCE); + } + + /** + * Register a listener to receive notifications of mismatched ANTLR + * versions. This method ensures that as long as + * {@link DefaultListener#INSTANCE} is registered as a listener, it will + * always be the last listener notified of mismatched versions. + * + * @param listener the listener to notify if mismatched ANTLR versions are + * detected + * + * @see #checkVersion + */ + public static synchronized void addListener(@NotNull Listener listener) { + boolean containedDefault = listeners.remove(DefaultListener.INSTANCE); + listeners.add(listener); + if (containedDefault) { + listeners.add(DefaultListener.INSTANCE); + } + } + + /** + * 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 synchronized boolean removeListener(@NotNull Listener listener) { + return listeners.remove(listener); + } + + /** + * Remove all listeners registered to receive notifications of mismatched + * ANTLR versions. + */ + public static synchronized 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 + * 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 + * expected next release version. For official releases, the value + * will be the actual published version of the library.

+ */ + public static final String VERSION = "4.2.3"; + + /** + * Gets the currently executing version of the ANTLR 4 runtime library. + * + *

+ * 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 + */ + @NotNull + public static String getRuntimeVersion() { + return VERSION; + } + + /** + * 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. + * + *

+ * The version check is designed to detect the following two specific + * scenarios.

+ * + *
    + *
  • The ANTLR Tool version used for code generation does not match the + * currently executing runtime version.
  • + *
  • The ANTLR Runtime version referenced at the time a parser was + * compiled does not match the currently executing runtime version.
  • + *
+ * + *

+ * 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.

+ * + *

+ * 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 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.

+ * + *

+ * 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 + * by, and does not reference, the ANTLR 4 Tool itself. + * @param compileTimeVersion The version of the runtime the parser was + * compiled against. This should always be passed using a direct reference + * to {@link #VERSION}. + */ + public static void checkVersion(@Nullable String toolVersion, @NotNull String compileTimeVersion) { + boolean report = false; + String message = null; + if (toolVersion != null && !VERSION.equals(toolVersion)) { + report = true; + message = String.format("ANTLR Tool version %s used for code generation does not match the current runtime version %s", toolVersion, VERSION); + } + else if (!VERSION.equals(compileTimeVersion)) { + report = true; + message = String.format("ANTLR Runtime version %s used for parser compilation does not match the current runtime version %s", compileTimeVersion, VERSION); + } + + if (report) { + VersionMismatchException ex = new VersionMismatchException(message, toolVersion, compileTimeVersion); + for (Listener listener : listeners) { + listener.reportVersionMismatch(ex); + } + } + } } 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 f6286f0f6..edd53bd3d 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,7 +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 { RuntimeMetaData.checkVersion(""); } + static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = @@ -816,7 +816,8 @@ import org.antlr.v4.runtime.misc.*; Lexer(lexer, atn, actionFuncs, sempredFuncs, superClass) ::= << @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class extends { - static { RuntimeMetaData.checkVersion(""); } + static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } + protected static final DFA[] _decisionToDFA; protected static final PredictionContextCache _sharedContextCache = new PredictionContextCache();