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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2011 jccastrejon
 *  
 * This file is part of Web2MexADL.
 *
 * Web2MexADL 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.
 *
 * Web2MexADL 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 Web2MexADL.  If not, see <http://www.gnu.org/licenses/>.
 */
package mx.itesm.web2mexadl.mvc;

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

import mx.itesm.web2mexadl.dependencies.ClassDependencies;
import mx.itesm.web2mexadl.dependencies.DependenciesUtil;
import mx.itesm.web2mexadl.dependencies.DependencyAnalyzer;
import mx.itesm.web2mexadl.util.Util;
import mx.itesm.web2mexadl.util.Util.Variable;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import weka.core.FastVector;
import weka.core.Instance;
import weka.core.Instances;

/**
 * The MvcAnalyzer class is responsible for the analysis of a web application
 * according to the MVC pattern, and the generation of both a MexADL document
 * and a SVG file representing the software architecture associated to the
 * application.
 * 
 * @author jccastrejon
 * 
 */
public class MvcAnalyzer {

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

    /**
     * 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,
                Util.getPropertyValues(Util.Variable.Type.getVariableName()));
        returnValue = MvcAnalyzer.generateArchitecture(dependencies, internalPackages, outputFile.getParentFile());

        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,
                Util.getPropertyValues(Util.Variable.Type.getVariableName()));
        returnValue = MvcAnalyzer.generateArchitecture(dependencies, internalPackages, outputFile.getParentFile());

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

        return returnValue;
    }

    /**
     * Generate the architecture document associated to the specified web
     * application data.
     * 
     * @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> generateArchitecture(final List<ClassDependencies> dependencies,
            final Map<String, Set<String>> internalPackages, final File outputDir) 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;
        StringBuilder modelPackages;
        StringBuilder viewPackages;
        StringBuilder controllerPackages;

        // 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 = Util.getPropertyValues(Util.Variable.Suffix.getVariableName());

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

        // Valid external api packages to look for in the classes dependencies
        externalApiValues = Util.getPropertyValues(Util.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],
                        Util.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) Util.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
        viewPackages = new StringBuilder();
        modelPackages = new StringBuilder();
        controllerPackages = new StringBuilder();
        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);
                Util.addImplementationPackage(modelPackages, currentPackage);
            } else if ((viewCount > modelCount) && (viewCount > controllerCount)) {
                packagesClassification.put(currentPackage, Layer.View);
                Util.addImplementationPackage(viewPackages, currentPackage);
            } else if ((controllerCount > viewCount) && (controllerCount > modelCount)) {
                packagesClassification.put(currentPackage, Layer.Controller);
                Util.addImplementationPackage(controllerPackages, currentPackage);
            } 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));
                }
            }
        }

        // Export MexADL architecture
        MvcAnalyzer.exportToMexADL(outputDir, modelPackages.toString(), controllerPackages.toString(),
                viewPackages.toString());

        return returnValue;
    }

    /**
     * Export the given data into a MexADL architecture document.
     * 
     * @param modelPackages
     * @param controllerPackages
     * @param viewPackages
     * @throws IOException
     */
    public static void exportToMexADL(final File outputDir, final String modelPackages,
            final String controllerPackages, final String viewPackages) throws IOException {
        File outputFile;
        String outputContents;

        // Initial template
        outputFile = new File(outputDir, "mvcArchitecture.xml");
        FileUtils.deleteQuietly(outputFile);
        FileUtils.copyInputStreamToFile(
                MvcAnalyzer.class.getResourceAsStream("/mx/itesm/web2mexadl/templates/MvcTemplate.xml"),
                outputFile);

        // Update template with implementation packages
        outputContents = FileUtils.readFileToString(outputFile, "UTF-8");
        outputContents = StringUtils.replace(outputContents, "<!-- Model implementation -->", modelPackages);
        outputContents = StringUtils.replace(outputContents, "<!-- View implementation -->", viewPackages);
        outputContents = StringUtils.replace(outputContents, "<!-- Controller implementation -->",
                controllerPackages);

        // Final architecture
        FileUtils.write(outputFile, outputContents);
    }
}