org.acmsl.commons.version.VersionUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.acmsl.commons.version.VersionUtils.java

Source

//;-*- mode: java -*-
/*
                    ACM-SL Commons
    
Copyright (C) 2002-today  Jose San Leandro Armendariz
                          chous@acm-sl.org
    
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
version 2 of the License, or any later version.
    
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
    
You should have received a copy of the GNU General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-307  USA
    
Thanks to ACM S.L. for distributing this library under the LGPL license.
Contact info: jose.sanleandro@acm-sl.com
    
 ******************************************************************************
 *
 * Filename: VersionUtils.java
 *
 * Author: Jose San Leandro Armendariz
 *
 * Description: Provides some useful methods when working with Version
 *              information.
 *
 */
package org.acmsl.commons.version;

/*
 * Importing some ACM-SL classes.
 */
import org.acmsl.commons.Literals;
import org.acmsl.commons.patterns.Singleton;
import org.acmsl.commons.patterns.Utils;
import org.acmsl.commons.regexpplugin.Compiler;
import org.acmsl.commons.regexpplugin.MalformedPatternException;
import org.acmsl.commons.regexpplugin.Matcher;
import org.acmsl.commons.regexpplugin.MatchResult;
import org.acmsl.commons.regexpplugin.Pattern;
import org.acmsl.commons.regexpplugin.RegexpEngine;
import org.acmsl.commons.regexpplugin.RegexpEngineNotFoundException;
import org.acmsl.commons.regexpplugin.RegexpManager;
import org.acmsl.commons.regexpplugin.RegexpPluginMisconfiguredException;
import org.acmsl.commons.utils.ConversionUtils;
import org.acmsl.commons.utils.StringValidator;
import org.acmsl.commons.utils.StringUtils;

/*
 * Importing some JDK classes.
 */
import java.text.MessageFormat;

/*
 * Importing commons-logging classes.
 */
import org.apache.commons.logging.LogFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Provides some useful methods when working with {@link Version}
 * information.
 * @author <a href="mailto:chous@acm-sl.org">Jose San Leandro Armendariz</a>
 */
public class VersionUtils implements Utils, Singleton {
    /**
     * The version regexp.
     */
    public static final String VERSION_REGEXP = "(.*?)([0-9]+)\\.?([0-9]+|{0})?\\.?([0-9]+|{0})?\\.?(.*)?";
    //     ^      ^        ^      ^        ^      ^       ^    ^
    //  prefix  major   minor wildcard subminor wildcard rest  suffix

    /**
     * The default wildcard.
     */
    public static final String DEFAULT_WILDCARD = "x";

    /**
     * An empty String array.
     */
    protected static final String[] EMPTY_STRING_ARRAY = new String[0];

    /**
     * Singleton implemented to avoid the double-checked locking.
     */
    private static class VersionUtilsSingletonContainer {
        /**
         * The actual singleton.
         */
        public static final VersionUtils SINGLETON = new VersionUtils();
    }

    /**
     * Compiler instance.
     */
    private volatile static Compiler m__Compiler;

    /**
     * The version pattern.
     */
    private volatile static Pattern m__VersionPattern;

    /**
     * Retrieves a <code>VersionUtils</code> instance.
     * @return such instance.
     */
    @NotNull
    public static VersionUtils getInstance() {
        @NotNull
        final VersionUtils result = VersionUtilsSingletonContainer.SINGLETON;

        synchronized (VersionUtils.class) {
            initialize();
        }

        return result;
    }

    /**
     * Protected constructor to avoid accidental instantiation.
     */
    protected VersionUtils() {
    }

    /**
     * Specifies the regexp compiler.
     * @param compiler the compiler.
     */
    protected final static void immutableSetCompiler(@NotNull final Compiler compiler) {
        m__Compiler = compiler;
    }

    /**
     * Specifies the regexp compiler.
     * @param compiler the compiler.
     */
    @SuppressWarnings("unused")
    protected static void setCompiler(@NotNull final Compiler compiler) {
        immutableSetCompiler(compiler);
    }

    /**
     * Retrieves the regexp compiler.
     * @return such compiler.
     */
    @Nullable
    protected static final Compiler immutableGetCompiler() {
        return m__Compiler;
    }

    /**
     * Retrieves the regexp compiler.
     * @return such compiler.
     */
    @NotNull
    protected static Compiler getCompiler() {
        @Nullable
        Compiler result = immutableGetCompiler();

        if (result == null) {
            initialize();
            result = immutableGetCompiler();
        }

        return result;
    }

    /**
     * Initializes the regex engine.
     */
    protected static void initialize() {
        @Nullable
        RuntimeException t_Exception = null;

        @Nullable
        Compiler t_Compiler = immutableGetCompiler();

        if (t_Compiler == null) {
            try {
                t_Compiler = createCompiler(RegexpManager.getInstance());

                immutableSetCompiler(t_Compiler);

                try {
                    immutableSetVersionPattern(immutableCompileVersionPattern(DEFAULT_WILDCARD, t_Compiler,
                            StringUtils.getInstance()));
                } catch (final MalformedPatternException exception) {
                    /*
                     * This should never happen. It's a compile-time
                     * error not detected by the compiler, but it's
                     * nothing dynamic. So, if it fails, fix it once
                     * and forget.
                     */
                    LogFactory.getLog(StringUtils.class).error(Literals.INVALID_SUB_PACKAGE_PATTERN, exception);

                    t_Exception = exception;
                }
            } catch (final RegexpEngineNotFoundException exception) {
                LogFactory.getLog(StringUtils.class).error(Literals.NO_REGEXP_ENGINE_FOUND, exception);

                t_Exception = exception;
            } catch (final Throwable throwable) {
                LogFactory.getLog(StringUtils.class).fatal(Literals.UNKNOWN_ERROR, throwable);

                t_Exception = new RuntimeException(Literals.COULD_NOT_INITIALIZE_STRING_UTILS, throwable);
            }

            if (t_Exception != null) {
                throw t_Exception;
            }
        }
    }

    /**
     * Specifies the version pattern.
     * @param pattern the pattern.
     */
    private static void immutableSetVersionPattern(@NotNull final Pattern pattern) {
        m__VersionPattern = pattern;
    }

    /**
     * Specifies the version pattern.
     * @param pattern the pattern.
     */
    @SuppressWarnings("unused")
    protected static void setVersionPattern(@NotNull final Pattern pattern) {
        immutableSetVersionPattern(pattern);
    }

    /**
     * Retrieves the version pattern.
     * @return such pattern.
     */
    @NotNull
    public static Pattern getVersionPattern() {
        return m__VersionPattern;
    }

    /**
     * Retrieves the version pattern.
     * @param wildcard the identifier used to identify "anything goes".
     * @return such pattern.
     */
    @NotNull
    public static Pattern getVersionPattern(@NotNull final String wildcard) {
        @NotNull
        final Pattern result;

        if (!DEFAULT_WILDCARD.equalsIgnoreCase(wildcard)) {
            result = immutableCompileVersionPattern(wildcard, getCompiler(), StringUtils.getInstance());
        } else {
            result = getVersionPattern();
        }

        return result;
    }

    /**
     * Compiles a version pattern using given wildcard.
     * @param wildcard the identifier used to identify "anything goes".
     * @param compiler the regexp compiler.
     * @param stringUtils the <code>StringUtils</code> instance.
     * @return such pattern.
     */
    @NotNull
    protected static final Pattern immutableCompileVersionPattern(@NotNull final String wildcard,
            @NotNull final Compiler compiler, @NotNull final StringUtils stringUtils) {
        @Nullable
        Pattern result = null;

        @NotNull
        final MessageFormat t_Formatter = new MessageFormat(VERSION_REGEXP);

        try {
            result = compiler.compile(t_Formatter.format(new Object[] { stringUtils.escapeRegexp(wildcard) }));
        } catch (@NotNull final MalformedPatternException exception) {
            /*
             * This should never happen. It's a compile-time
             * error not detected by the compiler, but it's
             * nothing dynamic. So, if it fails, fix it once
             * and forget.
             */
            LogFactory.getLog(VersionUtils.class).error("Invalid version pattern", exception);
        }

        return result;
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param version the version.
     * @param family the family.
     * @return <code>true</code> if the version is compatible.
     */
    public boolean matches(@NotNull final String version, @NotNull final String family) {
        return matches(version, family, DEFAULT_WILDCARD);
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param version the version.
     * @param family the family.
     * @param wildcard the identifier used to identify "anything goes".
     * @return <code>true</code> if the version is compatible.
     */
    public boolean matches(@NotNull final String version, @NotNull final String family,
            @NotNull final String wildcard) {
        boolean result = false;

        @NotNull
        final Pattern t_VersionPattern = getVersionPattern(wildcard);

        try {
            @NotNull
            final Matcher t_Matcher = createMatcher(RegexpManager.getInstance());

            result = matches(version, family, wildcard, t_VersionPattern, t_Matcher, StringValidator.getInstance());
        } catch (@NotNull final RegexpEngineNotFoundException missingEngine) {
            LogFactory.getLog(VersionUtils.class).fatal("Cannot find a suitable regex engine", missingEngine);
        } catch (@NotNull final RegexpPluginMisconfiguredException misconfiguredEngine) {
            LogFactory.getLog(VersionUtils.class).fatal("Cannot initialize regex plugin", misconfiguredEngine);
        }

        return result;
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param version the version.
     * @param family the family.
     * @param wildcard the identifier used to identify "anything goes".
     * @param pattern the version pattern.
     * @param matcher the matcher.
     * @param stringValidator the <code>StringValidator</code> instance.
     * @return <code>true</code> if the version is compatible.
     */
    protected boolean matches(@NotNull final String version, @NotNull final String family,
            @NotNull final String wildcard, @NotNull final Pattern pattern, @NotNull final Matcher matcher,
            @NotNull final StringValidator stringValidator) {
        boolean result = false;

        @NotNull
        final String[] t_astrVersion = parseVersion(version, pattern, matcher, stringValidator);

        @NotNull
        final String[] t_astrFamily = parseVersion(family, pattern, matcher, stringValidator);

        if ((t_astrVersion.length >= 1) && (t_astrFamily.length >= 1)) {
            @Nullable
            final String t_strVersionMajor = t_astrVersion[0];
            @Nullable
            final String t_strFamilyMajor = t_astrFamily[0];
            String t_strVersionMinor = "";
            String t_strFamilyMinor = "";
            String t_strVersionSubminor = "";
            String t_strFamilySubminor = "";

            if (t_astrVersion.length >= 2) {
                t_strVersionMinor = t_astrVersion[1];
            }
            if (t_astrFamily.length >= 2) {
                t_strFamilyMinor = t_astrFamily[1];
            }
            if (t_astrVersion.length >= 3) {
                t_strVersionSubminor = t_astrVersion[2];
            }
            if (t_astrFamily.length >= 3) {
                t_strFamilySubminor = t_astrFamily[2];
            }

            result = matches(t_strVersionMajor, t_strVersionMinor, t_strVersionSubminor, t_strFamilyMajor,
                    t_strFamilyMinor, t_strFamilySubminor, wildcard);
        }

        return result;
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param version the version.
     * @param pattern the version pattern.
     * @param matcher the matcher.
     * @param stringValidator the <code>StringValidator</code> instance.
     * @return <code>true</code> if the version is compatible.
     */
    @NotNull
    protected String[] parseVersion(final String version, final Pattern pattern, final Matcher matcher,
            final StringValidator stringValidator) {
        String[] result = EMPTY_STRING_ARRAY;

        try {
            @Nullable
            final MatchResult t_MatchResult;

            if ((!stringValidator.isEmpty(version)) && (matcher.contains(version, pattern))) {
                t_MatchResult = matcher.getMatch();

                if (t_MatchResult != null) {
                    result = new String[] { t_MatchResult.group(2), t_MatchResult.group(3),
                            t_MatchResult.group(4) };
                }
            }
        } catch (final MalformedPatternException exception) {
            LogFactory.getLog(VersionUtils.class)
                    .error(Literals.MALFORMED_PATTERN_POSSIBLY_DUE_TO_QUOTE_SYMBOL_CONFLICT, exception);
        } catch (final RegexpEngineNotFoundException exception) {
            /*
             * This exception is thrown only if no regexp library is available
             * at runtime. Not only this one, but any method provided by this
             * class that use regexps will not work.
             */
            LogFactory.getLog(getClass()).error(Literals.CANNOT_FIND_ANY_REGEXP_ENGINE, exception);
        }

        return result;
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param major the major version information.
     * @param minor the minor version information.
     * @param subminor the subminor version information.
     * @param familyMajor the family's major version.
     * @param familyMinor the family's minor version.
     * @param familySubminor the family's subminor version.
     * @return {@code true} if the versions match.
     */
    public boolean matches(@NotNull final String major, @Nullable final String minor,
            @Nullable final String subminor, @NotNull final String familyMajor, @Nullable final String familyMinor,
            @Nullable final String familySubminor) {
        return matches(major, minor, subminor, familyMajor, familyMinor, familySubminor, DEFAULT_WILDCARD);
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param major the major version information.
     * @param minor the minor version information.
     * @param subminor the subminor version information.
     * @param familyMajor the family's major version.
     * @param familyMinor the family's minor version.
     * @param familySubminor the family's subminor version.
     * @param wildcard the identifier used to identify "anything goes".
     * @return {@code true} if the versions match.
     */
    public boolean matches(@NotNull final String major, @Nullable final String minor,
            @Nullable final String subminor, @NotNull final String familyMajor, @Nullable final String familyMinor,
            @Nullable final String familySubminor, @NotNull final String wildcard) {
        return matches(major, minor, subminor, familyMajor, familyMinor, familySubminor, wildcard,
                ConversionUtils.getInstance());
    }

    /**
     * Checks whether given version value matches a concrete version family.
     * @param major the major version information.
     * @param minor the minor version information.
     * @param subminor the subminor version information.
     * @param familyMajor the family's major version.
     * @param familyMinor the family's minor version.
     * @param familySubminor the family's subminor version.
     * @param wildcard the identifier used to identify "anything goes".
     * @param conversionUtils the <code>ConversionUtils</code> instance.
     * @return {@code true} if the versions match.
     */
    protected boolean matches(@NotNull final String major, @Nullable final String minor,
            @Nullable final String subminor, @NotNull final String familyMajor, @Nullable final String familyMinor,
            @Nullable final String familySubminor, @NotNull final String wildcard,
            @NotNull final ConversionUtils conversionUtils) {
        boolean result = false;

        if (versionNumbersMatch(major, familyMajor, wildcard, conversionUtils)) {
            result = true;

            if ((minor != null) && (familyMinor != null)) {
                result = versionNumbersMatch(minor, familyMinor, wildcard, conversionUtils);
            }

            if (result) {
                if ((subminor != null) && (familySubminor != null)) {
                    result = versionNumbersMatch(subminor, familySubminor, wildcard, conversionUtils);
                }
            }
        }

        return result;
    }

    /**
     * Checks whether concrete version numbers match.
     * @param number the version number.
     * @param familyNumber the family number.
     * @return {@code true} if the versions match.
     */
    public boolean versionNumbersMatch(@NotNull final String number, @NotNull final String familyNumber) {
        return versionNumbersMatch(number, familyNumber, DEFAULT_WILDCARD);
    }

    /**
     * Checks whether concrete version numbers match.
     * @param number the version number.
     * @param familyNumber the family number.
     * @param wildcard the identifier used to identify "anything goes".
     * @return {@code true} if the versions match.
     */
    public boolean versionNumbersMatch(@NotNull final String number, @NotNull final String familyNumber,
            @NotNull final String wildcard) {
        return versionNumbersMatch(number, familyNumber, wildcard, ConversionUtils.getInstance());
    }

    /**
     * Checks whether concrete version numbers match.
     * @param number the version number.
     * @param familyNumber the family number.
     * @param wildcard the identifier used to identify "anything goes".
     * @param conversionUtils the <code>ConversionUtils</code> instance.
     * @return {@code true} if the versions match.
     */
    protected boolean versionNumbersMatch(@NotNull final String number, @NotNull final String familyNumber,
            @NotNull final String wildcard, @NotNull final ConversionUtils conversionUtils) {
        return ((wildcard.equalsIgnoreCase(number)) || (wildcard.equalsIgnoreCase(familyNumber))
                || (conversionUtils.toInt(number) == conversionUtils.toInt(familyNumber)));
    }

    /**
     * Creates the compiler.
     * @param regexpManager the <code>RegexpManager</code> instance.
     * @return the regexp compiler.
     * @throws RegexpEngineNotFoundException if a suitable instance
     * cannot be created.
     * @throws RegexpPluginMisconfiguredException if RegexpPlugin is
     * misconfigured.
     */
    @NotNull
    protected static synchronized Compiler createCompiler(@NotNull final RegexpManager regexpManager)
            throws RegexpEngineNotFoundException, RegexpPluginMisconfiguredException {
        return createCompiler(regexpManager.getEngine());
    }

    /**
     * Creates the compiler.
     * @param regexpEngine the RegexpEngine instance.
     * @return the regexp compiler.
     */
    @NotNull
    protected static synchronized Compiler createCompiler(@NotNull final RegexpEngine regexpEngine)
            throws RegexpEngineNotFoundException {
        @NotNull
        final Compiler result = regexpEngine.createCompiler();

        result.setCaseSensitive(false);

        return result;
    }

    /**
     * Creates the matcher.
     * @param regexpManager the RegexpManager instance.
     * @return the regexp matcher.
     * @throws RegexpEngineNotFoundException if a suitable instance
     * cannot be created.
     * @throws RegexpPluginMisconfiguredException if RegexpPlugin is
     * misconfigured.
     */
    @NotNull
    protected static synchronized Matcher createMatcher(@NotNull final RegexpManager regexpManager)
            throws RegexpEngineNotFoundException, RegexpPluginMisconfiguredException {
        return createMatcher(regexpManager.getEngine());
    }

    /**
     * Creates the matcher.
     * @param regexpEngine the RegexpEngine instance.
     * @return the regexp matcher.
     */
    @NotNull
    protected static synchronized Matcher createMatcher(final RegexpEngine regexpEngine)
            throws RegexpEngineNotFoundException {
        return regexpEngine.createMatcher();
    }
}