de.ovgu.featureide.featurehouse.FeatureHouseComposer.java Source code

Java tutorial

Introduction

Here is the source code for de.ovgu.featureide.featurehouse.FeatureHouseComposer.java

Source

/* FeatureIDE - An IDE to support feature-oriented software development
 * Copyright (C) 2005-2012  FeatureIDE team, University of Magdeburg
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program. If not, see http://www.gnu.org/licenses/.
 *
 * See http://www.fosd.de/featureide/ for further information.
 */
package de.ovgu.featureide.featurehouse;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.JavaProject;

import cide.gparser.ParseException;
import cide.gparser.TokenMgrError;

import composer.CmdLineInterpreter;
import composer.FSTGenComposer;
import composer.FSTGenComposerExtension;
import composer.IParseErrorListener;

import de.ovgu.featureide.core.IFeatureProject;
import de.ovgu.featureide.core.builder.ComposerExtensionClass;
import de.ovgu.featureide.featurehouse.errorpropagation.ErrorPropagation;
import de.ovgu.featureide.featurehouse.model.FeatureHouseModelBuilder;
import de.ovgu.featureide.fm.core.configuration.Configuration;

/**
 * Composes files using FeatureHouse.
 * 
 * @author Tom Brosch
 */
// TODO set "Composition errors" like *.png could not be composed with *.png
@SuppressWarnings("restriction")
public class FeatureHouseComposer extends ComposerExtensionClass {

    private FSTGenComposer composer;

    public FeatureHouseModelBuilder fhModelBuilder;

    private ErrorPropagation errorPropagation = new ErrorPropagation();

    private IParseErrorListener listener = createParseErrorListener();

    private IParseErrorListener createParseErrorListener() {
        return new IParseErrorListener() {

            @Override
            public void parseErrorOccured(ParseException e) {
                createBuilderProblemMarker(e.currentToken.next.endLine, e.getMessage());
            }
        };
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * de.ovgu.featureide.core.builder.ComposerExtensionClass#initialize(de.
     * ovgu.featureide.core.IFeatureProject)
     */
    @Override
    public boolean initialize(IFeatureProject project) {
        boolean supSuccess = super.initialize(project);
        fhModelBuilder = new FeatureHouseModelBuilder(project);
        createBuildStructure();
        if (supSuccess == false || fhModelBuilder == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Creates an error marker to the last error file.
     * @param line The line of the marker.
     * @param message The message.
     */
    protected void createBuilderProblemMarker(int line, String message) {
        try {
            IMarker marker = getErrorFile().createMarker(FeatureHouseCorePlugin.BUILDER_PROBLEM_MARKER);
            marker.setAttribute(IMarker.LINE_NUMBER, line);
            marker.setAttribute(IMarker.MESSAGE, message);
            marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
        } catch (CoreException e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }

    }

    /**
     * Gets the file containing the actual error.
     * @return The file.
     */
    protected IFile getErrorFile() {
        return featureProject.getProject().getWorkspace().getRoot()
                .findFilesForLocationURI(composer.getErrorFiles().getLast().toURI())[0];
    }

    /**
     * Removes line and column form the message of the TokenMgrError.<br>
     * Example message:<br>
     * -Lexical error at line 7, column 7.  Encountered: <EOF> after : "" 
     * @param message The message
     * @return message without "line i, column j." 
     */
    private String getTokenMgrErrorMessage(String message) {
        if (!message.contains("line ") || !message.contains("Encountered"))
            return message;
        return message.substring(0, message.indexOf(" at ")) + " e"
                + message.substring(message.indexOf("ncountered:"));
    }

    /**
     * Gets the line of the message of the TokenMgrError.<br>
     * Example message:<br>
     * -Lexical error at line 7, column 7.  Encountered: <EOF> after : ""
     * @param message The error message
     * @return The line
     */
    private int getTokenMgrErrorLine(String message) {
        if (!message.contains("line "))
            return -1;
        return Integer.parseInt(message.substring(message.indexOf("line ") + 5, message.indexOf(',')));
    }

    /**
     * Checks the current folder structure at the build folder and creates folders if necessary.
     */
    private void createBuildStructure() {
        IProject p = featureProject.getProject();
        if (p != null) {
            IFolder sourcefolder = featureProject.getBuildFolder();
            if (sourcefolder != null) {
                if (!sourcefolder.exists()) {
                    try {
                        sourcefolder.create(true, true, null);
                    } catch (CoreException e) {
                        FeatureHouseCorePlugin.getDefault().logError(e);
                    }
                }
                IFile conf = featureProject.getCurrentConfiguration();
                if (conf != null) {
                    String configName = conf.getName();
                    sourcefolder = sourcefolder.getFolder(configName.substring(0, configName.indexOf('.')));
                    if (!sourcefolder.exists()) {
                        try {
                            sourcefolder.create(true, true, null);
                        } catch (CoreException e) {
                            FeatureHouseCorePlugin.getDefault().logError(e);
                        }
                        callCompiler();
                    }
                    //setJavaBuildPath(conf.getName().split("[.]")[0]);
                }
            }
        }
    }

    public void performFullBuild(IFile config) {
        assert (featureProject != null) : "Invalid project given";

        final String configPath = config.getRawLocation().toOSString();
        final String basePath = featureProject.getSourcePath();
        final String outputPath = featureProject.getBuildPath();

        if (configPath == null || basePath == null || outputPath == null)
            return;

        IFolder buildFolder = featureProject.getBuildFolder().getFolder(config.getName().split("[.]")[0]);
        if (!buildFolder.exists()) {
            try {
                buildFolder.create(true, true, null);
                buildFolder.refreshLocal(IResource.DEPTH_ZERO, null);
            } catch (CoreException e) {
                FeatureHouseCorePlugin.getDefault().logError(e);
            }
        }

        setJavaBuildPath(config.getName().split("[.]")[0]);

        composer = new FSTGenComposer(false);
        try {
            composer.run(new String[] { CmdLineInterpreter.INPUT_OPTION_EQUATIONFILE, configPath,
                    CmdLineInterpreter.INPUT_OPTION_BASE_DIRECTORY, basePath,
                    CmdLineInterpreter.INPUT_OPTION_OUTPUT_DIRECTORY, outputPath + "/",
                    CmdLineInterpreter.INPUT_OPTION_CONTRACT_STYLE, getContractParameter() });
        } catch (TokenMgrError e) {

        }
        fhModelBuilder.buildModel(composer.getFstnodes(), false);

        composer = new FSTGenComposerExtension();
        composer.addParseErrorListener(listener);

        List<String> featureOrderList = featureProject.getFeatureModel().getConcreteFeatureNames();
        String[] features = new String[featureOrderList.size()];
        int i = 0;
        for (String f : featureOrderList) {
            features[i++] = f;
        }

        try {
            ((FSTGenComposerExtension) composer)
                    .buildFullFST(
                            new String[] { CmdLineInterpreter.INPUT_OPTION_EQUATIONFILE, configPath,
                                    CmdLineInterpreter.INPUT_OPTION_BASE_DIRECTORY, basePath,
                                    CmdLineInterpreter.INPUT_OPTION_OUTPUT_DIRECTORY, outputPath + "/",
                                    CmdLineInterpreter.INPUT_OPTION_CONTRACT_STYLE, getContractParameter() },
                            features);
        } catch (TokenMgrError e) {
            createBuilderProblemMarker(getTokenMgrErrorLine(e.getMessage()),
                    getTokenMgrErrorMessage(e.getMessage()));
        } catch (Error e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }

        fhModelBuilder.buildModel(composer.getFstnodes(), true);

        TreeBuilderFeatureHouse fstparser = new TreeBuilderFeatureHouse(featureProject.getProjectName());
        fstparser.createProjectTree(composer.getFstnodes());
        featureProject.setProjectTree(fstparser.getProjectTree());
        callCompiler();
    }

    /**
     * @return
     */
    private String getContractParameter() {
        String contractComposition = featureProject.getContractComposition().toLowerCase();
        if (contractComposition == null || contractComposition.equals("none"))
            return "none";
        if (contractComposition.equals("plain contracting"))
            return "plain_contracting";
        if (contractComposition.equals("contract overriding"))
            return "contract_overriding";
        if (contractComposition.equals("explicit contract refinement"))
            return "explicit_contracting";
        if (contractComposition.equals("consecutive contract refinement"))
            return "consecutive_contracting";

        return "none";
    }

    /**
     * This job calls the compiler by touching the .classpath file of the project.<br> 
     * This is necessary after calling <code>setAsCurrentConfiguration</code>.
     */
    private void callCompiler() {
        Job job = new Job("Call compiler") {
            protected IStatus run(IProgressMonitor monitor) {
                IFile iClasspathFile = featureProject.getProject().getFile(".classpath");
                if (iClasspathFile.exists()) {
                    try {
                        iClasspathFile.touch(monitor);
                        iClasspathFile.refreshLocal(IResource.DEPTH_ZERO, monitor);
                    } catch (CoreException e) {
                        FeatureHouseCorePlugin.getDefault().logError(e);
                    }
                }
                return Status.OK_STATUS;
            }
        };
        job.setPriority(Job.DECORATE);
        job.schedule();

    }

    /**
     * Sets the Java build path to the folder at the build folder, named like the current configuration.
     * @param buildPath The name of the current configuration
     */
    private void setJavaBuildPath(String buildPath) {
        try {
            JavaProject javaProject = new JavaProject(featureProject.getProject(), null);
            IClasspathEntry[] classpathEntrys = javaProject.getRawClasspath();

            int i = 0;
            for (IClasspathEntry e : classpathEntrys) {
                if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    IPath path = featureProject.getBuildFolder().getFolder(buildPath).getFullPath();

                    /** return if nothing has to be changed **/
                    if (e.getPath().equals(path)) {
                        return;
                    }

                    /** change the actual source entry to the new build path **/
                    ClasspathEntry changedEntry = new ClasspathEntry(e.getContentKind(), e.getEntryKind(), path,
                            e.getInclusionPatterns(), e.getExclusionPatterns(), e.getSourceAttachmentPath(),
                            e.getSourceAttachmentRootPath(), null, e.isExported(), e.getAccessRules(),
                            e.combineAccessRules(), e.getExtraAttributes());
                    classpathEntrys[i] = changedEntry;
                    javaProject.setRawClasspath(classpathEntrys, null);
                    return;
                }
                i++;
            }

            /** case: there is no source entry at the class path
             *       add the source entry to the classpath **/
            IFolder folder = featureProject.getBuildFolder().getFolder(buildPath);
            ClasspathEntry sourceEntry = new ClasspathEntry(IPackageFragmentRoot.K_SOURCE,
                    IClasspathEntry.CPE_SOURCE, folder.getFullPath(), new IPath[0], new IPath[0], null, null, null,
                    false, null, false, new IClasspathAttribute[0]);
            IClasspathEntry[] newEntrys = new IClasspathEntry[classpathEntrys.length + 1];
            System.arraycopy(classpathEntrys, 0, newEntrys, 0, classpathEntrys.length);
            newEntrys[newEntrys.length - 1] = sourceEntry;
            javaProject.setRawClasspath(newEntrys, null);
        } catch (JavaModelException e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }
    }

    /**
     * For <code>FeatureHouse<code> a clean does not remove the folder,
     * named like the current configuration at the build folder, 
     * to prevent build path errors.
     * @return always <code>false</code>
     */
    @Override
    public boolean clean() {
        if (featureProject == null || featureProject.getBuildFolder() == null) {
            return false;
        }
        IFile config = featureProject.getCurrentConfiguration();
        if (config == null) {
            return false;
        }
        try {
            for (IResource featureFolder : featureProject.getBuildFolder().members()) {
                if (featureFolder.getName().equals(config.getName().split("[.]")[0])) {
                    if (featureFolder instanceof IFolder) {
                        for (IResource res : ((IFolder) featureFolder).members()) {
                            res.delete(true, null);
                        }
                    } else {
                        featureFolder.delete(true, null);
                    }
                } else {
                    featureFolder.delete(true, null);
                }
            }
        } catch (CoreException e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }
        return false;
    }

    public static final LinkedHashSet<String> EXTENSIONS = createExtensions();

    private static LinkedHashSet<String> createExtensions() {
        LinkedHashSet<String> extensions = new LinkedHashSet<String>();
        extensions.add("java");
        extensions.add("cs");
        extensions.add("c");
        extensions.add("h");
        extensions.add("hs");
        extensions.add("jj");
        extensions.add("als");
        extensions.add("xmi");
        return extensions;
    }

    @Override
    public LinkedHashSet<String> extensions() {
        return EXTENSIONS;
    }

    @Override
    public void copyNotComposedFiles(IFile config, IFolder destination) {
        try {
            copy(config, destination);
        } catch (CoreException e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }
    }

    private void copy(IFile config, IFolder destination) throws CoreException {
        ArrayList<String> selectedFeatures = getSelectedFeatures(config);
        if (selectedFeatures != null)
            for (String feature : selectedFeatures) {
                IFolder folder = featureProject.getSourceFolder().getFolder(feature);
                copy(folder, destination);
            }
    }

    private void copy(IFolder featureFolder, IFolder buildFolder) throws CoreException {
        if (!featureFolder.exists()) {
            return;
        }

        for (IResource res : featureFolder.members()) {
            if (res instanceof IFolder) {
                IFolder folder = buildFolder.getFolder(res.getName());
                if (!folder.exists()) {
                    folder.create(false, true, null);
                }
                copy((IFolder) res, folder);
            } else if (res instanceof IFile) {
                if (!extensions().contains(res.getFileExtension())) {
                    IFile file = buildFolder.getFile(res.getName());
                    if (!file.exists()) {
                        res.copy(file.getFullPath(), true, null);
                    }
                }
            }
        }
    }

    private static ArrayList<String> getSelectedFeatures(IFile config) {
        File configFile = config.getRawLocation().toFile();
        return getTokenListFromFile(configFile);
    }

    @Override
    public ArrayList<String[]> getTemplates() {
        return TEMPLATES;
    }

    private static final ArrayList<String[]> TEMPLATES = createTempltes();

    private static ArrayList<String[]> createTempltes() {
        ArrayList<String[]> list = new ArrayList<String[]>(8);
        list.add(new String[] { "Alloy", "als", "module " + CLASS_NAME_PATTERN });
        list.add(new String[] { "C", "c", "" });
        list.add(new String[] { "C#", "cs", "public class " + CLASS_NAME_PATTERN + " {\n\n}" });
        list.add(new String[] { "Haskell", "hs", "module " + CLASS_NAME_PATTERN + " where \n{\n\n}" });
        list.add(JAVA_TEMPLATE);
        list.add(new String[] { "JavaCC", "jj",
                "PARSER_BEGIN(" + CLASS_NAME_PATTERN + ") \n \n PARSER_END(" + CLASS_NAME_PATTERN + ")" });
        list.add(new String[] { "UML", "xmi",
                "<?xml version = '1.0' encoding = 'UTF-8' ?> \n   <XMI xmi.version = '1.2' xmlns:UML = 'org.omg.xmi.namespace.UML'>\n\n</XMI>" });
        list.add(new String[] { "Jak", "jak", "/**\r\n * TODO description\r\n */\r\npublic " + REFINES_PATTERN
                + " class " + CLASS_NAME_PATTERN + " {\r\n\r\n}" });
        return list;
    }

    @Override
    public void postCompile(IResourceDelta delta, final IFile file) {
        super.postCompile(delta, file);
        try {
            if (!file.getWorkspace().isTreeLocked()) {
                file.refreshLocal(IResource.DEPTH_ZERO, null);
            }
            errorPropagation.addFile(file);
        } catch (CoreException e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }
    }

    @Override
    public int getDefaultTemplateIndex() {
        return 4;
    }

    @Override
    public void buildFSTModel() {
        if (featureProject == null) {
            return;
        }
        final String configPath;
        if (featureProject.getCurrentConfiguration() != null) {
            configPath = featureProject.getCurrentConfiguration().getRawLocation().toOSString();
        } else {
            configPath = featureProject.getProject().getFile(".project").getRawLocation().toOSString();
        }
        final String basePath = featureProject.getSourcePath();
        final String outputPath = featureProject.getBuildPath();
        if (configPath == null || basePath == null || outputPath == null)
            return;

        composer = new FSTGenComposerExtension();
        composer.addParseErrorListener(listener);

        List<String> featureOrderList = featureProject.getFeatureModel().getConcreteFeatureNames();
        String[] features = new String[featureOrderList.size()];
        int i = 0;
        for (String f : featureOrderList) {
            features[i++] = f;
        }

        try {
            ((FSTGenComposerExtension) composer)
                    .buildFullFST(
                            new String[] { CmdLineInterpreter.INPUT_OPTION_EQUATIONFILE, configPath,
                                    CmdLineInterpreter.INPUT_OPTION_BASE_DIRECTORY, basePath,
                                    CmdLineInterpreter.INPUT_OPTION_OUTPUT_DIRECTORY, outputPath + "/",
                                    CmdLineInterpreter.INPUT_OPTION_CONTRACT_STYLE, getContractParameter() },
                            features);
        } catch (TokenMgrError e) {
            createBuilderProblemMarker(getTokenMgrErrorLine(e.getMessage()),
                    getTokenMgrErrorMessage(e.getMessage()));
        } catch (Error e) {
            FeatureHouseCorePlugin.getDefault().logError(e);
        }

        fhModelBuilder.buildModel(composer.getFstnodes(), false);
    }

    @Override
    public void buildConfiguration(IFolder folder, Configuration configuration, String congurationName) {
        super.buildConfiguration(folder, configuration, folder.getName());
        IFile configurationFile = folder.getFile(folder.getName() + '.' + getConfigurationExtension());
        FSTGenComposer composer = new FSTGenComposer(false);
        composer.addParseErrorListener(createParseErrorListener());
        composer.run(new String[] { CmdLineInterpreter.INPUT_OPTION_EQUATIONFILE,
                configurationFile.getRawLocation().toOSString(), CmdLineInterpreter.INPUT_OPTION_BASE_DIRECTORY,
                featureProject.getSourcePath(), CmdLineInterpreter.INPUT_OPTION_OUTPUT_DIRECTORY,
                folder.getParent().getLocation().toOSString() + "/", CmdLineInterpreter.INPUT_OPTION_CONTRACT_STYLE,
                getContractParameter() });
        if (errorPropagation.job != null) {
            /*
             * Waiting for the propagation job to finish, 
             * because the corresponding FSTModel is necessary for propagation at FH
             * This is in general no problem because the compiler is much faster then the composer
             */
            try {
                errorPropagation.job.join();
            } catch (InterruptedException e) {
                FeatureHouseCorePlugin.getDefault().logError(e);
            }
        }
        fhModelBuilder.buildModel(composer.getFstnodes(), false);
        if (!configurationFile.getName().startsWith(congurationName)) {
            try {
                configurationFile.move(
                        ((IFolder) configurationFile.getParent())
                                .getFile(congurationName + '.' + getConfigurationExtension()).getFullPath(),
                        true, null);
            } catch (CoreException e) {
                FeatureHouseCorePlugin.getDefault().logError(e);
            }
        }
    }

    /**
     * FeatureHouse causes access violation errors if it is executed parallel.
     */
    @Override
    public boolean canGeneratInParallelJobs() {
        return false;
    }

    /* (non-Javadoc)
     * @see de.ovgu.featureide.core.builder.IComposerExtensionClass#hasContractComposition()
     */
    @Override
    public boolean hasContractComposition() {
        return true;
    }
}