org.eclipse.gmf.internal.common.codegen.GeneratorBase.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gmf.internal.common.codegen.GeneratorBase.java

Source

/*
 * Copyright (c) 2005, 2010 Borland Software Corporation 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
 *
 * Contributors:
 *    Artem Tikhomirov (Borland) - initial API and implementation
 */
package org.eclipse.gmf.internal.common.codegen;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.MultiStatus;
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.util.CodeGenUtil;
import org.eclipse.emf.codegen.util.CodeGenUtil.EclipseUtil;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticException;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.common.UnexpectedBehaviourException;
import org.eclipse.gmf.common.codegen.ImportAssistant;
import org.eclipse.gmf.internal.common.Activator;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.TextEdit;

/**
 * XXX do I really need refreshLocal in doGenerate[Binary]File? Guess, not.
 * @author artem
 */
public abstract class GeneratorBase implements Runnable {

    private CodeFormatter myCodeFormatter;
    private OrganizeImportsPostprocessor myImportsPostprocessor;
    private IProgressMonitor myProgress = new NullProgressMonitor();

    // myDestRoot.getJavaProject().getElementName() == myDestProject.getName()
    private IPackageFragmentRoot myDestRoot;
    private IProject myDestProject;
    private final List<IStatus> myExceptions;
    private IStatus myRunStatus = Status.CANCEL_STATUS;
    private TextMerger myMerger;
    private final boolean isToRestoreExistingImports = true;

    protected abstract void customRun() throws InterruptedException, UnexpectedBehaviourException;

    protected abstract void setupProgressMonitor();

    public GeneratorBase() {
        myExceptions = new LinkedList<IStatus>();
    }

    public void run(IProgressMonitor progress) throws InterruptedException {
        setProgressMonitor(progress);
        clearExceptionsList();
        doRun();
    }

    public void run() {
        clearExceptionsList();
        try {
            doRun();
        } catch (InterruptedException ex) {
            myRunStatus = new Status(IStatus.CANCEL, Activator.getID(), 0, Messages.interrupted, ex);
        }
    }

    /**
     * Provides information about success/failures during {@link #run()}
     * @return state of the generator run, or CANCEL if generator was not yet run.
     */
    public IStatus getRunStatus() {
        return myRunStatus;
    }

    /**
     * Optionally, specify progressMonitor to use. Should be called prior to {@link #run()}
     * @param progress
     */
    public void setProgressMonitor(IProgressMonitor progress) {
        myProgress = progress;
    }

    protected final void handleException(CoreException ex) {
        handleException(ex.getStatus());
    }

    protected final void handleException(IStatus status) {
        myExceptions.add(status);
    }

    protected final void handleException(Throwable ex) {
        if (ex instanceof DiagnosticException) {
            // unwind invocation target exceptions as much as possible 
            final Diagnostic diagnostic = ((DiagnosticException) ex).getDiagnostic();
            if (diagnostic.getException() instanceof InvocationTargetException) {
                Throwable originalEx = ((InvocationTargetException) diagnostic.getException()).getCause();
                handleException(newStatus(originalEx));
            } else {
                handleException(BasicDiagnostic.toIStatus(diagnostic));
            }
        } else {
            handleException(newStatus(ex));
        }
    }

    /**
     * by default, process as ordinary exception
     */
    protected void handleUnexpected(UnexpectedBehaviourException ex) {
        handleException(ex);
    }

    protected static IStatus newStatus(Throwable ex) {
        return newStatus(IStatus.ERROR, ex);
    }

    protected static IStatus newStatus(int severity, Throwable ex) {
        final String msg = ex.getMessage() == null ? ex.getClass().getName() : ex.getMessage();
        return new Status(severity, Activator.getID(), 0, Messages.bind(Messages.exception, msg), ex);
    }

    protected final IProject getDestProject() {
        return myDestProject;
    }

    protected final IProgressMonitor getProgress() {
        return myProgress;
    }

    /**
     * @param task optional string to be shown in the progress dialog
     * @param total estimation of number of activities to happen
     */
    protected final void setupProgressMonitor(String task, int total) {
        if (myProgress == null) {
            myProgress = new NullProgressMonitor();
            return;
            // no need to set it up
        }
        myProgress.beginTask(task == null ? Messages.start : task, total);
    }

    protected final IProgressMonitor getNextStepMonitor() throws InterruptedException {
        if (myProgress.isCanceled()) {
            throw new InterruptedException();
        }
        return new SubProgressMonitor(myProgress, 1);
    }

    /**
     * @see #initializeEditorProject(String, IPath, List)
     */
    protected final void initializeEditorProject(String pluginId, IPath projectLocation)
            throws UnexpectedBehaviourException, InterruptedException {
        initializeEditorProject(pluginId, projectLocation, Collections.<IProject>emptyList());
    }

    /**
     * Delegates to {@link #initializeEditorProject(IPath, IPath, List)}, using plug-in id as workspace project name and
     * 'src' as Java sources location.  
     * @param pluginId both name of workspace project and plug-in id 
     * @param projectLocation {@link IPath} to folder where <code>.project</code> file would reside. Use <code>null</code> to use default workspace location.
     * @param referencedProjects collection of {@link IProject}
     * 
     */
    protected final void initializeEditorProject(String pluginId, IPath projectLocation,
            List<IProject> referencedProjects) throws UnexpectedBehaviourException, InterruptedException {
        // not sure if there's any reason to get project's name via IProject (not use pluginId directly), this is just how it was done from 1.1.
        IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(pluginId);
        initializeEditorProject(new Path('/' + p.getName() + "/src"), projectLocation, referencedProjects);
    }

    /**
     * @param javaSource workspace absolute path to java source folder of the generated project, e.g. '/org.sample.aaa/sources'. 
     * @param projectLocation {@link IPath} to folder where <code>.project</code> file would reside. Use <code>null</code> to use default workspace location.
     * @param referencedProjects collection of {@link IProject}
     * @throws UnexpectedBehaviourException something goes really wrong 
     * @throws InterruptedException user canceled operation
     */
    protected final void initializeEditorProject(IPath javaSource, IPath projectLocation,
            List<IProject> referencedProjects) throws UnexpectedBehaviourException, InterruptedException {
        myDestProject = ResourcesPlugin.getWorkspace().getRoot().getProject(javaSource.segment(0));
        final int style = org.eclipse.emf.codegen.ecore.Generator.EMF_PLUGIN_PROJECT_STYLE;
        // pluginVariables is NOT used when style is EMF_PLUGIN_PROJECT_STYLE
        final List<?> pluginVariables = null;
        final IProgressMonitor pm = getNextStepMonitor();
        setProgressTaskName(Messages.initproject);

        org.eclipse.emf.codegen.ecore.Generator.createEMFProject(javaSource, projectLocation, referencedProjects,
                pm, style, pluginVariables);

        try {
            final IJavaProject jp = JavaCore.create(myDestProject);
            myDestRoot = jp.findPackageFragmentRoot(javaSource);
            // createEMFProject doesn't create source entry in case project exists and has some classpath entries already, 
            // though the folder gets created. 
            if (myDestRoot == null) {
                IClasspathEntry[] oldCP = jp.getRawClasspath();
                IClasspathEntry[] newCP = new IClasspathEntry[oldCP.length + 1];
                System.arraycopy(oldCP, 0, newCP, 0, oldCP.length);
                newCP[oldCP.length] = JavaCore.newSourceEntry(javaSource);
                jp.setRawClasspath(newCP, new NullProgressMonitor());
                myDestRoot = jp.findPackageFragmentRoot(javaSource);
            }
        } catch (JavaModelException ex) {
            throw new UnexpectedBehaviourException(ex.getMessage());
        }
        if (myDestRoot == null) {
            throw new UnexpectedBehaviourException("no source root can be found");
        }
    }

    /**
     * Generate ordinary file.
     * @param emitter template to use
     * @param filePath - project-relative path to file, e.g. META-INF/MANIFEST.MF
     * @param param TODO
     * @throws InterruptedException
     */
    protected final void doGenerateFile(TextEmitter emitter, IPath filePath, Object... param)
            throws InterruptedException {
        assert !myDestProject.getName().equals(filePath.segment(0));
        IProgressMonitor pm = getNextStepMonitor();
        try {
            setProgressTaskName(filePath.lastSegment());
            pm.beginTask(null, 5);
            IPath containerPath = myDestProject.getFullPath().append(filePath.removeLastSegments(1));
            EclipseUtil.findOrCreateContainer(containerPath, false, (IPath) null, new SubProgressMonitor(pm, 1));
            String genText = emitter.generate(new SubProgressMonitor(pm, 1), param);
            IFile f = myDestProject.getFile(filePath);
            final boolean propertyFile = "properties".equals(filePath.getFileExtension());
            String charset = propertyFile ? "ISO-8859-1" : "UTF-8";
            if (propertyFile) {
                genText = Conversions.escapeUnicode(genText);
            }
            String oldText = null;
            if (f.exists()) {
                oldText = FileServices.getFileContents(f);
            }
            if (oldText != null) {
                genText = mergePlainText(oldText, genText, f, new SubProgressMonitor(pm, 1));
                if (!oldText.equals(genText)) {
                    f.setContents(new ByteArrayInputStream(genText.getBytes(charset)), true, true,
                            new SubProgressMonitor(pm, 1));
                } else {
                    pm.worked(1);
                }
            } else {
                f.create(new ByteArrayInputStream(genText.getBytes(charset)), true, new SubProgressMonitor(pm, 2));
            }
        } catch (InvocationTargetException ex) {
            handleException(ex.getCause());
        } catch (UnexpectedBehaviourException ex) {
            handleUnexpected(ex);
        } catch (CoreException ex) {
            handleException(ex);
        } catch (UnsupportedEncodingException ex) {
            handleException(ex);
        } finally {
            pm.done();
        }
    }

    /**
     * Inspired by GenBaseImpl.EclipseUtil.findOrCreateContainer
     * Although later (with EMF API adopting Platform changes) we might need to return URI here
     * @return path suitable for IProjectDescription, or <code>null</code> to indicate use of default
     */
    protected final IPath guessNewProjectLocation(Path examplaryProjectPath, String newProjectName) {
        assert newProjectName != null;
        try {
            if (ResourcesPlugin.getWorkspace().getRoot().getProject(newProjectName).exists()) {
                // just use whatever already specified.
                // Returned value doesn't make sense in this case -
                // oee.codegen.ecore.Generator#EclipseHelper#createEMFProject doesn't use it then. 
                return null;
            }
            if (examplaryProjectPath == null || !examplaryProjectPath.isAbsolute()) {
                return null;
            }
            IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(examplaryProjectPath.segment(0));
            if (!p.exists()) {
                return null;
            }
            java.net.URI locationURI = p.getDescription().getLocationURI();
            // org.eclipse.core.internal.utils.FileUtil#toPath
            if (locationURI == null) {
                return null;
            }
            if (locationURI.getScheme() != null && !"file".equals(locationURI.getScheme())) {
                return null;
            }
            return new Path(locationURI.getSchemeSpecificPart()).removeLastSegments(1).append(newProjectName);
        } catch (CoreException ex) {
            handleException(newStatus(IStatus.WARNING, ex));
            return null;
        }
    }

    protected final ImportAssistant createImportAssistant(String packageName, String className) {
        return new ImportUtil(packageName, className, myDestRoot);
    }

    protected final void doGenerateJavaClass(TextEmitter emitter, String qualifiedClassName, Object... input)
            throws InterruptedException {
        doGenerateJavaClass(emitter, CodeGenUtil.getPackageName(qualifiedClassName),
                CodeGenUtil.getSimpleClassName(qualifiedClassName), input);
    }

    /**
     * NOTE: potential problem - packageName and className should match those specified in 
     * the template. Besides, getQualifiedXXX helpers in diagram GenModel should also correctly
     * return qualified class names.  
     */
    protected final void doGenerateJavaClass(TextEmitter emitter, String packageName, String className,
            Object... input) throws InterruptedException {
        IProgressMonitor pm = getNextStepMonitor();
        try {
            setProgressTaskName(className);
            pm.beginTask(null, 7);
            String genText = emitter.generate(new SubProgressMonitor(pm, 2), input);
            IPackageFragment pf = myDestRoot.createPackageFragment(packageName, true,
                    new SubProgressMonitor(pm, 1));
            ICompilationUnit cu = pf.getCompilationUnit(className + ".java"); //$NON-NLS-1$
            if (cu.exists()) {
                ICompilationUnit workingCopy = null;
                try {
                    workingCopy = cu.getWorkingCopy(new SubProgressMonitor(pm, 1));
                    final String oldContents = workingCopy.getSource();
                    IImportDeclaration[] declaredImports = workingCopy.getImports();
                    workingCopy.getBuffer().setContents(genText);
                    workingCopy.reconcile(ICompilationUnit.NO_AST, false, null, null);
                    try {
                        //Since we do organizeImports prior to merge, we must ensure imports added manually are known to OrganizeImportsProcessor
                        String[] declaredImportsAsStrings = new String[declaredImports.length];
                        for (int i = 0; i < declaredImports.length; i++) {
                            declaredImportsAsStrings[i] = declaredImports[i].getElementName();
                        }
                        getImportsPostrocessor().organizeImports(workingCopy, declaredImportsAsStrings,
                                new SubProgressMonitor(pm, 1));
                    } catch (CoreException e) {
                        workingCopy.commitWorkingCopy(true, new SubProgressMonitor(pm, 1)); // save to investigate contents
                        throw e;
                    }
                    genText = mergeJavaCode(oldContents, workingCopy.getSource(), new SubProgressMonitor(pm, 1));
                    genText = formatCode(genText);
                    if (!genText.equals(oldContents)) {
                        workingCopy.getBuffer().setContents(genText);
                        workingCopy.reconcile(ICompilationUnit.NO_AST, false, null, null);
                        workingCopy.commitWorkingCopy(true, new SubProgressMonitor(pm, 1));
                    } else {
                        // discard changes - would happen in finally, nothing else to do
                        pm.worked(1);
                    }
                } finally {
                    workingCopy.discardWorkingCopy();
                }
            } else {
                cu = pf.createCompilationUnit(cu.getElementName(), genText, true, new SubProgressMonitor(pm, 1));
                getImportsPostrocessor().organizeImports(cu, null, new SubProgressMonitor(pm, 1));
                String newContents = formatCode(cu.getSource());
                cu.getBuffer().setContents(newContents);
                cu.save(new SubProgressMonitor(pm, 2), true);
            }
        } catch (NullPointerException ex) {
            handleException(ex);
        } catch (InvocationTargetException ex) {
            handleException(ex.getCause());
        } catch (UnexpectedBehaviourException ex) {
            handleUnexpected(ex);
        } catch (CoreException ex) {
            handleException(ex);
        } finally {
            pm.done();
        }
    }

    protected final void doGenerateBinaryFile(BinaryEmitter emitter, Path outputPath, Object[] params)
            throws InterruptedException, UnexpectedBehaviourException {
        IProgressMonitor pm = getNextStepMonitor();
        setProgressTaskName(outputPath.lastSegment());
        IFile f = getDestProject().getFile(outputPath);
        if (f.exists()) {
            // Follow EMF's policy and do not overwrite file if exists
            return;
        }
        try {
            pm.beginTask(null, 4);
            IPath containerPath = getDestProject().getFullPath().append(outputPath.removeLastSegments(1));
            EclipseUtil.findOrCreateContainer(containerPath, false, (IPath) null, new SubProgressMonitor(pm, 1));
            byte[] contents = emitter.generate(new SubProgressMonitor(pm, 1), params);
            f.create(new ByteArrayInputStream(contents), true, new SubProgressMonitor(pm, 1));
            f.getParent().refreshLocal(IResource.DEPTH_ONE, new SubProgressMonitor(pm, 1));
        } catch (InvocationTargetException ex) {
            handleException(ex.getCause());
        } catch (CoreException ex) {
            handleException(ex);
        } finally {
            pm.done();
        }
    }

    protected String mergeJavaCode(String oldContents, String generatedText, IProgressMonitor pm)
            throws JavaModelException {
        pm.beginTask(Messages.merge, 1);
        try {
            return getMergeService().mergeJava(oldContents, generatedText);
        } finally {
            pm.done();
        }
    }

    protected String mergePlainText(String oldText, String genText, IFile oldRes, IProgressMonitor pm) {
        pm.beginTask(Messages.merge, 1);
        try {
            return getMergeService().process(oldRes.getName(), oldText, genText);
        } finally {
            pm.done();
        }
    }

    private TextMerger getMergeService() {
        if (myMerger == null) {
            myMerger = createMergeService();
            assert myMerger != null;
        }
        return myMerger;
    }

    /**
     * By default, provides facility that doesn't perform any merge at all.
     * @return facility to perform merges, should never return null. 
     */
    protected TextMerger createMergeService() {
        return new TextMerger();
    }

    protected void setProgressTaskName(String text) {
        myProgress.subTask(text);
    }

    protected final String formatCode(String text) {
        IDocument doc = new Document(text);
        TextEdit edit = getCodeFormatter().format(CodeFormatter.K_COMPILATION_UNIT, doc.get(), 0,
                doc.get().length(), 0, null);

        try {
            // check if text formatted successfully
            if (edit != null) {
                edit.apply(doc);
                text = doc.get();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return text;
    }

    private void doRun() throws InterruptedException {
        try {
            setupProgressMonitor();
            ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {
                public void run(IProgressMonitor monitor) throws CoreException {
                    try {
                        customRun();
                        myRunStatus = getExceptionsStatus();
                        // XXX consider catching CCE and provide "programming error"
                        // to help users with their templates
                    } catch (NullPointerException ex) {
                        myRunStatus = new Status(IStatus.ERROR, Activator.getID(), 0,
                                NullPointerException.class.getName(), ex);
                    } catch (UnexpectedBehaviourException ex) {
                        myRunStatus = new Status(Status.ERROR, Activator.getID(), 0, Messages.unexpected, ex);
                    } catch (InterruptedException ex) {
                        myRunStatus = new Status(IStatus.CANCEL, Activator.getID(), 0, Messages.interrupted, ex);
                    }
                }
            }, null);
            if (myRunStatus.getSeverity() == IStatus.CANCEL
                    && myRunStatus.getException() instanceof InterruptedException) {
                throw (InterruptedException) myRunStatus.getException();
            }
        } catch (CoreException ex) {
            myRunStatus = ex.getStatus();
        } finally {
            getProgress().done();
            clearExceptionsList();
        }
    }

    private CodeFormatter getCodeFormatter() {
        if (myCodeFormatter == null) {
            myCodeFormatter = ToolFactory.createCodeFormatter(null);
        }
        return myCodeFormatter;
    }

    private OrganizeImportsPostprocessor getImportsPostrocessor() {
        if (myImportsPostprocessor == null) {
            myImportsPostprocessor = new OrganizeImportsPostprocessor(isToRestoreExistingImports);
        }
        return myImportsPostprocessor;
    }

    private final void clearExceptionsList() {
        myExceptions.clear();
    }

    private final IStatus getExceptionsStatus() {
        if (myExceptions == null || myExceptions.isEmpty()) {
            return Status.OK_STATUS;
        } else {
            IStatus[] s = myExceptions.toArray(new IStatus[myExceptions.size()]);
            return new MultiStatus(Activator.getID(), 0, s, Messages.problems, null);
        }
    }

    protected static final class Counter {
        private final HashMap<EClass, Integer> myCounters = new HashMap<EClass, Integer>();
        private final HashMap<EClass, Integer> myCache = new HashMap<EClass, Integer>();
        private final Integer CACHE_MISS = new Integer(0);

        public Counter() {
        }

        public void registerFactor(EClass eClass, int count) {
            myCounters.put(eClass, count);
        }

        public int getTotal(EObject from) {
            int total = process(from);
            for (Iterator<EObject> it = from.eAllContents(); it.hasNext();) {
                total += process(it.next());
            }
            return total;
        }

        protected int process(EObject next) {
            final EClass nextKey = next.eClass();
            Integer cachedValue = checkCached(nextKey);
            if (cachedValue != null) {
                return cachedValue;
            }
            LinkedList<EClass> checkQueue = new LinkedList<EClass>();
            checkQueue.add(nextKey);
            do {
                EClass key = checkQueue.removeFirst();
                if (myCounters.containsKey(key)) {
                    final Integer value = myCounters.get(key);
                    cache(nextKey, value);
                    return value;
                } else {
                    // add immeditate superclasses to check first
                    checkQueue.addAll(key.getESuperTypes());
                }
            } while (!checkQueue.isEmpty());
            cache(nextKey, CACHE_MISS);
            return 0;
        }

        private Integer checkCached(EClass nextKey) {
            return myCache.get(nextKey);
        }

        private void cache(EClass nextKey, Integer value) {
            myCache.put(nextKey, value);
        }
    }
}