net.sf.eclipsecs.core.builder.CheckerFactory.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.eclipsecs.core.builder.CheckerFactory.java

Source

//============================================================================
//
// Copyright (C) 2002-2014  David Schneider, Lars Kdderitzsch
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//============================================================================

package net.sf.eclipsecs.core.builder;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.sf.eclipsecs.core.CheckstylePlugin;
import net.sf.eclipsecs.core.config.CheckstyleConfigurationFile;
import net.sf.eclipsecs.core.config.ConfigurationReader;
import net.sf.eclipsecs.core.config.ConfigurationReader.AdditionalConfigData;
import net.sf.eclipsecs.core.config.ICheckConfiguration;
import net.sf.eclipsecs.core.config.configtypes.IContextAware;
import net.sf.eclipsecs.core.util.CheckstylePluginException;

import org.apache.commons.io.IOUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.xml.sax.InputSource;

import com.google.common.collect.MapMaker;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
import com.puppycrawl.tools.checkstyle.PropertyResolver;
import com.puppycrawl.tools.checkstyle.TreeWalker;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.api.FileSetCheck;

/**
 * Factory class to create (and cache) checker objects.
 *
 * @author Lars Kdderitzsch
 */
public final class CheckerFactory {

    //
    // class attributes
    //

    /** Map containing the configured checkers. */
    private static Map<String, Checker> sCheckerMap;

    /** Map containing the modification times of configs. */
    private static Map<String, Long> sModifiedMap;

    /** Map containing additional data about the check configurations. */
    private static Map<String, AdditionalConfigData> sAdditionalDataMap;

    /** the shared classloader for the checkers. */
    private static ProjectClassLoader sSharedClassLoader;

    //
    // static initializer
    //

    /**
     * Initialize the cache.
     */
    static {

        // Use synchronized collections to avoid concurrent modification
        sCheckerMap = new MapMaker().softValues().makeMap();
        sModifiedMap = Collections.synchronizedMap(new HashMap<String, Long>());
        sAdditionalDataMap = Collections.synchronizedMap(new HashMap<String, AdditionalConfigData>());

        sSharedClassLoader = new ProjectClassLoader();
    }

    //
    // constructors
    //

    /**
     * Hidden utility class constructor.
     */
    private CheckerFactory() {
        // noop
    }

    //
    // methods
    //

    /**
     * Creates a checker for a given configuration file.
     *
     * @param config
     *            the check configuration data
     * @param project
     *            the project to create the checker for
     * @return the checker for the given configuration file
     * @throws CheckstyleException
     *             the configuration file had errors
     * @throws CheckstylePluginException
     *             the configuration could not be read
     */
    public static Checker createChecker(ICheckConfiguration config, IProject project)
            throws CheckstyleException, CheckstylePluginException {

        String cacheKey = getCacheKey(config, project);

        CheckstyleConfigurationFile configFileData = config.getCheckstyleConfiguration();
        Checker checker = tryCheckerCache(cacheKey, configFileData.getModificationStamp());

        // workaround for issue 377
        applyTreeWalkerCacheWorkaround(checker);

        // no cache hit
        if (checker == null) {
            PropertyResolver resolver = configFileData.getPropertyResolver();

            // set the project context if the property resolver needs the
            // context
            if (resolver instanceof IContextAware) {
                ((IContextAware) resolver).setProjectContext(project);
            }

            InputSource in = null;
            try {
                in = configFileData.getCheckConfigFileInputSource();
                checker = createCheckerInternal(in, resolver, project);
            } finally {
                IOUtils.closeQuietly(in.getByteStream());
            }

            // store checker in cache
            Long modified = new Long(configFileData.getModificationStamp());
            sCheckerMap.put(cacheKey, checker);
            sModifiedMap.put(cacheKey, modified);
        }

        return checker;
    }

    /**
     * Determines the additional data for a given configuration file.
     *
     * @param config
     *            the check configuration
     * @param project
     *            the project to create the checker for
     * @return the checker for the given configuration file
     * @throws CheckstylePluginException
     *             the configuration could not be read
     */
    public static ConfigurationReader.AdditionalConfigData getAdditionalData(ICheckConfiguration config,
            IProject project) throws CheckstylePluginException {

        String cacheKey = getCacheKey(config, project);

        AdditionalConfigData additionalData = sAdditionalDataMap.get(cacheKey);

        // no cache hit - create the additional data
        if (additionalData == null) {
            CheckstyleConfigurationFile configFileData = config.getCheckstyleConfiguration();

            InputSource in = null;
            try {
                in = configFileData.getCheckConfigFileInputSource();
                additionalData = ConfigurationReader.getAdditionalConfigData(in);
            } finally {
                IOUtils.closeQuietly(in.getByteStream());
            }

            sAdditionalDataMap.put(cacheKey, additionalData);
        }

        return additionalData;
    }

    /**
     * Returns the shared classloader which is used by all checkers created by this factory.
     *
     * @return the shared classloader
     */
    public static ProjectClassLoader getSharedClassLoader() {
        return sSharedClassLoader;
    }

    /**
     * Cleans up the checker cache.
     */
    public static void cleanup() {
        sCheckerMap.clear();
        sModifiedMap.clear();
        sAdditionalDataMap.clear();
    }

    /**
     * Build a unique cache key for the check configuration.
     *
     * @param config
     *            the check configuration
     * @param project
     *            the project being checked
     * @return the unique cache key
     * @throws CheckstylePluginException
     *             error getting configuration file data
     */
    private static String getCacheKey(ICheckConfiguration config, IProject project)
            throws CheckstylePluginException {
        CheckstyleConfigurationFile configFileData = config.getCheckstyleConfiguration();

        URL configLocation = configFileData.getResolvedConfigFileURL();
        String checkConfigName = config.getName() + "#" + (config.isGlobal() ? "Global" : "Local");

        String cacheKey = project.getName() + "#" + configLocation + "#" + checkConfigName; //$NON-NLS-1$

        return cacheKey;
    }

    /**
     * Tries to reuse an already configured checker for this configuration.
     *
     * @param config
     *            the configuration file
     * @param cacheKey
     *            the key for cache access
     * @return the cached checker or null
     */
    private static Checker tryCheckerCache(String cacheKey, long modificationStamp) {

        // try the cache
        Checker checker = sCheckerMap.get(cacheKey);

        // if cache hit
        if (checker != null) {

            // compare modification times of the configs
            Long oldTime = sModifiedMap.get(cacheKey);
            Long newTime = new Long(modificationStamp);

            // no match - remove checker from cache
            if (oldTime == null || oldTime.compareTo(newTime) != 0) {
                checker = null;
                sCheckerMap.remove(cacheKey);
                sModifiedMap.remove(cacheKey);
                sAdditionalDataMap.remove(cacheKey);
            }
        }
        return checker;
    }

    /**
     * Creates a new checker and configures it with the given configuration file.
     *
     * @param input
     *            the input source for the configuration file
     * @param configFileUri
     *            the URI of the configuration file, or <code>null</code> if it could not be determined
     * @param propResolver
     *            a property resolver null
     * @param project
     *            the project
     * @return the newly created Checker
     * @throws CheckstyleException
     *             an exception during the creation of the checker occured
     */
    private static Checker createCheckerInternal(InputSource input, PropertyResolver propResolver, IProject project)
            throws CheckstyleException, CheckstylePluginException {

        // load configuration
        Configuration configuration = ConfigurationLoader.loadConfiguration(input, propResolver, true);

        // create and configure checker
        Checker checker = new Checker();
        checker.setModuleClassLoader(CheckstylePlugin.getDefault().getAddonExtensionClassLoader());
        try {
            checker.setCharset(project.getDefaultCharset());
        } catch (UnsupportedEncodingException e) {
            CheckstylePluginException.rethrow(e);
        } catch (CoreException e) {
            CheckstylePluginException.rethrow(e);
        }

        // set the eclipse platform locale
        Locale platformLocale = CheckstylePlugin.getPlatformLocale();
        checker.setLocaleLanguage(platformLocale.getLanguage());
        checker.setLocaleCountry(platformLocale.getCountry());
        checker.setClassloader(sSharedClassLoader);

        checker.configure(configuration);

        // reset the basedir if it is set so it won't get into the plugins way
        // of determining workspace resources from checkstyle reported file
        // names, see
        // https://sourceforge.net/tracker/?func=detail&aid=2880044&group_id=80344&atid=559497
        if (checker.getBasedir() != null) {
            checker.setBasedir(null);
        }

        return checker;
    }

    private static void applyTreeWalkerCacheWorkaround(Checker checker) {
        try {
            Field fileSetChecksField = Checker.class.getDeclaredField("mFileSetChecks");
            fileSetChecksField.setAccessible(true);

            @SuppressWarnings("unchecked")
            List<FileSetCheck> fileSetChecks = (List<FileSetCheck>) fileSetChecksField.get(checker);

            for (FileSetCheck fsc : fileSetChecks) {
                if (fsc instanceof TreeWalker) {
                    TreeWalker tw = (TreeWalker) fsc;

                    // only reset when we have a "default cache" without an actual configured cache file.
                    Field cacheField = TreeWalker.class.getDeclaredField("mCache");
                    cacheField.setAccessible(true);

                    Object cache = cacheField.get(tw);

                    Field detailsFileField = cache.getClass().getDeclaredField("mDetailsFile");
                    detailsFileField.setAccessible(true);

                    if (detailsFileField.get(cache) == null) {
                        tw.setCacheFile(null);
                    }
                }
            }
        } catch (Exception e) {
            // Ah, what the heck, I tried. Now get out of my way.
        }
    }
}