Package org.jnode.build.dependencies

Source Code of org.jnode.build.dependencies.PluginDependencyChecker$JarFiles

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

package org.jnode.build.dependencies;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;
import org.apache.bcel.util.ClassPath;
import org.apache.bcel.util.SyntheticRepository;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.FileSet;
import org.jnode.build.AbstractPluginTask;
import org.jnode.plugin.FragmentDescriptor;
import org.jnode.plugin.PluginDescriptor;
import org.jnode.plugin.PluginPrerequisite;
import org.jnode.plugin.PluginReference;

/**
* Task used to check dependencies between plugins.
*
* @author Fabien DUMINY
* @author Sebastian Lohmeier
*/
public class PluginDependencyChecker extends AbstractPluginTask {
    private List<FileSet> descriptorSets = new ArrayList<FileSet>(256);
    private List<FileSet> pluginSets = new ArrayList<FileSet>(256);

    private List<Plugin> plugins;
    private List<Fragment> fragments;
    private Set<Plugin> systemPlugins;

    public FileSet createDescriptors() {
        final FileSet fs = new FileSet();
        descriptorSets.add(fs);
        return fs;
    }

    public FileSet createPlugins() {
        final FileSet fs = new FileSet();
        pluginSets.add(fs);
        return fs;
    }

    /**
     * @throws BuildException
     * @see org.apache.tools.ant.Task#execute()
     */
    public void execute() throws BuildException {
        if (descriptorSets.isEmpty()) {
            throw new BuildException("At least 1 descriptors element is required");
        }

        if (pluginSets.isEmpty()) {
            throw new BuildException("At least 1 plugins element is required");
        }

        Map<String, Plugin> containedClasses = new HashMap<String, Plugin>();
        if (createPlugins(containedClasses) > 0) {
            System.err.println("\nCan't proceed with more in-depth checks. Please fix above errors first.");
        } else {
            analyzePlugins(containedClasses, plugins);
        }
    }

    private Plugin findPlugin(String fullPluginId) {
        // TODO ... consider replacing this with a map.
        for (Plugin plugin : plugins) {
            if (plugin.fullPluginId.equals(fullPluginId)) {
                return plugin;
            }
        }
        return null;
    }

    private Set<Fragment> findFragmentsOwnedByPlugin(Plugin plugin) {
        String fullPluginId = plugin.getFullPluginId();
        Set<Fragment> results = new HashSet<Fragment>();
        for (Fragment fragment : fragments) {
            if (fragment.getFullPluginIdOfOwningPlugin().equals(fullPluginId)) {
                results.add(fragment);
            }
        }
        return results;
    }

    private Collection<Plugin> findSystemPlugins() {
        if (systemPlugins != null) {
            return systemPlugins;
        } else {
            systemPlugins = new HashSet<Plugin>();
            for (Plugin plugin : plugins) {
                if (plugin.isSystemPlugin()) {
                    systemPlugins.add(plugin);
                }
            }
            return systemPlugins;
        }
    }

    /**
     * @param containedClasses
     * @param plugins
     */
    private void analyzePlugins(Map<String, Plugin> containedClasses, List<Plugin> plugins) {
        Collections.sort(plugins);
        for (Plugin plugin : plugins) {
            analyzePlugin(containedClasses, plugin);
        }
    }

    /**
     * @param containedClasses
     * @param plugin
     */
    private void analyzePlugin(Map<String, Plugin> containedClasses, Plugin plugin) {
        plugin.verifyDescriptor(containedClasses);
    }

    /**
     * Creates the Plugin instances, reading and parsing the XML descriptors
     * and the JAR files of all plugins.
     *
     * @param containedClasses
     * @return The number of errors found.
     */
    private int createPlugins(Map<String, Plugin> containedClasses) {
        JarFiles jarFiles = new JarFiles(pluginSets);
        plugins = new ArrayList<Plugin>();
        fragments = new ArrayList<Fragment>();
        int errorCount = 0;
        for (File descFile : getDescriptorFiles()) {
            Plugin plugin = processPlugin(descFile, jarFiles);
            if (plugin != null) {
                plugins.add(plugin);

                if (plugin instanceof Fragment) {
                    fragments.add((Fragment) plugin);
                }

                for (String containedClass : plugin.containedClasses) {
                    if (!containedClasses.containsKey(containedClass)) {
                        containedClasses.put(containedClass, plugin);
                    } else {
                        System.err.println("WARNING: Class " + containedClass + " contained in both plugins:");
                        System.err.println(containedClasses.get(containedClass).getFullPluginId() + " and " +
                            plugin.getFullPluginId());
                        errorCount++;
                    }
                }
            }
        }
        return errorCount;
    }

    protected File[] getDescriptorFiles() {
        List<File> files = new ArrayList<File>();
        for (FileSet fs : descriptorSets) {
            final DirectoryScanner ds = fs.getDirectoryScanner(getProject());
            for (String incFile : ds.getIncludedFiles()) {
                files.add(new File(ds.getBasedir(), incFile));
            }
        }
        return files.toArray(new File[files.size()]);
    }


    /**
     * @param jarFiles
     * @param descriptor
     * @throws BuildException
     */
    protected Plugin processPlugin(File descriptor, JarFiles jarFiles)
        throws BuildException {
        try {
            final PluginDescriptor descr = readDescriptor(descriptor);
            String fullPluginId = descr.getId() + "_" + descr.getVersion();

            if (!jarFiles.hasPluginJar(fullPluginId)) {
                System.out.println("WARNING: no Jar file found for plugin " + fullPluginId);
                return null;
            }

            if (descr instanceof FragmentDescriptor) {
                return new Fragment(jarFiles.getPluginJar(fullPluginId), (FragmentDescriptor) descr);
            } else {
                return new Plugin(jarFiles.getPluginJar(fullPluginId), descr);
            }
        } catch (IOException e) {
            throw new BuildException(e);
        }
    }

    private class Plugin implements Comparable<Plugin> {
        private final String classSuffix = ".class";
        private final Pattern typePattern = Pattern.compile("\u004C[a-zA-Z_0-9/\u002E\u0024]*;");
        protected final String fullPluginId;
        protected final PluginDescriptor descr;
        protected final Set<String> containedClasses = new HashSet<String>();
        protected final Map<String, Set<String>> usedClasses = new HashMap<String, Set<String>>();

        private Plugin(JarFile jarFile, PluginDescriptor descr) {
            this.fullPluginId = createFullPluginId(descr.getPluginReference());
            this.descr = descr;
            initContainedClasses(jarFile);
            initUsedClasses(jarFile);
        }

        private boolean isSystemPlugin() {
            return descr.isSystemPlugin();
        }

        protected String createFullPluginId(PluginReference reference) {
            return reference.getId() + "_" + reference.getVersion();
        }

        protected String getFullPluginId() {
            return fullPluginId;
        }

        protected void verifyDescriptor(Map<String, Plugin> containedClasses) {
            boolean errorFound = false;
            StringBuffer buffer = new StringBuffer("The plugin " + fullPluginId + "");
            buffer.append("\n------------------------------------------------------------------\n");

            errorFound |= collectUnmatchedDependencies(buffer, containedClasses);
            errorFound |= isNotUseful(buffer);

            Set<String> allClasses = getAllUsedClasses();
            errorFound |= assortClassesContainedInImportedPlugins(buffer, this, true, allClasses);

            for (Plugin plugin : findSystemPlugins()) {
                allClasses.removeAll(plugin.containedClasses);
            }

            //errorFound |= isMissingImportDeclarations(buffer, allClasses);

            if (errorFound) {
                System.out.println(buffer.toString());
            }
        }

        private boolean isNotUseful(StringBuffer buffer) {
            if (descr.getRuntime() != null)
                return false;
            if ((descr.getExtensions() != null) && (descr.getExtensions().length > 0))
                return false;
            if (descr.hasCustomPluginClass())
                return false;
            buffer.append(" * neither exports classes, nor is an extension, nor provides a plugin class\n");
            return true;
        }

        /**
         * @param buffer
         * @param containedClasses
         */
        private boolean collectUnmatchedDependencies(StringBuffer buffer, Map<String, Plugin> containedClasses) {
            Map<String, List<String>> unmatchedDependencies = new HashMap<String, List<String>>();
            for (String usingClass : usedClasses.keySet()) {
                for (String className : usedClasses.get(usingClass)) {
                    if (!containedClasses.containsKey(className)) {
                        if (unmatchedDependencies.containsKey(className)) {
                            unmatchedDependencies.get(className).add(usingClass);
                        } else {
                            List<String> list = new ArrayList<String>();
                            list.add(usingClass);
                            unmatchedDependencies.put(className, list);
                        }
                    }
                }
            }

            if (!unmatchedDependencies.isEmpty()) {
                dumpUnmatchedDependencies(buffer, unmatchedDependencies);
                return true;
            } else {
                return false;
            }
        }

        /**
         * @param buffer
         * @param unmatchedDependencies
         */
        private void dumpUnmatchedDependencies(StringBuffer buffer, Map<String, List<String>> unmatchedDependencies) {
            buffer.append(" * has unresolved class dependencies:\n");
            List<String> keys = new ArrayList<String>(unmatchedDependencies.keySet());
            Collections.sort(keys);
            for (String className : keys) {
                List<String> usedClasses = unmatchedDependencies.get(className);
                Collections.sort(usedClasses);
                buffer.append("     " + className + "\n");
                boolean first = true;
                for (String usedClass : usedClasses) {
                    buffer.append("       " + (first ? "is used by " : "and        ") + usedClass + "\n");
                    first = false;
                }
            }
        }

        private boolean assortClassesContainedInImportedPlugins(StringBuffer buffer, Plugin plugin,
                                                                boolean isFirst, Set<String> allClasses) {
            boolean error = false;
            Map<String, Plugin> usedPlugins = new HashMap<String, Plugin>();

            if (plugin instanceof Fragment) {
                String idOfOwningPlugin = ((Fragment) plugin).getFullPluginIdOfOwningPlugin();
                usedPlugins.put(idOfOwningPlugin, findPlugin(idOfOwningPlugin));
            }

            for (Fragment fragment : findFragmentsOwnedByPlugin(plugin)) {
                usedPlugins.put(fragment.getFullPluginId(), fragment);
            }

            for (PluginPrerequisite prerequisite : plugin.descr.getPrerequisites()) {
                String idOfUsedPlugin = createFullPluginId(prerequisite.getPluginReference());
                usedPlugins.put(idOfUsedPlugin, findPlugin(idOfUsedPlugin));
            }

            for (String idOfUsedPlugin : usedPlugins.keySet()) {
                Plugin usedPlugin = usedPlugins.get(idOfUsedPlugin);

                if (usedPlugin == null) {
                    buffer.append(" * references unknown plugin " + idOfUsedPlugin + "\n");
                    error = true;
                    continue;
                }

                if (usedPlugin.containedClasses.size() > 0
                    && !allClasses.removeAll(usedPlugin.containedClasses)) {
                    if (isFirst && !(plugin instanceof Fragment) &&
                        (usedPlugin.descr.getPrerequisites() == null
                            || usedPlugin.descr.getPrerequisites().length == 0)) {
                        buffer.append(" * references plugin " + idOfUsedPlugin + ", which is not actually required\n");
                        error = true;
                        // don't need to analyze recursively, since the plugin
                        // imports no other plugins
                    }
                } else {
                    // recursively analyze this plugin ...
                    error |= assortClassesContainedInImportedPlugins(buffer, usedPlugin, false, allClasses);
                }
            }
            return error;
        }

        @SuppressWarnings("unused")
        private boolean isMissingImportDeclarations(StringBuffer buffer, Set<String> allClasses) {
            if (!allClasses.isEmpty()) {
                buffer.append(" * is missing import declarations covering the following classes: " + allClasses + "\n");
                return true;
            } else {
                return false;
            }
        }

        private Set<String> getAllUsedClasses() {
            HashSet<String> allClasses = new HashSet<String>();
            for (Set<String> classNames : usedClasses.values()) {
                allClasses.addAll(classNames);
            }
            return allClasses;
        }

        private void initContainedClasses(JarFile jarFile) {
            Enumeration<JarEntry> entries = jarFile.entries();

            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                String entryName = entry.getName();
                if (!entry.isDirectory() && entryName.endsWith(classSuffix)) {
                    String name = entryName.substring(0, entryName.length()
                        - classSuffix.length());
                    if (name.indexOf(".") != -1) {
                        System.err.println(name);
                    }
                    name = name.replace('/', '.');
                    name = name.replace('\\', '.');
                    containedClasses.add(name);
                }
            }
        }

        private void initUsedClasses(JarFile jarFile) {
            SyntheticRepository repository = SyntheticRepository.getInstance(new ClassPath(jarFile.getName()));
            for (String className : containedClasses) {
                try {
                    analyzeClass(className, repository.loadClass(className));
                } catch (ClassNotFoundException cnfe) {
                    cnfe.printStackTrace();
                }
            }
        }

        private void analyzeClass(String className, JavaClass javaClass) {
            analyzeSuperClass(className, javaClass);
            analyzeInterfaces(className, javaClass);
            analyzeFields(className, javaClass);
            analyzeMethods(className, javaClass);
            analyzeConstantPool(className, javaClass);
        }

        private void analyzeSuperClass(String usingClass, JavaClass javaClass) {
            addUsedClass(usingClass, javaClass.getSuperclassName());
        }

        private void analyzeInterfaces(String usingClass, JavaClass javaClass) {
            addUsedClasses(usingClass, javaClass.getInterfaceNames());
        }

        private void analyzeFields(String usingClass, JavaClass javaClass) {
            for (Field field : javaClass.getFields()) {
                addUsedClasses(usingClass, decodeSignature(field.getType().getSignature()));
            }
        }

        private void analyzeMethods(String usingClass, JavaClass javaClass) {
            for (Method method : javaClass.getMethods()) {
                for (Type argument : method.getArgumentTypes()) {
                    String typeName = decodeTypeName(argument.getSignature());
                    if (typeName != null) {
                        addUsedClass(usingClass, typeName);
                    }
                }
                addUsedClasses(usingClass, decodeSignature(method.getReturnType().getSignature()));
            }
        }

        private void analyzeConstantPool(String usingClass, JavaClass javaClass) {
            final ConstantPool constantPool = javaClass.getConstantPool();
            for (Constant constant : constantPool.getConstantPool()) {
                if (constant instanceof ConstantClass) {
                    String signature = ((ConstantClass) constant).getBytes(constantPool);
                    if (signature != null) {
                        if (signature.startsWith("[")) {
                            signature = decodeTypeName(signature);
                        }
                        if (signature != null) {
                            addUsedClass(usingClass, signature);
                        }
                    }
                } else if (constant instanceof ConstantNameAndType) {
                    for (String typeName : decodeSignature(
                        ((ConstantNameAndType) constant).getSignature(constantPool))) {
                        if (typeName != null) {
                            addUsedClass(usingClass, typeName);
                        }
                    }
                }
            }
        }

        private void addUsedClasses(String usingClass, Collection<String> classNames) {
            for (String className : classNames) {
                addUsedClass(usingClass, className);
            }
        }

        private void addUsedClasses(String usingClass, String[] classNames) {
            for (String className : classNames) {
                addUsedClass(usingClass, className);
            }
        }

        private void addUsedClass(String usingClass, String usedClass) {
            usedClass = usedClass.replace('/', '.');
            if (!containedClasses.contains(usedClass)) {
                if (usedClasses.containsKey(usingClass)) {
                    usedClasses.get(usingClass).add(usedClass);
                } else {
                    Set<String> set = new HashSet<String>();
                    set.add(usedClass);
                    usedClasses.put(usingClass, set);
                }
            }
        }

        private String decodeTypeName(String signature) {
            Matcher matcher = typePattern.matcher(signature);
            if (matcher.find()) {
                String objectTypeName = matcher.group();
                return objectTypeName.substring(1, objectTypeName.length() - 1);
            } else {
                return null;
            }
        }

        /**
         * @return Returns a list of classnames referenced by the
         *         signature.
         */
        private List<String> decodeSignature(String signature) {
            return getObjectTypeNames(signature);
        }

        private List<String> getObjectTypeNames(String signature) {
            List<String> objectTypeNames = new ArrayList<String>();
            Matcher matcher = typePattern.matcher(signature);
            while (matcher.find()) {
                String objectTypeName = matcher.group();
                objectTypeName = objectTypeName.substring(1, objectTypeName.length() - 1);
                objectTypeNames.add(objectTypeName);
            }
            return objectTypeNames;
        }

        public String toString() {
            return "Plugin " + fullPluginId + " contained=" + containedClasses + " used=" + usedClasses;
        }

        @Override
        public int compareTo(Plugin other) {
            return fullPluginId.compareTo(other.fullPluginId);
        }
    }

    private class Fragment extends Plugin {
        private String fullPluginIdOfOwningPlugin;

        private Fragment(JarFile jarFile, FragmentDescriptor descr) {
            super(jarFile, descr);
            this.fullPluginIdOfOwningPlugin = createFullPluginId(descr.getPluginReference());
        }

        private String getFullPluginIdOfOwningPlugin() {
            return fullPluginIdOfOwningPlugin;
        }

        public String toString() {
            return "Fragment " + fullPluginId + " owned by=" + fullPluginIdOfOwningPlugin + " contained=" +
                containedClasses + " used=" + usedClasses;
        }
    }

    private class JarFiles {
        /**
         * Maps plugin names to paths of jar files.
         */
        private final Map<String, String> jarFiles = new HashMap<String, String>();

        private JarFiles(List<FileSet> pluginSets) {
            for (FileSet fs : pluginSets) {
                addFileSet(fs);
            }
        }

        private void addFileSet(FileSet fileSet) {
            final DirectoryScanner ds = fileSet.getDirectoryScanner(getProject());
            final String[] files = ds.getIncludedFiles();
            String baseDir = ds.getBasedir().getAbsolutePath();
            //System.err.println("getAllJars() iterating ... basedir="+baseDir+" files.length="+files.length);
            for (String file : files) {
                jarFiles.put(file, baseDir + File.separatorChar + file);
            }
        }

        private boolean hasPluginJar(String fullPluginId) {
            return jarFiles.containsKey(fullPluginId + ".jar");
        }

        private JarFile getPluginJar(String fullPluginId) throws IOException {
            return new JarFile(jarFiles.get(fullPluginId + ".jar"));
        }
    }
}
TOP

Related Classes of org.jnode.build.dependencies.PluginDependencyChecker$JarFiles

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.
ipt>