mx.itesm.arch.mvc.MvcAnalyzer.java Source code

Java tutorial

Introduction

Here is the source code for mx.itesm.arch.mvc.MvcAnalyzer.java

Source

/*
 * Copyright 2011 jccastrejon
 *  
 * This file is part of MvcAnalyzer.
 *
 * MvcAnalyzer 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 3 of the License, or
 * any later version.
 *
 * MvcAnalyzer 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 MvcAnalyzer. If not, see <http://www.gnu.org/licenses/>.
 */
package mx.itesm.arch.mvc;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import mx.itesm.arch.dependencies.ClassDependencies;
import mx.itesm.arch.dependencies.DependenciesUtil;
import mx.itesm.arch.dependencies.DependencyAnalyzer;
import weka.classifiers.Classifier;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.SerializationHelper;

/**
 * Classify components in a web project according to the MVC pattern.
 * 
 * @author jccastrejon
 * 
 */
public class MvcAnalyzer {

    /**
     * Random Variables used in the Uncertainty model.
     * 
     * @author jccastrejon
     * 
     */
    public enum Variable {
        Type("variable.type"), ExternalAPI("variable.externalApi"), Suffix("variable.suffix");

        /**
         * Variable property name.
         */
        private String variableName;

        /**
         * Variable attribute.
         */
        private Attribute attribute;

        /**
         * Constructor that specifies the Variable's property name. The
         * attribute's values are read from the
         * <em>MvcAnalyzer.classifierVariables</em> properties.
         * 
         * @param variableName
         *            Variable Name.
         */
        private Variable(final String variableName) {
            String[] propertyValues;
            FastVector valuesVector;

            // Load property values
            propertyValues = MvcAnalyzer.getPropertyValues(variableName);
            valuesVector = new FastVector(propertyValues.length);
            for (String propertyValue : propertyValues) {
                valuesVector.addElement(propertyValue);
            }

            this.variableName = variableName;
            this.attribute = new Attribute(this.toString(), valuesVector, this.ordinal());
        }

        /**
         * Get the variables property name.
         * 
         * @return Property name.
         */
        public String getVariableName() {
            return this.variableName;
        }

        /**
         * Get the variable's attribute.
         * 
         * @return Attribute.
         */
        public Attribute getAttribute() {
            return this.attribute;
        }
    };

    /**
     * Path to the properties file containing the model's variables.
     */
    private final static String PROPERTIES_FILE_PATH = "/mvc-variables-grails-play-struts-roo.properties";

    /**
     * Path to the file containing the model's classifier.
     */
    private final static String CLASSIFIER_FILE_PATH = "/mvc-classifier-grails-play-struts-roo.model";

    /**
     * Properties file containing the variables data.
     */
    private static Properties classifierVariables;

    /**
     * MVC Classifier.
     */
    private static Classifier classifier;

    /**
     * Class logger.
     */
    private static Logger logger = Logger.getLogger(MvcAnalyzer.class.getName());

    /**
     * Initialize properties file.
     */
    static {
        MvcAnalyzer.classifierVariables = new Properties();

        try {
            MvcAnalyzer.classifierVariables
                    .load(MvcAnalyzer.class.getResourceAsStream(MvcAnalyzer.PROPERTIES_FILE_PATH));

            MvcAnalyzer.classifier = (Classifier) SerializationHelper
                    .read(MvcAnalyzer.class.getResourceAsStream(MvcAnalyzer.CLASSIFIER_FILE_PATH));
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Properties file: " + MvcAnalyzer.PROPERTIES_FILE_PATH + " could not be read",
                    e);
        }
    }

    /**
     * Classify each class within the specified path into one of the layers of
     * the MVC pattern.
     * 
     * @param path
     *            Path to the directory containing the classes.
     * @param includeExternal
     *            Should the external dependencies be exported.
     * @param outputFile
     *            File where to export the classification results.
     * @return Map containing the classification results for each class.
     * @throws Exception
     *             If an Exception occurs during classification.
     */
    public static Map<String, Layer> classifyClassesInDirectory(final File path, final boolean includeExternal,
            final File outputFile) throws Exception {
        Map<String, Layer> returnValue;
        List<ClassDependencies> dependencies;
        Map<String, Set<String>> internalPackages;

        // Classify each class in the specified path
        dependencies = DependencyAnalyzer.getDirectoryDependencies(path.getAbsolutePath(),
                new MvcDependencyCommand());
        internalPackages = DependenciesUtil.getInternalPackages(dependencies,
                MvcAnalyzer.getPropertyValues(MvcAnalyzer.Variable.Type.getVariableName()));
        returnValue = MvcAnalyzer.classifyClasses(dependencies, internalPackages);

        if (outputFile != null) {
            DependenciesUtil.exportDependenciesToSVG(dependencies, includeExternal, outputFile, internalPackages,
                    new MvcExportCommand(returnValue));
        }

        return returnValue;
    }

    /**
     * Classify each class within the specified WAR file into one of the layers
     * of the MVC pattern.
     * 
     * @param file
     *            Path to the WAR file.
     * @param includeExternal
     *            Should the external dependencies be exported.
     * @param outputFile
     *            File where to export the classification results.
     * @return Map containing the classification results for each class.
     * @throws Exception
     *             If an Exception occurs during classification.
     */
    public static Map<String, Layer> classifyClassesinWar(final File file, final boolean includeExternal,
            final File outputFile) throws Exception {
        Map<String, Layer> returnValue;
        List<ClassDependencies> dependencies;
        Map<String, Set<String>> internalPackages;

        // Classify each class in the specified war
        dependencies = DependencyAnalyzer.getWarDependencies(file.getAbsolutePath(), new MvcDependencyCommand());

        // Remove the WEB-INF.classes prefix
        for (ClassDependencies dependency : dependencies) {
            dependency.setClassName(dependency.getClassName().replace("WEB-INF.classes.", ""));
            dependency.setPackageName(dependency.getPackageName().replace("WEB-INF.classes.", ""));
        }

        internalPackages = DependenciesUtil.getInternalPackages(dependencies,
                MvcAnalyzer.getPropertyValues(MvcAnalyzer.Variable.Type.getVariableName()));
        returnValue = MvcAnalyzer.classifyClasses(dependencies, internalPackages);

        if (outputFile != null) {
            DependenciesUtil.exportDependenciesToSVG(dependencies, includeExternal, outputFile, internalPackages,
                    new MvcExportCommand(returnValue));
        }

        return returnValue;
    }

    /**
     * Classify each class in the specified List into one of the layers of the
     * MVC pattern.
     * 
     * @param dependencies
     *            List containing the dependencies for each class to classify.
     * @param internalPackages
     *            Project's internal packages.
     * @return Map containing the classification layer for each class.
     * @throws Exception
     *             If an Exception occurs during classification.
     */
    private static Map<String, Layer> classifyClasses(final List<ClassDependencies> dependencies,
            final Map<String, Set<String>> internalPackages) throws Exception {
        int viewCount;
        int modelCount;
        int instanceLayer;
        Instance instance;
        boolean valueFound;
        int controllerCount;
        Instances instances;
        String instanceType;
        String[] typeValues;
        Layer componentLayer;
        String[] suffixValues;
        Layer dependencyLayer;
        FastVector attributes;
        String[] externalApiValues;
        Map<String, Layer> returnValue;
        Set<String> currentPackageContent;
        Map<String, Layer> packagesClassification;
        Map<String, String[]> externalApiPackages;

        // Model variables
        attributes = new FastVector();
        for (Variable variable : Variable.values()) {
            attributes.addElement(variable.getAttribute());
        }

        // Layer variable
        attributes.addElement(Layer.attribute);

        // Set the test instances, the Layer variable is unknown
        instances = new Instances("mvc", attributes, 0);
        instances.setClassIndex(Variable.values().length);

        // Valid suffixes to look for in the class names
        suffixValues = MvcAnalyzer.getPropertyValues(MvcAnalyzer.Variable.Suffix.getVariableName());

        // Valid file types to look for in the component names
        typeValues = MvcAnalyzer.getPropertyValues(MvcAnalyzer.Variable.Type.getVariableName());

        // Valid external api packages to look for in the classes dependencies
        externalApiValues = MvcAnalyzer.getPropertyValues(MvcAnalyzer.Variable.ExternalAPI.getVariableName());
        externalApiPackages = new HashMap<String, String[]>(externalApiValues.length);
        for (int i = 0; i < externalApiValues.length; i++) {
            if (!externalApiValues[i].equals("none")) {
                externalApiPackages.put(externalApiValues[i],
                        MvcAnalyzer.getPropertyValues("externalApi." + externalApiValues[i] + ".packages"));
            }
        }

        returnValue = new HashMap<String, Layer>(dependencies.size());
        for (ClassDependencies classDependencies : dependencies) {
            // Variables + Layer
            instance = new Instance(Variable.values().length + 1);

            // Type
            instanceType = "java";
            for (String validType : typeValues) {
                if (classDependencies.getClassName().endsWith("." + validType)) {
                    instanceType = validType;
                    break;
                }
            }
            instance.setValue(Variable.Type.getAttribute(), instanceType);

            // ExternalAPI
            valueFound = false;
            externalApi: for (String externalApi : externalApiValues) {
                if (externalApi.equals("none")) {
                    continue;
                }

                // Check if any of the class' external dependencies match with
                // one of the key external dependencies
                if (classDependencies.getExternalDependencies() != null) {
                    for (String externalDependency : classDependencies.getExternalDependencies()) {
                        for (String externalPackage : externalApiPackages.get(externalApi)) {
                            if (externalDependency.toLowerCase().startsWith(externalPackage)) {
                                valueFound = true;
                                instance.setValue(Variable.ExternalAPI.getAttribute(), externalApi);
                                break externalApi;
                            }
                        }
                    }
                }
            }

            // No key external dependency found
            if (!valueFound) {
                instance.setValue(Variable.ExternalAPI.getAttribute(), "none");
            }

            // Suffix
            valueFound = false;
            for (String suffix : suffixValues) {
                if (classDependencies.getClassName().toLowerCase().endsWith(suffix)) {
                    valueFound = true;
                    instance.setValue(Variable.Suffix.getAttribute(), suffix);
                    break;
                }
            }

            // No key suffix found
            if (!valueFound) {
                instance.setValue(Variable.Suffix.getAttribute(), "none");
            }

            // Layer, the unknown variable
            instance.setMissing(Layer.attribute);
            instances.add(instance);
            instance.setDataset(instances);

            try {
                instanceLayer = (int) MvcAnalyzer.classifier.classifyInstance(instance);
            } catch (Exception e) {
                // Default value
                instanceLayer = 0;
                logger.severe("Unable to classify: " + instance);
            }

            returnValue.put(classDependencies.getClassName(), Layer.values()[instanceLayer]);
            logger.info(
                    classDependencies.getClassName() + " : " + returnValue.get(classDependencies.getClassName()));
        }

        // Check for any invalid relation
        packagesClassification = new HashMap<String, Layer>(internalPackages.size());
        for (String currentPackage : internalPackages.keySet()) {
            modelCount = viewCount = controllerCount = 0;
            currentPackageContent = internalPackages.get(currentPackage);

            for (String component : currentPackageContent) {
                componentLayer = returnValue.get(component);
                if (componentLayer == Layer.Model) {
                    modelCount++;
                } else if (componentLayer == Layer.View) {
                    viewCount++;
                } else if (componentLayer == Layer.Controller) {
                    controllerCount++;
                }
            }

            if ((modelCount > viewCount) && (modelCount > controllerCount)) {
                packagesClassification.put(currentPackage, Layer.Model);
            } else if ((viewCount > modelCount) && (viewCount > controllerCount)) {
                packagesClassification.put(currentPackage, Layer.View);
            } else if ((controllerCount > viewCount) && (controllerCount > modelCount)) {
                packagesClassification.put(currentPackage, Layer.Controller);
            } else {
                packagesClassification.put(currentPackage, null);
            }
        }

        for (ClassDependencies classDependencies : dependencies) {
            // Code relations
            valueFound = false;
            componentLayer = returnValue.get(classDependencies.getClassName());
            if (classDependencies.getInternalDependencies() != null) {
                for (String internalDependency : classDependencies.getInternalDependencies()) {
                    dependencyLayer = returnValue.get(internalDependency);

                    if (!componentLayer.isValidRelation(dependencyLayer)) {
                        valueFound = true;
                        returnValue.put(classDependencies.getClassName(),
                                Layer.valueOf("Invalid" + componentLayer));
                        logger.info("Invalid relation detected between: " + classDependencies.getClassName()
                                + " and " + internalDependency);
                    }
                }
            }

            // Package relations
            if (!valueFound) {
                dependencyLayer = packagesClassification.get(classDependencies.getPackageName());

                if ((dependencyLayer != null) && (componentLayer != dependencyLayer)) {
                    returnValue.put(classDependencies.getClassName(), Layer.valueOf("Invalid" + componentLayer));
                }
            }
        }

        return returnValue;
    }

    /**
     * Get a property's values, specified in the MvcAnalyzer.classifierVariables
     * properties file.
     * 
     * @param propertyName
     *            Property name.
     * @return Property's values.
     */
    public static String[] getPropertyValues(final String propertyName) {
        String[] returnValue;

        if ((propertyName == null) || (!MvcAnalyzer.classifierVariables.containsKey(propertyName))) {
            throw new IllegalArgumentException("Invalid property: " + propertyName);
        }

        // Make sure all the values are in lower case
        returnValue = MvcAnalyzer.classifierVariables.getProperty(propertyName).split(",");
        for (int i = 0; i < returnValue.length; i++) {
            returnValue[i] = returnValue[i].toLowerCase();
        }

        return returnValue;
    }
}