Package org.infernus.idea.checkstyle.checker

Source Code of org.infernus.idea.checkstyle.checker.CheckerFactory$CheckerFactoryWorker

package org.infernus.idea.checkstyle.checker;

import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.PropertyResolver;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infernus.idea.checkstyle.exception.CheckStylePluginException;
import org.infernus.idea.checkstyle.model.ConfigurationLocation;
import org.infernus.idea.checkstyle.util.IDEAUtilities;
import org.infernus.idea.checkstyle.util.ModuleClassPathBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.*;

/**
* A configuration factory and resolver for CheckStyle.
*/
public class CheckerFactory {

    private static final Log LOG = LogFactory.getLog(CheckerFactory.class);

    private final Map<ConfigurationLocation, CachedChecker> cache = new HashMap<ConfigurationLocation, CachedChecker>();
    private List<URL> thirdPartyClassPath = null;

    public CheckerContainer getChecker(final ConfigurationLocation location, final List<String> thirdPartyJars)
            throws CheckstyleException, IOException {
        thirdPartyClassPath = new ArrayList<URL>();
        for (String path : thirdPartyJars) {
            thirdPartyClassPath.add(new File(path).toURI().toURL());
        }
        return getChecker(location, null, null);
    }

    /**
     * Get a checker for a given configuration, with the default module classloader.
     *
     * @param location the location of the CheckStyle file.
     * @param module   the current module.
     * @return the checker for the module or null if it cannot be created.
     * @throws IOException         if the CheckStyle file cannot be resolved.
     * @throws CheckstyleException if CheckStyle initialisation fails.
     */
    public CheckerContainer getChecker(@NotNull final ConfigurationLocation location,
                                       @Nullable final Module module)
            throws CheckstyleException, IOException {
        return getChecker(location, module, null);
    }

    /**
     * Get a checker for a given configuration.
     *
     * @param location    the location of the CheckStyle file.
     * @param module      the current module.
     * @param classLoader class loader for CheckStyle use, or null to create a module class-loader if required.
     * @return the checker for the module or null if it cannot be created.
     * @throws IOException         if the CheckStyle file cannot be resolved.
     * @throws CheckstyleException if CheckStyle initialisation fails.
     */
    public CheckerContainer getChecker(@NotNull final ConfigurationLocation location,
                                       @Nullable final Module module,
                                       @Nullable final ClassLoader classLoader)
            throws CheckstyleException, IOException {
        final CachedChecker cachedChecker = getOrCreateCachedChecker(location, module, classLoader);
        if (cachedChecker != null) {
            return cachedChecker.getCheckerContainer();
        }
        return null;
    }

    private CachedChecker getOrCreateCachedChecker(final ConfigurationLocation location,
                                                   final Module module,
                                                   final ClassLoader classLoader)
            throws IOException, CheckstyleException {
        synchronized (cache) {
            if (cache.containsKey(location)) {
                final CachedChecker cachedChecker = cache.get(location);
                if (cachedChecker != null && cachedChecker.isValid()) {
                    return cachedChecker;
                } else {
                    if (cachedChecker != null) {
                        cachedChecker.getCheckerContainer().destroy();
                    }
                    cache.remove(location);
                }
            }

            final ListPropertyResolver propertyResolver = new ListPropertyResolver(location.getProperties());
            final CachedChecker checker = createChecker(location, module, propertyResolver,
                    moduleClassLoaderFrom(module, classLoader));
            if (checker != null) {
                cache.put(location, checker);
                return checker;
            }

            return null;
        }
    }

    private ClassLoader moduleClassLoaderFrom(final Module module, final ClassLoader classLoader)
            throws MalformedURLException {
        if (classLoader == null && module != null) {
            return moduleClassPathBuilder(module).build(module);
        } else if (classLoader == null && module == null && thirdPartyClassPath != null) {
            URL[] urls = new URL[thirdPartyClassPath.size()];
            return new URLClassLoader(thirdPartyClassPath.toArray(urls), this.getClass().getClassLoader());
        }
        return classLoader;
    }

    private ModuleClassPathBuilder moduleClassPathBuilder(@NotNull final Module module) {
        return ServiceManager.getService(module.getProject(), ModuleClassPathBuilder.class);
    }

    /**
     * Invalidate any cached checkers.
     */
    public void invalidateCache() {
        synchronized (cache) {
            for (CachedChecker cachedChecker : cache.values()) {
                cachedChecker.getCheckerContainer().destroy();
            }
            cache.clear();
        }
    }

    /**
     * Get the checker configuration for a given configuration.
     *
     * @param location the location of the CheckStyle file.
     * @param module   the current module.
     * @return a configuration.
     * @throws IllegalArgumentException if no checker with the given location exists and it cannot be created.
     */
    public Configuration getConfig(@NotNull final ConfigurationLocation location,
                                   @Nullable final Module module) {
        try {
            final CachedChecker checker = getOrCreateCachedChecker(location, module, null);
            if (checker != null) {
                return checker.getConfig();
            }
            return null;

        } catch (Exception e) {
            throw new IllegalStateException("Unable to find or create a checker from " + location, e);
        }
    }

    /**
     * Load the Checkstyle configuration in a separate thread.
     *
     * @param location           The location of the Checkstyle configuration file.
     * @param module             the current module.
     * @param resolver           the resolver.
     * @param contextClassLoader the context class loader, or null for default.
     * @return loaded Configuration object
     * @throws CheckstyleException If there was any error loading the configuration file.
     */
    private CachedChecker createChecker(final ConfigurationLocation location,
                                        final Module module,
                                        final PropertyResolver resolver,
                                        final ClassLoader contextClassLoader)
            throws CheckstyleException {

        if (LOG.isDebugEnabled()) {
            // debug information

            LOG.debug("Call to create new checker.");

            logProperties(resolver);
            logClassLoaders(contextClassLoader);
        }

        final CheckerFactoryWorker worker = new CheckerFactoryWorker(
                location, resolver, module, contextClassLoader);

        // Begin reading the configuration
        worker.start();

        // Wait for configuration thread to complete
        while (worker.isAlive()) {
            try {
                worker.join();
            } catch (InterruptedException e) {
                // Just be silent for now
            }
        }

        // Did the process of reading the configuration fail?
        if (worker.getResult() instanceof CheckstyleException) {
            final CheckstyleException checkstyleException = (CheckstyleException) worker.getResult();
            if (checkstyleException.getMessage().contains("Unable to instantiate DoubleCheckedLocking")) {
                return blacklistAndShowMessage(location, module, "checkstyle.double-checked-locking",
                        "Not compatible with CheckStyle 5.6+. Remove DoubleCheckedLocking.");
            }
            return blacklistAndShowMessage(location, module, "checkstyle.checker-failed", "Load failed due to {0}",
                    checkstyleException.getMessage());

        } else if (worker.getResult() instanceof IOException) {
            LOG.info("CheckStyle configuration could not be loaded: " + location.getLocation(),
                    (IOException) worker.getResult());
            return blacklistAndShowMessage(location, module, "checkstyle.file-not-found", "Not found: {0}", location.getLocation());

        } else if (worker.getResult() instanceof Throwable) {
            location.blacklist();
            throw new CheckstyleException("Could not load configuration", (Throwable) worker.getResult());
        }

        return (CachedChecker) worker.getResult();
    }

    private CachedChecker blacklistAndShowMessage(final ConfigurationLocation location,
                                                  final Module module,
                                                  final String messageKey,
                                                  final String messageFallback,
                                                  final Object... messageArgs) {
        if (!location.isBlacklisted()) {
            location.blacklist();

            final MessageFormat messageFormat = new MessageFormat(IDEAUtilities.getResource(messageKey, messageFallback));
            if (module != null) {
                IDEAUtilities.showError(module.getProject(), messageFormat.format(messageArgs));
            } else {
                throw new CheckStylePluginException(messageFormat.format(messageArgs));
            }
        }
        return null;
    }

    private void logClassLoaders(final ClassLoader contextClassLoader) {
        // Log classloaders, if known
        if (contextClassLoader != null) {
            ClassLoader currentLoader = contextClassLoader;
            while (currentLoader != null) {
                if (currentLoader instanceof URLClassLoader) {
                    LOG.debug("+ URLClassLoader: "
                            + currentLoader.getClass().getName());
                    final URLClassLoader urlLoader = (URLClassLoader)
                            currentLoader;
                    for (final URL url : urlLoader.getURLs()) {
                        LOG.debug(" + URL: " + url);
                    }
                } else {
                    LOG.debug("+ ClassLoader: " + currentLoader.getClass().getName());
                }

                currentLoader = currentLoader.getParent();
            }
        }
    }

    private void logProperties(final PropertyResolver resolver) {
        // Log properties if known
        if (resolver != null && resolver instanceof ListPropertyResolver) {
            final ListPropertyResolver listResolver = (ListPropertyResolver)
                    resolver;
            final Map<String, String> propertiesToValues
                    = listResolver.getPropertyNamesToValues();
            for (final String propertyName : propertiesToValues.keySet()) {
                final String propertyValue
                        = propertiesToValues.get(propertyName);
                LOG.debug("- Property: " + propertyName + "="
                        + propertyValue);
            }
        }
    }

    private class CheckerFactoryWorker extends Thread {
        private static final String TREE_WALKER_ELEMENT = "TreeWalker";
        private static final String SUPPRESSION_FILTER_ELEMENT = "SuppressionFilter";
        private static final String SUPPRESSION_FILTER_FILE = "file";
        private static final String IMPORT_CONTROL_ELEMENT = "ImportControl";
        private static final String IMPORT_CONTROL_FILE = "file";
        private static final String REGEXP_HEADER_ELEMENT = "RegexpHeader";
        private static final String REGEXP_HEADER_HEADERFILE = "headerFile";
        private static final String PROPERTY_ELEMENT = "property";
        private static final int DEFAULT_TAB_WIDTH = 8;

        private final Object[] threadReturn = new Object[1];

        private final ConfigurationLocation location;
        private final PropertyResolver resolver;
        private final Module module;

        public CheckerFactoryWorker(final ConfigurationLocation location,
                                    final PropertyResolver resolver,
                                    final Module module,
                                    final ClassLoader contextClassLoader) {
            this.location = location;
            this.resolver = resolver;
            this.module = module;


            if (contextClassLoader != null) {
                setContextClassLoader(contextClassLoader);
            } else {
                final ClassLoader loader = CheckerFactory.this.getClass().getClassLoader();
                setContextClassLoader(loader);
            }
        }

        public Object getResult() {
            return threadReturn[0];
        }

        public void run() {
            try {
                int tabWidth = DEFAULT_TAB_WIDTH;
                final Checker checker = new Checker();
                Configuration config = null;

                if (location != null) {
                    InputStream configurationInputStream = null;

                    try {
                        configurationInputStream = location.resolve();
                        config = ConfigurationLoader.loadConfiguration(
                                new InputSource(configurationInputStream), resolver, true);
                        if (config == null) {
                            // from the CS code this state appears to occur when there's no <module> element found
                            // in the input stream
                            throw new CheckstyleException("Couldn't find root module in " + location.getLocation());
                        }

                        replaceFilePaths(config);
                        tabWidth = findTabWidthFrom(config);
                        checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
                        checker.configure(config);

                    } finally {
                        if (configurationInputStream != null) {
                            try {
                                configurationInputStream.close();
                            } catch (Exception ignored) {
                                // ignored
                            }
                        }
                    }
                }

                if (config == null) {
                    config = new DefaultConfiguration("checker");
                }

                threadReturn[0] = new CachedChecker(new CheckerContainer(checker, tabWidth), config);

            } catch (Exception e) {
                threadReturn[0] = e;
            }
        }

        private int findTabWidthFrom(final Configuration rootElement) {
            for (final Configuration currentChild : rootElement.getChildren()) {
                if (TREE_WALKER_ELEMENT.equals(currentChild.getName())) {
                    for (Configuration configuration : currentChild.getChildren()) {
                        try {
                            if (PROPERTY_ELEMENT.equals(configuration.getName())
                                    && "tabWidth".equals(configuration.getAttribute("name"))) {
                                return Integer.parseInt(configuration.getAttribute("value"));
                            }
                        } catch (Exception ignored) {
                            // every property is required to have a name and value element
                        }
                    }
                }
            }
            return DEFAULT_TAB_WIDTH;
        }

        /**
         * Scans the configuration for elements with filenames and
         * replaces relative paths with absolute ones.
         *
         * @param rootElement the current configuration.
         * @throws CheckstyleException if configuration fails.
         */
        private void replaceFilePaths(final Configuration rootElement)
                throws CheckstyleException {

            if (!(rootElement instanceof DefaultConfiguration)) {
                LOG.warn("Root element is of unknown class: " + rootElement.getClass().getName());
                return;
            }

            for (final Configuration currentChild : rootElement.getChildren()) {
                if (SUPPRESSION_FILTER_ELEMENT.equals(currentChild.getName())) {
                    checkFilenameForProperty((DefaultConfiguration) rootElement,
                            currentChild, SUPPRESSION_FILTER_ELEMENT, SUPPRESSION_FILTER_FILE);

                } else if (REGEXP_HEADER_ELEMENT.equals(currentChild.getName())) {
                    checkFilenameForProperty((DefaultConfiguration) rootElement,
                            currentChild, REGEXP_HEADER_ELEMENT, REGEXP_HEADER_HEADERFILE);

                } else if (IMPORT_CONTROL_ELEMENT.equals(currentChild.getName())) {
                    checkFilenameForProperty((DefaultConfiguration) rootElement,
                            currentChild, IMPORT_CONTROL_ELEMENT, IMPORT_CONTROL_FILE);

                } else if (TREE_WALKER_ELEMENT.equals(currentChild.getName())) {
                    replaceFilePaths(currentChild);
                }
            }
        }

        private void checkFilenameForProperty(final DefaultConfiguration rootElement,
                                              final Configuration currentChild,
                                              final String elementName,
                                              final String propertyName)
                throws CheckstyleException {
            final String[] attributeNames = currentChild.getAttributeNames();
            if (attributeNames == null || Arrays.binarySearch(attributeNames, propertyName) < 0) {
                return;
            }

            final String fileName = currentChild.getAttribute(propertyName);
            if (fileName != null && !new File(fileName).exists()) {
                final String resolvedFile = findFile(fileName);

                rootElement.removeChild(currentChild);

                if (resolvedFile != null) {
                    rootElement.addChild(elementWithUpdatedFile(
                            resolvedFile, currentChild, elementName, propertyName));

                } else if (module != null) {
                    IDEAUtilities.showWarning(module.getProject(),
                            IDEAUtilities.getResource(String.format("checkstyle.not-found.%s", elementName),
                                    String.format("CheckStyle %s %s not found", elementName, propertyName)));
                }
            }
        }

        private DefaultConfiguration elementWithUpdatedFile(@NotNull final String filename,
                                                            @NotNull final Configuration originalElement,
                                                            @NotNull final String elementName,
                                                            @NotNull final String propertyName) {
            // The CheckStyle API won't allow attribute values to be changed, only appended to,
            // hence we must recreate the node.

            final DefaultConfiguration newFilter = new DefaultConfiguration(elementName);

            if (originalElement.getChildren() != null) {
                for (Configuration child : originalElement.getChildren()) {
                    newFilter.addChild(child);
                }
            }
            if (originalElement.getMessages() != null) {
                for (String messageKey : originalElement.getMessages().keySet()) {
                    newFilter.addMessage(messageKey, originalElement.getMessages().get(messageKey));
                }
            }
            if (originalElement.getAttributeNames() != null) {
                for (String attributeName : originalElement.getAttributeNames()) {
                    if (attributeName.equals(propertyName)) {
                        continue;
                    }
                    try {
                        newFilter.addAttribute(attributeName, originalElement.getAttribute(attributeName));
                    } catch (CheckstyleException e) {
                        LOG.error("Unable to copy attribute for " + elementName + ": " + attributeName, e);
                    }
                }
            }

            newFilter.addAttribute(propertyName, filename);

            return newFilter;
        }

        private String findFile(final String fileName) {
            File suppressionFile = null;

            if (fileName != null && (fileName.toLowerCase().startsWith("http://")
                    || fileName.toLowerCase().startsWith("https://"))) {
                return fileName;
            }

            // check relative to config
            if (location.getBaseDir() != null) {
                final File configFileRelativePath = new File(location.getBaseDir(), fileName);
                if (configFileRelativePath.exists()) {
                    suppressionFile = configFileRelativePath;
                }
            }

            if (module != null) {
                final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);

                // check module content roots
                if (suppressionFile == null && rootManager.getContentEntries().length > 0) {
                    for (final ContentEntry contentEntry : rootManager.getContentEntries()) {
                        final File contentEntryPath = new File(contentEntry.getFile().getPath(), fileName);
                        if (contentEntryPath.exists()) {
                            suppressionFile = contentEntryPath;
                            break;
                        }
                    }
                }

                // check module file
                if (suppressionFile == null && module.getModuleFile() != null) {
                    final File moduleRelativePath = new File(module.getModuleFile().getParent().getPath(), fileName);
                    if (moduleRelativePath.exists()) {
                        suppressionFile = moduleRelativePath;
                    }
                }

                // check project base dir
                if (suppressionFile == null && module.getProject().getBaseDir() != null) {
                    final File projectRelativePath = new File(module.getProject().getBaseDir().getPath(), fileName);
                    if (projectRelativePath.exists()) {
                        suppressionFile = projectRelativePath;
                    }
                }
            }

            if (suppressionFile != null) {
                return suppressionFile.getAbsolutePath();
            }
            return null;
        }
    }
}
TOP

Related Classes of org.infernus.idea.checkstyle.checker.CheckerFactory$CheckerFactoryWorker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.