net.cliseau.composer.javatarget.PointcutParseException.java Source code

Java tutorial

Introduction

Here is the source code for net.cliseau.composer.javatarget.PointcutParseException.java

Source

/* Copyright (c) 2011-2014 Richard Gay <gay@mais.informatik.tu-darmstadt.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package net.cliseau.composer.javatarget;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.antlr.stringtemplate.*;
import org.antlr.stringtemplate.language.*;
import org.apache.commons.lang.StringUtils;
import net.cliseau.composer.UnitGenerationException;
import net.cliseau.composer.PlainInetAddress;
import net.cliseau.composer.javatarget.PointcutParser.ParseException;
import net.cliseau.composer.javatarget.PointcutParser.PointcutParser;
import net.cliseau.composer.javatarget.PointcutParser.Pointcut;
import net.cliseau.composer.javatarget.PointcutParser.PointcutSpec;
import net.cliseau.composer.config.base.InvalidConfigurationException;
import net.cliseau.composer.config.target.AspectJConfig;
import net.cliseau.utils.CommandRunner;

/**
 * Exception during unit generation caused by a pointcut parsing failure.
 *
 * Objects of this class should be thrown during unit generation, when the
 * pointcut parser could not handle the specified pointcut file.
 *
 * @see net.cliseau.composer.javatarget.PointcutParser.PointcutParser
 */
class PointcutParseException extends UnitGenerationException {
    /**
     * Construct a PointcutParseException object.
     *
     * @param message The message explaining this exception.
     */
    public PointcutParseException(final String message) {
        super(message);
    }
}

/**
 * Class for generating and weaving CliSeAu aspects from pointcut definitions.
 *
 * An AspectWeaver object generates AOP aspects from a pointcut definition
 * and two templates for aspects and advices. For working with the templates are
 * stored in files and processed with the help of the StringTemplate library.
 * The generated aspects are woven into a specified Java program using AspectJ.
 *
 * The aspects generated by this class represent the Interceptor and Enforcer
 * components in the conceptual model of Service Automata.
 */
public class AspectWeaver implements Callable<Void> {
    /** The directory into which the modified target is to be written. */
    private final String destinationDirectory;
    /** The configuration of the target. */
    private final AspectJConfig aspectjConfig;
    /** Address under which the interceptor part of the aspect can contact the local coordinator. */
    private final PlainInetAddress coordinatorAddress;
    /** Address under which the enforcer part of the aspect is supposed to listen to decisions from the local coordinator. */
    private final PlainInetAddress enforcerAddress;
    /** Collection of dependencies that are introduced by the woven aspect (relative to the target program path). */
    private final Collection<String> inlinedDependencies;
    /** Enable/disable verbose information. */
    private final boolean verbose;
    /** Enable/disable measurements inside the advice. */
    private final boolean measureAdviceTime;

    /** Name of the aspect that realizes a CliSeAu unit */
    private static final String AspectName = "CliSeAu";

    /**
     * Source version for the AspectJ aspect.
     *
     * This version depends on the features that the advice code makes use of.
     * Currently, we need at least version 1.5 in order to let the advice make
     * use of Java's generics (which might be used by the Enforcer class).
     */
    private static final String aspectJSourceVersion = "1.5";

    /** String for separating class paths in manifest files */
    private static final String manifestClassPathSeparator = " "; // not ":"!

    /**
     * Construct AspectWeaver object.
     *
     * @param destinationDirectory The directory into which the modified target is to be written.
     * @param aspectjConfig The configuration of the target.
     * @param coordinatorAddress Address under which the interceptor part of the aspect can contact the local coordinator.
     * @param enforcerAddress Address under which the enforcer part of the aspect is supposed to listen to decisions from the local coordinator.
     * @param inlinedDependencies Collection of dependencies that are introduced by the woven aspect (relative to the target program path).
     * @param measureAdviceTime Enable/disable measurements inside the advice.
     * @param verbose Enable/disable verbose information.
     */
    public AspectWeaver(final String destinationDirectory, final AspectJConfig aspectjConfig,
            final PlainInetAddress coordinatorAddress, final PlainInetAddress enforcerAddress,
            final Collection<String> inlinedDependencies, final boolean measureAdviceTime, final boolean verbose)
            throws InvalidConfigurationException {
        this.destinationDirectory = destinationDirectory;
        this.aspectjConfig = aspectjConfig;
        this.coordinatorAddress = coordinatorAddress;
        this.enforcerAddress = enforcerAddress;
        this.inlinedDependencies = inlinedDependencies;
        this.measureAdviceTime = measureAdviceTime;
        this.verbose = verbose;
    }

    /**
     * Create aspect and weave it into the target program.
     *
     * @return Void Nothing.
     * @exception InstrumentationException Thrown when instrumenting the target program fails.
     */
    public Void call()
            throws IOException, InstrumentationException, PointcutParseException, InvalidConfigurationException {
        File aspectFile = new File(getDestinationDirectory() + File.separator + AspectName + ".aj");
        File targetJARFile = new File(getDestinationDirectory() + File.separator
                + (new File(aspectjConfig.getJavaProgramJAR())).getName());

        generateAspect(aspectFile);
        weaveAspect(aspectFile, targetJARFile);
        aspectFile.delete();
        updateTargetManifest(targetJARFile);

        return null;
    }

    /**
     * Get the boxed type name for a given type name.
     *
     * All object types are unchanged; only primitive types such as "int", "char"
     * etc. are transformed to their boxed variants such as "Integer", "Char",
     * etc. Such a non-primitive type may be required when the type is used for
     * Java's generics.
     *
     * @param type Type name
     * @return Object type name corresponding to the given type
     */
    private static String typeToBoxedType(final String type) {
        if (type.equals("boolean"))
            return "Boolean";
        else if (type.equals("byte"))
            return "Byte";
        else if (type.equals("char"))
            return "Char";
        else if (type.equals("double"))
            return "Double";
        else if (type.equals("float"))
            return "Float";
        else if (type.equals("int"))
            return "Integer";
        else if (type.equals("long"))
            return "Long";
        else if (type.equals("short"))
            return "Short";
        else if (type.equals("void"))
            return "Void"; // yes, java.lang.Void exists
        else
            return type; // "type" is already an object (all primitive types are listed above)
    }

    /**
     * Generate an aspect which realizes interceptor and enforcer of a CliSeAu unit.
     *
     * Using the aspect and advice template files, this method generates a
     * CliSeAu unit's aspect.
     *
     * @param destinationFile Name of file to write the aspect to.
     */
    private void generateAspect(final File destinationFile)
            throws IOException, PointcutParseException, InvalidConfigurationException {
        StringTemplateGroup group = new StringTemplateGroup("SAgroup", AngleBracketTemplateLexer.class);

        // Parse the pointcuts, extracting all information required
        // for the generation of our advice
        // (note: the FileReader may throw a FileNotFoundException)
        PointcutParser parser = new PointcutParser(
                new FileReader(aspectjConfig.getInstrumentationPointcutSpecFile()));
        PointcutSpec pspec;
        try {
            pspec = parser.PointcutSpecification();
        } catch (ParseException e) {
            // This happens when the aspect generator fails to parse the pointcut
            // specification.
            //TODO: add some more information to the message of the exception!
            throw new PointcutParseException(e.getMessage());
        }

        String advice_list = "";
        String pointcuts = "";
        for (Pointcut pc : pspec.getPointcuts()) {
            StringTemplate advice = group.getInstanceOf(aspectjConfig.getAspectJAdviceTemplate());
            advice.setAttribute("PointcutName", pc.getName());
            advice.setAttribute("TypedParameters", pc.getTypedParametersList());
            advice.setAttribute("Parameters", pc.getParametersList());
            String returnType = pc.getReturnType();
            advice.setAttribute("ReturnType", returnType);
            advice.setAttribute("ObjReturnType", typeToBoxedType(returnType));
            advice.setAttribute("hasReturnType", new Boolean(!returnType.equals(Pointcut.noReturnType)));
            advice.setAttribute("CriticalEventFactory", aspectjConfig.getCriticalEventFactoryName());
            advice.setAttribute("EnforcerFactory", aspectjConfig.getEnforcerFactoryName());
            advice.setAttribute("measureTime", new Boolean(measureAdviceTime));

            advice_list = advice_list + "\n" + advice.toString();

            // In order to avoid that the CliSeAu unit's interceptor itself is modified,
            // we add the restriction that modifications must not happen in the automaton aspects
            pc.setPointcutExpression("(" + pc.getPointcutExpression() + ") && !within(" + AspectName + ")");
            pointcuts += pc.toString();
        }

        String import_list = "";
        for (String imp : pspec.getImports()) {
            import_list += "import " + imp + ";\n";
        }

        StringTemplate aspect = group.getInstanceOf(aspectjConfig.getAspectJAspectTemplate());
        aspect.setAttribute("AspectName", AspectName);
        aspect.setAttribute("Pointcuts", pointcuts);
        aspect.setAttribute("Advice", advice_list);
        aspect.setAttribute("Imports", import_list);
        setAspectProperties(aspect);

        // write the aspect to the given file (may throw IOException)
        PrintWriter aspectWriter = new PrintWriter(new FileWriter(destinationFile, false));
        aspectWriter.print(aspect.toString());
        aspectWriter.close();
    }

    /**
     * Set aspect properties beyond the advice, imports and aspect name.
     *
     * This method particularly allows easily extending AspectWeaver
     * to use further aspect properties with modified aspect templates.
     */
    protected void setAspectProperties(StringTemplate aspect) {
        aspect.setAttribute("CoordAddr", getCoordinatorAddress().toCreationCode());
        aspect.setAttribute("EnfSocket", getEnforcerAddress().toServerCreationCode());
    }

    /**
     * Weave a given aspect into a given target program using AspectJ.
     *
     * @param aspectFile File containing the aspect to weave.
     * @param targetJARFile File to write the resulting JAR into.
     * @exception InstrumentationException Thrown when instrumenting the target program fails.
     */
    private void weaveAspect(final File aspectFile, final File targetJARFile)
            throws IOException, InstrumentationException, InvalidConfigurationException {
        ArrayList<String> instrumentParams = new ArrayList<String>(
                Arrays.asList(aspectjConfig.getAspectJExecutable(), "-classpath",
                        StringUtils.join(getAbsoluteInlinedDependencies(), File.pathSeparator), "-injars",
                        aspectjConfig.getJavaProgramJAR(), "-outjar", targetJARFile.getPath(), "-source",
                        aspectJSourceVersion));
        if (verbose)
            instrumentParams.add("-showWeaveInfo");
        instrumentParams.add(aspectFile.getPath());
        String[] instrParamsArray = new String[instrumentParams.size()];
        instrumentParams.toArray(instrParamsArray);
        // note: the "exec" below may throw an IOException
        if (CommandRunner.exec(instrParamsArray) != CommandRunner.NORMAL_TERMINATION) {
            throw new InstrumentationException("Could not instrument the target.");
        }
    }

    /**
     * Update the manifest of a given JAR file to include CliSeAu's dependencies in the classpath list.
     *
     * This method modifies the "Class-Path" entry of the given JAR file's
     * manifest to include the paths of all runtime dependencies that are caused
     * by the instrumentation with the CliSeAu unit.
     *
     * @param targetJARFile The JAR file whose manifest to update.
     * @exception IOException Thrown when reading or writing the JAR file fails.
     * @todo Check whether this update is possible also with the JarFile API alone.
     */
    private void updateTargetManifest(final File targetJARFile) throws IOException, InvalidConfigurationException {
        // Step 1: Obtain the existing class path list from the target JAR file
        JarFile targetJAR = new JarFile(targetJARFile);
        Manifest targetManifest = targetJAR.getManifest();
        LinkedList<String> classPathEntries;
        if (targetManifest != null) {
            String targetClassPath = targetManifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
            if (targetClassPath == null) {
                targetClassPath = "";
            }
            classPathEntries = new LinkedList<String>(
                    Arrays.asList(targetClassPath.split(manifestClassPathSeparator)));
        } else {
            classPathEntries = new LinkedList<String>();
        }
        // close the object again (this shall ensure that the command in
        // Step 4 can safely work on the file again)
        targetJAR.close();

        // Step 2: Add all newly introduced runtime dependencies of CliSeAu
        classPathEntries.addAll(getInlinedDependencies());

        // Step 3: Create a new manifest file with *only* the updated class path directive
        File manifestUpdate = File.createTempFile("MANIFEST", ".MF");
        PrintWriter muWriter = new PrintWriter(manifestUpdate);
        muWriter.print("Class-path:");
        muWriter.print(StringUtils.join(classPathEntries, manifestClassPathSeparator));
        muWriter.println();
        muWriter.close();

        // Step 4: Run "jar" to update the JAR file with the new manifest; this
        // does not replace the JAR file's manifest with the new one, but
        // *update* *only* those entries in the JAR file's manifest which are
        // present in the new manifest. That is, only the class path settings are
        // updated and everything else remains intact.
        CommandRunner.exec(new String[] { aspectjConfig.getJarExecutable(), "umf", // update manifest
                manifestUpdate.getPath(), targetJARFile.getPath() });

        // Step 5: cleanup
        manifestUpdate.delete();
    }

    //TODO: remove all unnecessary getters (i.e., all simple ones??)
    /**
     * Get destinationDirectory.
     *
     * @return destinationDirectory as String.
     */
    public String getDestinationDirectory() {
        return destinationDirectory;
    }

    /**
     * Get coordinatorAddress.
     *
     * @return coordinatorAddress as PlainInetAddress.
     */
    public PlainInetAddress getCoordinatorAddress() {
        return coordinatorAddress;
    }

    /**
     * Get enforcerAddress.
     *
     * @return enforcerAddress as PlainInetAddress.
     */
    public PlainInetAddress getEnforcerAddress() {
        return enforcerAddress;
    }

    /**
     * Get verbose.
     *
     * @return verbose as boolean.
     */
    public boolean getVerbose() {
        return verbose;
    }

    /**
     * Get inlinedDependencies.
     *
     * @return inlinedDependencies as Collection&lt;String&gt;.
     */
    public Collection<String> getInlinedDependencies() {
        return inlinedDependencies;
    }

    /**
     * Get absolute version of inlinedDependencies, based on destinationDirectory
     *
     * @return Absolute version of inlinedDependencies as Collection&lt;String&gt;.
     */
    public Collection<String> getAbsoluteInlinedDependencies() {
        ArrayList<String> deps = new ArrayList<String>(inlinedDependencies.size());
        for (String dep : inlinedDependencies) {
            deps.add(destinationDirectory + File.separator + dep);
        }
        return deps;
    }
}