/* * [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; 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. * * <p> * 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.</p> * * <p> * 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.</p> * * <p> * 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 that custom listeners registered by a user will be notified even in * the event the default listener throws an exception. 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}.</p> * * @since 4.3 */ public class RuntimeMetaData { /** * A compile-time constant containing the current version of the ANTLR 4 * runtime library. * * <p> * 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.</p> */ public static final String VERSION = "4.4"; /** * 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; /** * 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; } } /** * 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 #checkVersion}. * * <p> * 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:</p> * * <ul> * <li>The lexer or parser which called {@link #checkVersion} will be * unusable due to throwing an exception in a static initializer * block.</li> * <li>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), it * will not affect the execution of any other registered listeners, even * in the case where it throws an exception.</li> * </ul> * * @param ex a {@link VersionMismatchException} instance containing * detailed information about the specific version mismatch detected */ void reportVersionMismatch(@NotNull VersionMismatchException ex) throws VersionMismatchException; } /** * This class provides a default implementation of {@link Listener} which * responds to mismatched versions by throwing the provided * {@link VersionMismatchException} if the reported version mismatch * indicates the versions differ by more than the <em>major</em> and * <em>minor</em> version components. * * <p> * For example, version strings x.y and x.y.z are considered "compatible", * and this listener will not throw an exception. Likewise, version strings * x.y-SNAPSHOT and x.y.z are considered "compatible" because the major and * minor components x.y are the same in each.</p> * * <p> * For the purposes of this listener, version numbers are assumed to have * the form * <em>major</em>.<em>minor</em>.<em>patch</em>.<em>revision</em>-<em>suffix</em>, * with the individual components defined as follows.</p> * * <ul> * <li><em>major</em> is a required non-negative integer, and is equal to * {@code 4} for ANTLR 4.</li> * <li><em>minor</em> is a required non-negative integer.</li> * <li><em>patch</em> is an optional non-negative integer. When * <em>patch</em> is omitted, the {@code .} (dot) appearing before it is * also omitted.</li> * <li><em>revision</em> is an optional non-negative integer, and may only * be included when <em>patch</em> is also included. When <em>revision</em> * is omitted, the {@code .} (dot) appearing before it is also omitted.</li> * <li><em>suffix</em> is an optional string. When <em>suffix</em> is * omitted, the {@code -} (hyphen-minus) appearing before it is also * omitted.</li> * </ul> */ public static class DefaultListener implements Listener { /** * A default instance of {@link DefaultListener} which is automatically * registered to receive version mismatch events. */ public static final DefaultListener INSTANCE = new DefaultListener(); /** * {@inheritDoc} * * <p> * The default implementation only throws an exception when the reported * version mismatch contains a mismatched <em>major</em> or * <em>minor</em> version component. For details about the syntax of the * input {@code version}, see the documentation for * {@link DefaultListener}.</p> */ @Override public void reportVersionMismatch(@NotNull VersionMismatchException ex) throws VersionMismatchException { if (!isMinorVersionMatch(ex)) { throw ex; } } /** * Determines if the reported version mismatch are a match when * considering only the <em>major</em> and <em>minor</em> version * components of the version strings. * * @param ex a {@link VersionMismatchException} instance containing * detailed information about the specific version mismatch detected * @return {@code true} if the <em>major</em> and <em>minor</em> version * components of the version strings match; otherwise, {@code false}. */ protected boolean isMinorVersionMatch(@NotNull VersionMismatchException ex) { String generatingToolVersion = ex.generatingToolVersion; if (generatingToolVersion != null) { if (!getMajorMinorVersion(VERSION).equals(getMajorMinorVersion(generatingToolVersion))) { return false; } } return getMajorMinorVersion(VERSION).equals(getMajorMinorVersion(ex.compileTimeRuntimeVersion)); } } /** * The list of listeners registered to receive notifications of mismatched * ANTLR versions. */ private static final Collection<Listener> listeners = new CopyOnWriteArraySet<Listener>(); 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(); } /** * Gets the currently executing version of the ANTLR 4 runtime library. * * <p> * This method provides runtime access to the {@link #VERSION} field, as * opposed to directly referencing the field as a compile-time constant.</p> * * @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. * * <p> * The version check is designed to detect the following two specific * scenarios.</p> * * <ul> * <li>The ANTLR Tool version used for code generation does not match the * currently executing runtime version.</li> * <li>The ANTLR Runtime version referenced at the time a parser was * compiled does not match the currently executing runtime version.</li> * </ul> * * <p> * Starting with ANTLR 4.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.</p> * * <p> * 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. A default instance of {@link DefaultListener} is notified * unless it is explicitly removed.</p> * * <p> * 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.</p> * * <p> * <strong>Additional note for target developers:</strong> 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.</p> * * @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); } } } /** * Gets the major and minor version numbers from a version string. For * details about the syntax of the input {@code version}, see the * documentation for {@link org.antlr.v4.runtime.RuntimeMetaData.DefaultListener}. * E.g., from x.y.z return x.y. * * @param version The complete version string. * @return A string of the form <em>major</em>.<em>minor</em> containing * only the major and minor components of the version string. */ @NotNull public static String getMajorMinorVersion(@NotNull String version) { int firstDot = version.indexOf('.'); int secondDot = firstDot >= 0 ? version.indexOf('.', firstDot + 1) : -1; int firstDash = version.indexOf('-'); int referenceLength = version.length(); if (secondDot >= 0) { referenceLength = Math.min(referenceLength, secondDot); } if (firstDash >= 0) { referenceLength = Math.min(referenceLength, firstDash); } return version.substring(0, referenceLength); } }