net.onedaybeard.agrotera.maven.ComponentMatrix.java Source code

Java tutorial

Introduction

Here is the source code for net.onedaybeard.agrotera.maven.ComponentMatrix.java

Source

package net.onedaybeard.agrotera.maven;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import net.onedaybeard.agrotera.annotations.ArtemisSystem;
import net.onedaybeard.agrotera.annotations.ArtemisTemplate;
import net.onedaybeard.agrotera.matrix.MatrixStringUtil;
import net.onedaybeard.agrotera.maven.matrix.SystemRow;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.sonatype.plexus.build.incremental.BuildContext;

import com.x5.template.Chunk;
import com.x5.template.Theme;

import edu.emory.mathcs.backport.java.util.Collections;

/**
 * Scans the class path for all occurences of the annotation {@link ArtemisSystem}; and implementations of Component,
 * and creates a dependency matrix.
 * 
 * @author Adrian Papari junkdog@onedaybeard.net
 * @author GJ Roelofs info@codepoke.net
 * 
 */
@Mojo(name = "matrix", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.COMPILE)
public class ComponentMatrix extends AbstractMojo {

    public static String[] EXCLUSION_LIST = new String[] { "org.eclipse.jdt.core", "org/apache",
            "org/codehaus/plexus", "org/ow2/asm" };

    @Parameter(property = "project.build.outputDirectory")
    private File classDirectory;

    @Parameter(property = "project.build.sourceDirectory")
    private File sourceDirectory;

    @Component
    private BuildContext context;

    @Component
    private MavenProject project;

    @Parameter(property = "project.build.directory")
    private File saveDirectory;

    @Parameter(property = "project.name")
    private String name;

    @Override
    public void execute() throws MojoExecutionException {

        long then = System.currentTimeMillis();
        Log log = getLog();

        /**
         * Create Resolver for all classes on the classpath; including dependencies
         */
        List<URL> classPathURLS = new ArrayList<URL>();
        List<String> classpathElements = null;
        try {
            classpathElements = project.getCompileClasspathElements();
            List<URL> projectClasspathList = new ArrayList<URL>();
            for (String element : classpathElements) {

                // Check if part of the exclusion list
                boolean found = false;
                for (String key : EXCLUSION_LIST) {
                    if (element.contains(key)) {
                        found = true;
                        break;
                    }
                }

                if (found) {
                    log.debug(String.format("ComponentMatrix::execute() skipping class path: %s", element));
                    continue;
                }

                try {
                    URL url = new File(element).toURI().toURL();

                    log.debug(String.format("ComponentMatrix::execute() adding class path: %s", element));

                    projectClasspathList.add(url);
                } catch (MalformedURLException e) {
                    throw new MojoExecutionException(element + " is an invalid classpath element", e);
                }
            }

            classPathURLS.addAll(projectClasspathList);
        } catch (DependencyResolutionRequiredException e) {
            new MojoExecutionException("Dependency resolution failed", e);
        }

        /**
         * Creates a class loader with all dependencies
         * TODO: Restrict this only to those dependencies which are interesting
         * COMMENT #1: Can't just do INCLUSION, might forget some class paths which are needed for type resolvement.
         * COMMENT #2: Just restrict to package-phase; don't care about 1 or 2 second generation then.
         */
        URLClassLoader urlcl = new URLClassLoader(classPathURLS.toArray(new URL[0]),
                Thread.currentThread().getContextClassLoader());
        Reflections reflections = new Reflections(
                new ConfigurationBuilder().setUrls(ClasspathHelper.forClassLoader(urlcl)).addClassLoader(urlcl));

        /**
         * Populate all required fields, Systems, Templates and Components
         */
        ArrayList<Class<?>> artemisSystems = new ArrayList<Class<?>>(
                reflections.getTypesAnnotatedWith(ArtemisSystem.class));
        ArrayList<Class<?>> artemisTemplates = new ArrayList<Class<?>>(
                reflections.getTypesAnnotatedWith(ArtemisTemplate.class));
        ArrayList<Class<?>> components = new ArrayList<Class<?>>(
                reflections.getSubTypesOf(com.artemis.Component.class));
        ArrayList<Class<?>> managers = new ArrayList<Class<?>>(
                reflections.getSubTypesOf(com.artemis.Manager.class));

        // Sort on simple name (class name)
        Comparator<Class<?>> lexicalCompare = new Comparator<Class<?>>() {
            @Override
            public int compare(Class<?> arg0, Class<?> arg1) {
                return arg0.getSimpleName().compareTo(arg1.getSimpleName());
            }
        };
        Comparator<Class<?>> packageCompare = new Comparator<Class<?>>() {
            @Override
            public int compare(Class<?> arg0, Class<?> arg1) {
                return arg0.getName().compareTo(arg1.getName());
            }
        };

        // Sort everything and create strings for export
        Collections.sort(artemisSystems, packageCompare);
        Collections.sort(artemisTemplates, lexicalCompare);
        Collections.sort(components, lexicalCompare);

        ArrayList<String> componentsStr = convertToString(components);
        ArrayList<String> templatesStr = convertToString(artemisTemplates);

        /**
         * Create the Rows, and create Strings
         * TODO: Create better hierarchical representation?
         */

        ArrayList<SystemRow> rows = new ArrayList<SystemRow>();
        Class prev = String.class; // Prev is a nonsensical class the package of which we can never be in
        for (Class system : artemisSystems) {

            // See if we need to add a name row
            Package curPack = system.getPackage();
            Package prevPack = prev.getPackage();
            if (curPack != prevPack) {
                // Create the difference string between the current and last package
                String constructDiffPackage = constructDiffPackage(prevPack, curPack);
                System.err.println(constructDiffPackage);
                rows.add(new SystemRow(constructDiffPackage));
                prev = system;
            }

            rows.add(new SystemRow(system, components));
        }

        Theme theme = new Theme();
        Chunk chunk = theme.makeChunk("altMatrix");

        chunk.set("longestName", MatrixStringUtil.findLongestString(components).replaceAll(".", "_") + "______");
        chunk.set("longestManagers", MatrixStringUtil.findLongestString(managers).replaceAll(".", "_"));
        chunk.set("longestSystems", MatrixStringUtil.findLongestString(artemisSystems).replaceAll(".", "_"));
        chunk.set("systems", rows);
        chunk.set("headers", componentsStr);
        chunk.set("templates", templatesStr);
        chunk.set("project", name);

        BufferedWriter out = null;
        try {
            System.err.println("Writing to: " + saveDirectory);
            out = new BufferedWriter(new FileWriter(new File(saveDirectory, "altMatrix.html")));
            chunk.render(out);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (out != null)
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }

        log.debug(String.format("ComponentMatrix::execute() matrix generation took: %.4f s",
                (System.currentTimeMillis() - then) / 1000f));
    }

    /**
     * Returns, in String, the difference in package names.
     * 
     * test.foo.bar && test.foo.baz returns: baz
     * 
     * @param a
     * @param b
     * @return
     */
    public static String constructDiffPackage(Package a, Package b) {

        // Get the package names and split on the separator
        String[] aName = a.getName().split("\\.");
        String[] bName = b.getName().split("\\.");

        // Compare all packages and start appending the moment one differs
        StringBuffer buffer = new StringBuffer();
        int size = Math.min(aName.length, bName.length);
        boolean diff = false, first = true;
        for (int i = 0; i < size; i++) {

            // Check if we already found a diff package name, or the current one is different
            diff = !diff ? !aName[i].equals(bName[i]) : true;

            // Append everything of package B if we have found a differing package
            if (diff) {
                if (!first)
                    buffer.append(".");

                buffer.append(bName[i]);

                first = false;
            }
        }

        // Append any possible packages existing after Package a.
        for (int i = size; i < bName.length; i++) {
            if (!first)
                buffer.append(".");
            buffer.append(bName[i]);
        }

        return buffer.toString();
    }

    protected static ArrayList<String> convertToString(ArrayList<Class<?>> list) {
        ArrayList<String> output = new ArrayList<String>();
        for (Class<?> c : list) {
            output.add(c.getSimpleName());
        }

        return output;
    }
}