org.eclipse.emf.search.codegen.engine.AbstractModelSearchCodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.search.codegen.engine.AbstractModelSearchCodeGenerator.java

Source

/*******************************************************************************
 * Copyright (c) 2007-2008 Anyware Technologies and others. All rights reserved. 
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution, 
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 * 
 * AbstractEMFCodeGenerator.java
 * 
 * Contributors:
 *      David Sciamma (Anyware Technologies), Mathieu Garcia (Anyware
 *      Technologies), Jacques Lescot (Anyware Technologies) - initial API and
 *       implementation
 *      Lucas Bigeardel (Anyware Technologies) - Model Search Code Generation 
 *      additions
 * 
 ******************************************************************************/

package org.eclipse.emf.search.codegen.engine;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IContainer;
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.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
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.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.codegen.jet.JETEmitter;
import org.eclipse.emf.codegen.jet.JETException;
import org.eclipse.emf.codegen.merge.java.JControlModel;
import org.eclipse.emf.codegen.merge.java.JMerger;
import org.eclipse.emf.codegen.merge.java.facade.jdom.JDOMFacadeHelper;
import org.eclipse.emf.codegen.merge.properties.PropertyMerger;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.search.codegen.Activator;
import org.eclipse.emf.search.codegen.constants.GenConstants;
import org.eclipse.emf.search.codegen.l10n.Messages;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.ui.actions.OrganizeImportsAction;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.osgi.framework.Bundle;

/**
 * An abstract implementation of a generator using JET and JMerge to generate code for EMF projects. <br>
 * 
 * Creation : 9 nov. 2005
 * 
 * @author <a href="mailto:thomas@anyware-tech.com">Thomas Friol</a>
 */
public abstract class AbstractModelSearchCodeGenerator {
    /** The name of the JControl model */
    public static final String JCONTROL_MODEL_NAME = GenConstants.ROOT_TEMPLATES_DIRECTORY + IPath.SEPARATOR
            + "JMerge" + IPath.SEPARATOR + "emf-merge.xml"; //$NON-NLS-1$ //$NON-NLS-2$

    /** The PDE nature id */
    public static final String PDE_NATURE = "org.eclipse.pde.PluginNature"; //$NON-NLS-1$

    /** The Java builder id */
    public static final String JAVA_BUILDER = "org.eclipse.jdt.core.javabuilder"; //$NON-NLS-1$

    /** The Manifest builder id */
    public static final String MANIFEST_BUILDER = "org.eclipse.pde.ManifestBuilder"; //$NON-NLS-1$

    /** The Schema builder id */
    public static final String SCHEMA_BUILDER = "org.eclipse.pde.SchemaBuilder"; //$NON-NLS-1$

    /** The name of the src directory */
    public static final String SOURCE_DIRECTORY = "src"; //$NON-NLS-1$

    /** Encoding for properties file */
    protected final static String PROPERTIES_ENCODING = "ISO-8859-1"; //$NON-NLS-1$

    /*
     * Fields
     */
    private JControlModel jControlModel = null;

    /**
     * Launch the generation. Subclass must implements this method to customize their own generation.
     * 
     * @param monitor the monitor for the work progression
     * 
     * @return the generated project
     * @throws CoreException if the generation fails
     */
    public abstract Diagnostic generate(Monitor monitor) throws CoreException, JETException;

    /**
     * Add the default natures if they are not already present in the given IProjectDescription.<br>
     * Default implementation adds the Java and the PDE natures.
     * 
     * @param projectDescription an existing project description
     * @return the new natures array
     */
    protected static String[] addDefaultNatures(IProjectDescription projectDescription) {
        String[] natureIds = projectDescription.getNatureIds();

        if (natureIds == null) {
            natureIds = new String[] { JavaCore.NATURE_ID };
        } else {
            boolean hasJavaNature = false;
            boolean hasPDENature = false;
            for (int i = 0; i < natureIds.length; ++i) {
                if (JavaCore.NATURE_ID.equals(natureIds[i])) {
                    hasJavaNature = true;
                }
                if (PDE_NATURE.equals(natureIds[i])) {
                    hasPDENature = true;
                }
            }
            if (!hasJavaNature) {
                String[] oldNatureIds = natureIds;
                natureIds = new String[oldNatureIds.length + 1];
                System.arraycopy(oldNatureIds, 0, natureIds, 0, oldNatureIds.length);
                natureIds[oldNatureIds.length] = JavaCore.NATURE_ID;
            }
            if (!hasPDENature) {
                String[] oldNatureIds = natureIds;
                natureIds = new String[oldNatureIds.length + 1];
                System.arraycopy(oldNatureIds, 0, natureIds, 0, oldNatureIds.length);
                natureIds[oldNatureIds.length] = PDE_NATURE;
            }
        }
        return natureIds;
    }

    /**
     * Add the default builders if they are not already present in the given IProjectDescription.<br>
     * Default implementation adds the Java, the Manifest and the Schema builder.
     * 
     * @param projectDescription an existing project description
     * @return the new builders array
     */
    protected static ICommand[] addDefaultBuilders(IProjectDescription projectDescription) {
        ICommand[] builders = projectDescription.getBuildSpec();

        if (builders == null) {
            builders = new ICommand[0];
        }
        boolean hasJavaBuilder = false;
        boolean hasManifestBuilder = false;
        boolean hasSchemaBuilder = false;
        for (int i = 0; i < builders.length; ++i) {
            if (JAVA_BUILDER.equals(builders[i].getBuilderName())) {
                hasJavaBuilder = true;
            }
            if (MANIFEST_BUILDER.equals(builders[i].getBuilderName())) {
                hasManifestBuilder = true;
            }
            if (SCHEMA_BUILDER.equals(builders[i].getBuilderName())) {
                hasSchemaBuilder = true;
            }
        }
        if (!hasJavaBuilder) {
            ICommand[] oldBuilders = builders;
            builders = new ICommand[oldBuilders.length + 1];
            System.arraycopy(oldBuilders, 0, builders, 0, oldBuilders.length);
            builders[oldBuilders.length] = projectDescription.newCommand();
            builders[oldBuilders.length].setBuilderName(JAVA_BUILDER);
        }
        if (!hasManifestBuilder) {
            ICommand[] oldBuilders = builders;
            builders = new ICommand[oldBuilders.length + 1];
            System.arraycopy(oldBuilders, 0, builders, 0, oldBuilders.length);
            builders[oldBuilders.length] = projectDescription.newCommand();
            builders[oldBuilders.length].setBuilderName(MANIFEST_BUILDER);
        }
        if (!hasSchemaBuilder) {
            ICommand[] oldBuilders = builders;
            builders = new ICommand[oldBuilders.length + 1];
            System.arraycopy(oldBuilders, 0, builders, 0, oldBuilders.length);
            builders[oldBuilders.length] = projectDescription.newCommand();
            builders[oldBuilders.length].setBuilderName(SCHEMA_BUILDER);
        }
        return builders;
    }

    /**
     * Create an empty EMF Project with default values.
     * 
     * @param projectName the name of the project to create.
     * @return the newly created project
     */
    protected static IProject createEMFProject(String projectName) {
        IPath javaSource = new Path(IPath.SEPARATOR + projectName + IPath.SEPARATOR + SOURCE_DIRECTORY);
        IPath projectLocationPath = null;
        IProgressMonitor progressMonitor = new NullProgressMonitor();

        IProject project = null;

        try {
            List<IClasspathEntry> classpathEntries = new UniqueEList<IClasspathEntry>();

            project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
            IJavaProject javaProject = JavaCore.create(project);
            IProjectDescription projectDescription = null;
            if (!project.exists()) {
                projectDescription = project.getWorkspace().newProjectDescription(projectName);
                projectDescription.setLocation(projectLocationPath);
                project.create(projectDescription, new NullProgressMonitor());
            } else {
                projectDescription = project.getDescription();
                classpathEntries.addAll(Arrays.asList(javaProject.getRawClasspath()));
            }

            boolean isInitiallyEmpty = classpathEntries.isEmpty();

            // add the natures
            String[] natureIds = addDefaultNatures(projectDescription);
            projectDescription.setNatureIds(natureIds);

            // add the builders
            ICommand[] builders = addDefaultBuilders(projectDescription);
            projectDescription.setBuildSpec(builders);

            // open the project and apply the new Descriptions
            project.open(new NullProgressMonitor());
            project.setDescription(projectDescription, new NullProgressMonitor());

            // initialize the directory which will contains the sources
            IContainer sourceContainer = project;
            if (javaSource.segmentCount() > 1) {
                sourceContainer = project.getFolder(javaSource.removeFirstSegments(1).makeAbsolute());
                if (!sourceContainer.exists()) {
                    ((IFolder) sourceContainer).create(false, true, new SubProgressMonitor(progressMonitor, 1));
                }
            }

            if (isInitiallyEmpty) {
                IClasspathEntry sourceClasspathEntry = JavaCore.newSourceEntry(javaSource);
                for (Iterator<IClasspathEntry> i = classpathEntries.iterator(); i.hasNext();) {
                    IClasspathEntry classpathEntry = (IClasspathEntry) i.next();
                    if (classpathEntry.getPath().isPrefixOf(javaSource)) {
                        i.remove();
                    }
                }
                classpathEntries.add(0, sourceClasspathEntry);

                IClasspathEntry jreClasspathEntry = JavaCore.newVariableEntry(new Path(JavaRuntime.JRELIB_VARIABLE),
                        new Path(JavaRuntime.JRESRC_VARIABLE), new Path(JavaRuntime.JRESRCROOT_VARIABLE));
                for (Iterator<IClasspathEntry> i = classpathEntries.iterator(); i.hasNext();) {
                    IClasspathEntry classpathEntry = (IClasspathEntry) i.next();
                    if (classpathEntry.getPath().isPrefixOf(jreClasspathEntry.getPath())) {
                        i.remove();
                    }
                }
                classpathEntries.add(jreClasspathEntry);
                classpathEntries.add(JavaCore.newContainerEntry(new Path("org.eclipse.pde.core.requiredPlugins"))); //$NON-NLS-1$
            }

            javaProject.setRawClasspath(
                    (IClasspathEntry[]) classpathEntries.toArray(new IClasspathEntry[classpathEntries.size()]),
                    new SubProgressMonitor(progressMonitor, 1));

        } catch (CoreException e) {
            IStatus s = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
                    Messages.getString("AbstractEMFCodeGenerator.PluginCreationError"), e); //$NON-NLS-1$
            Activator.getDefault().getLog().log(s);
        } finally {
            progressMonitor.done();
        }

        return project;
    }

    /**
     * Performs an Organize imports on the generated project.
     * 
     * @param project the generated project
     */
    public static void organizeImports(IProject project) {
        final IJavaProject jProject = JavaCore.create(project);

        Runnable runnable = new Runnable() {
            public void run() {
                OrganizeImportsAction action = new OrganizeImportsAction(Activator.getDefault().getWorkbench()
                        .getActiveWorkbenchWindow().getActivePage().getActivePart().getSite());
                action.run(new StructuredSelection(jProject));
            }
        };
        Display.getDefault().syncExec(runnable);
    }

    /**
     * Save the generated text to a file in the same location as the specified file.
     * 
     * @param generated the generated text to save
     * @param file the original template file
     * @param monitor
     * @param isOverwrite whether the file should be overwritten
     * 
     * @throws CoreException exception
     */
    private void saveGenerated(String generated, IFile file, IProgressMonitor monitor, boolean isOverwrite)
            throws CoreException {
        InputStream contents = new ByteArrayInputStream(generated.getBytes());

        if (!file.exists()) {
            File systemFile = file.getLocation().toFile();
            if (systemFile.exists()) {
                // check if out of sync
                // not user-friendly : user did not request Refresh...
                file.getParent().refreshLocal(1, monitor);
                file.setContents(contents, true, false, monitor);
            } else {
                file.create(contents, false, monitor);
            }
        } else if (isOverwrite) {
            file.setContents(contents, true, false, monitor);
        }
    }

    private JControlModel getJControlModel() {
        if (jControlModel == null) {
            URL jControlUrl = Activator.getDefault().getBundle().getEntry(JCONTROL_MODEL_NAME);
            jControlModel = new JControlModel();
            jControlModel.initialize(new JDOMFacadeHelper(), jControlUrl.toString());
        }
        return jControlModel;
    }

    /**
     * Use the default JDT code formatter to format the given compilation unit contents.
     * 
     * @param contents the content to format
     * @param filename the name of the file to parse
     * @return the formatted string
     * @throws JETException if the formatting failed
     */
    private String formatCode(String contents, String filename) throws JETException {
        // Create a code formatter for this compilation unit
        CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(null);
        IDocument doc = new Document(contents);
        TextEdit edit = codeFormatter.format(CodeFormatter.K_COMPILATION_UNIT, doc.get(), 0, doc.get().length(), 0,
                null);

        try {
            if (edit != null) {
                edit.apply(doc);
                contents = doc.get();
            } else {
                IStatus s = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
                        Messages.getString("AbstractEMFCodeGenerator.DocFormattingError1") + filename //$NON-NLS-1$
                                + Messages.getString("AbstractEMFCodeGenerator.DocFormattingError2")); //$NON-NLS-1$
                Activator.getDefault().getLog().log(s);
            }
        } catch (MalformedTreeException e) {
            throw new JETException(e);
        } catch (BadLocationException e) {
            throw new JETException(e);
        }
        return contents;
    }

    /**
     * Perform a Java merge.
     * 
     * @param generatedString
     * @param targetFile
     * @return String
     * @throws CoreException
     */
    private String mergeJava(String generatedString, IFile targetFile) throws CoreException {
        JMerger jMerger = new JMerger(getJControlModel());
        jMerger.setSourceCompilationUnit(jMerger.createCompilationUnitForContents(generatedString));
        String newContents = null;
        if (targetFile.exists()) {
            jMerger.setTargetCompilationUnit(
                    jMerger.createCompilationUnitForInputStream(targetFile.getContents(true)));

        }

        jMerger.merge();
        newContents = jMerger.getTargetCompilationUnitContents();

        return newContents;
    }

    /**
     * Perform a Properties merge.
     * 
     * @param generatedString
     * @param targetFile
     * @param monitor
     * @return String
     * @throws CoreException
     */
    private String mergeProperties(String generatedString, IFile targetFile, IProgressMonitor monitor)
            throws CoreException {
        String newContents = generatedString;
        if (targetFile.exists()) {

            PropertyMerger propertyMerger = new PropertyMerger();
            propertyMerger.setSourceProperties(generatedString);
            monitor.subTask(Messages.getString("AbstractEMFCodeGenerator.MessagesParsingError1")); //$NON-NLS-1$

            String oldProperties = propertyMerger.createPropertiesForInputStream(targetFile.getContents());
            propertyMerger.setTargetProperties(oldProperties);
            monitor.subTask(Messages.getString("AbstractEMFCodeGenerator.MessagesParsingError2")); //$NON-NLS-1$
            propertyMerger.merge();

            String mergedResult = propertyMerger.getTargetProperties();
            if (!mergedResult.equals(oldProperties)) {
                // If the target is read-only, we can ask the platform to
                // release it, and it may be updated in the process.
                //
                if (targetFile.isReadOnly() && validateEdit(targetFile)) {
                    propertyMerger.setTargetProperties(
                            propertyMerger.createPropertiesForInputStream(targetFile.getContents()));
                    propertyMerger.merge();
                    mergedResult = propertyMerger.getTargetProperties();
                }
            }
        }
        return newContents;
    }

    /**
     * Check whether the file can be modified
     * 
     * @param file
     * @return true if it is OK
     */
    protected static boolean validateEdit(IFile file) {
        return file.getWorkspace().validateEdit(new IFile[] { file }, null).isOK();
    }

    /**
     * Wraps text generation and save in a <code>WorkspaceModifyOperation</code>, and runs this operation in a
     * <code>ProgressMonitorDialog</code>.
     * 
     * @param emitter generates text to save
     * @param arguments arguments to pass to the emitter
     * @param file the original template file
     * @param isOverwrite whether the file should be overwritten
     * @throws JETException
     * @throws CoreException
     */
    private void generate(final JETEmitter emitter, final Object[] arguments, final IFile file, boolean isOverwrite,
            Monitor monitor) throws JETException, CoreException {
        String generated = emitter.generate(monitor, arguments);

        boolean overwriteWithGenerated = isOverwrite;

        // Merge and format if it is a Java file
        if ("java".equals(file.getFileExtension())) { //$NON-NLS-1$
            generated = mergeJava(generated, file);
            generated = formatCode(generated, file.getName());
            // Merge generated the overwrite
            overwriteWithGenerated = true;
        }
        // Merge properties file
        if ("properties".equals(file.getFileExtension())) { //$NON-NLS-1$
            generated = mergeProperties(generated, file, BasicMonitor.toIProgressMonitor(monitor));
            // Merge generated the overwrite
            overwriteWithGenerated = true;
        }
        saveGenerated(generated, file, BasicMonitor.toIProgressMonitor(monitor), overwriteWithGenerated);
    }

    /**
     * Apply a JET template to an object.
     * 
     * @param input the input object
     * @param templateURI
     * @param outputFile
     * @param isOverwrite whether the file should be overwritten
     * @throws JETException
     * @throws CoreException
     */
    protected void applyTemplate(Object input, String templateURI, IPath outputFile, boolean isOverwrite,
            Monitor monitor) throws JETException, CoreException {
        JETEmitter emitter = createJETEmitter(templateURI);

        IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(outputFile);
        if (file != null && file.isAccessible()) {
            file.deleteMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
            file.refreshLocal(IResource.DEPTH_INFINITE, BasicMonitor.toIProgressMonitor(monitor));
        }

        Object[] arguments = new Object[] { input };

        generate(emitter, arguments, file, isOverwrite, monitor);

        monitor.worked(1);
    }

    /**
     * Creates the JET Emitter to use for the generation.
     * 
     * @param templateURI the template URI.
     * @return a JET Emitter
     */
    protected abstract JETEmitter createJETEmitter(String templateURI);

    /**
     * Returns a template URI for the given bundle and path relative to this bundle.
     * 
     * @param bundle
     * @param relativePath
     * @return a string URI
     */
    protected String getTemplateURI(Bundle bundle, String relativePath) {
        return bundle.getEntry(relativePath).toString();
    }

    /**
     * Creates the given package on the project
     * 
     * @param packageName the name of the package to create if not exists
     * @param project the project where generates
     * @throws CoreException if the generation failed
     */
    protected void createFolder(IPath projectPath, String folderName) throws CoreException {
        IPath dirPath = projectPath.append(IPath.SEPARATOR + folderName);
        IFolder folder = ResourcesPlugin.getWorkspace().getRoot().getFolder(dirPath);
        if (!(folder.exists())) {
            folder.create(true, false, new NullProgressMonitor());
        }
    }

    /**
     * Creates the given package on the project
     * 
     * @param packageName the name of the package to create if not exists
     * @param project the project where generates
     * @throws CoreException if the generation failed
     */
    protected void createPackage(String packageName, IProject project) throws CoreException {
        IPath pathProject = project.getFullPath();
        IPath packagePath = new Path(packageName.replace('.', IPath.SEPARATOR));

        for (int i = 1; i < packagePath.segmentCount() + 1; i++) {
            IPath pathPackage = pathProject.append(IPath.SEPARATOR + SOURCE_DIRECTORY + IPath.SEPARATOR
                    + packagePath.removeLastSegments(packagePath.segmentCount() - i));
            IFolder packagefolder = ResourcesPlugin.getWorkspace().getRoot().getFolder(pathPackage);
            if (!(packagefolder.exists())) {
                packagefolder.create(true, false, new NullProgressMonitor());
            }
        }
    }
}