org.eclipse.imp.wizards.WizardUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.imp.wizards.WizardUtilities.java

Source

/*******************************************************************************
* Copyright (c) 2007 IBM Corporation.
* 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:
*    Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation
*******************************************************************************/

package org.eclipse.imp.wizards;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.imp.WizardPlugin;
import org.eclipse.imp.core.ErrorHandler;
import org.eclipse.imp.utils.StreamUtils;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jdt.internal.core.JavaModel;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.osgi.framework.Bundle;

public class WizardUtilities {

    // Probably need to think about how to treat this in a more
    // principled manner (but the value is needed in one or more
    // of these methods)
    private static final String START_HERE = "// START_HERE";

    /**
     * Returns the bundle id for the bundle that contains this
     * utility class along with (presumably) most of the rest of
     * the IMP wizards.  Presently that is the bundle id for the
     * plugin org.eclipse.imp.metatooling.  Making it available here
     * means that the wizards in this plugin won't have to provide
     * it as a parameter in calls to several of the methods.
     */
    protected static String getTemplateBundleID() {
        return WizardPlugin.kPluginID;
    }

    /**
     * Returns the string representation of the workbench relative
     * path to the source location of the given project.
     * 
     * Note:  This method effectively duplicates one defined in IMPWizard.
     * That one is intended for use by wizards, where it is commonly needed.
     * This one is intended for users that are not wizards.
     * 
     * @param project   An IProject that also presumably represents
     *                an IJavaProject
     * @return         The string representation of the path, relative
     *                to the workbench, to the project source location
     */
    public static String getProjectSourceLocation(IProject project) {
        try {
            if (project == null)
                return null;
            JavaModel jm = JavaModelManager.getJavaModelManager().getJavaModel();
            IJavaProject jp = jm.getJavaProject(project);
            if (jp == null)
                return null;
            else {
                IPackageFragmentRoot[] roots = jp.getPackageFragmentRoots();
                for (int i = 0; i < roots.length; i++) {
                    if (roots[i].getCorrespondingResource() instanceof IFolder) {
                        IPath lcnPath = roots[i].getPath();
                        lcnPath = lcnPath.removeFirstSegments(1);
                        String lcn = lcnPath.toString();
                        if (lcn.startsWith("/"))
                            lcn = lcn.substring(1);
                        if (!lcn.endsWith("/"))
                            lcn = lcn + "/";
                        return lcn;
                    }
                }
            }
        } catch (JavaModelException e) {

        }
        return null;
    }

    /**
     * Creates a file of the given name from the named template in the given folder in the
     * given project. Subjects the template's contents to meta-variable substitution.
     * 
     * SMS 5 Sep 2007:  When working on NewPreferencesSpecificationWizard, I had occasion
     * to override ExstensionPointWizard.createFileFromTemplate(..) because that rendition
     * of the method had some elements specific to the creation of Java files and I wasn't
     * creating a Java file (or package).  Later I changed NewPreferencesSpecificationWizard
     * so that it did create a Java package, and I tried relying on createFileFromTemplate(..)
     * from ExtensionPointWizard.  Strangely (it seemed to me) I found that that version of
     * the method was creating a plain folder rather than a package folder.  I can't tell
     * why that was happening, and it evidently doesn't happen when we invoke the method to
     * generate Java files (which get generated into package folders).  In contrast, the
     * version of createFileFromTemplate(..) that I'd adpated for NewPreferencesSpecificationWizard
     * did create a package folder, even though I'd done nothing in particular to achieve that
     * result(not suspecting that there was anything that needed to, or could, be done).
     * On the assumption that we probably want to use ExtensionPointWizard to create Java
     * packages even when we're not immediately creating Java classes, I've substituted the
     * alternative version of createFileFromTemplate(..) here.
     * 
     * @param fileName      Unqualified name of the new file being created
     * @param templateName   Short (unqualified) name of the template file to be used
     *                   in creating the new file
     * @param folder      Name of the folder in which the new file will be created
     *                   (presumably a package folder)
     * @param replacements  A Map of meta-variable substitutions to apply to the template
     * @param project      The project in which the new file will be created
     * @param monitor      A monitor
     * @return             A handle to the file created
     * @throws CoreException
     */
    public static IFile createFileFromTemplate(String fileName, String templateName, String folder,
            String projectSourceLocation, Map<String, String> replacements, IProject project,
            IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName("ExtensionPointWizard.createFileFromTemplate:  Creating " + fileName);

        String packagePath = projectSourceLocation + folder.replace('.', '/');
        IPath specFilePath = new Path(packagePath + "/" + fileName);

        final IFile file = project.getFile(specFilePath);
        //      String templateContents= new String(getTemplateFileContents(templateName));
        //      String contents= performSubstitutions(templateContents, replacements);
        String contents = createFileContentsFromTemplate(templateName, replacements, monitor);

        if (fileName.endsWith(".java")) {
            contents = formatJavaCode(contents);
        }

        if (file.exists()) {
            file.setContents(new ByteArrayInputStream(contents.getBytes()), true, true, monitor);
        } else {
            createSubFolders(packagePath, project, monitor);
            file.create(new ByteArrayInputStream(contents.getBytes()), true, monitor);
        }
        //   monitor.worked(1);
        return file;
    }

    /**
     * Returns a String consisting of the contents of a named template file from the IMP
     * metatooling plugin substituted with meta-variable values provided by a given map.
     * 
     * @param templateName  Short (unqualified) name of the template file to be used
     *                      in creating the String
     * @param replacements  A Map of meta-variable substitutions to apply to the template
     * @param monitor       A monitor
     * @return              A String consisting of the template contents with meta-variables
     *                      substituted from the replacements
     * @throws CoreException
     */
    public static String createFileContentsFromTemplate(String templateName, Map<String, String> replacements,
            IProgressMonitor monitor) {
        return createFileContentsFromTemplate(templateName, getTemplateBundleID(), replacements, monitor);
    }

    /**
     * Returns a String consisting of the contents of a named template file in the
     * given plugin substituted with meta-variable values provided by a given map.
     * 
     * @param templateName  Short (unqualified) name of the template file to be used
     *                      in creating the String
     * @param pluginID      The ID of the plugin containing the template
     * @param replacements  A Map of meta-variable substitutions to apply to the template
     * @param monitor       A monitor
     * @return              A String consisting of the template contents with meta-variables
     *                      substituted from the replacements
     * @throws CoreException
     */
    public static String createFileContentsFromTemplate(String templateName, String bundleID,
            Map<String, String> replacements, IProgressMonitor monitor) {
        monitor.setTaskName("WizardUtilities.createFileContentsFromTemplate: template = " + templateName);

        String templateContents = new String(getTemplateFileContents(bundleID, templateName));
        String contents = performSubstitutions(templateContents, replacements);

        //   monitor.worked(1);
        return contents;
    }

    /**
     * Creates a file of the given name from the named template in the given folder in the
     * given project.  The template is sought in the bundle identified by the given template
     * bundle Id.  Subjects the template's contents to meta-variable substitution.
    *
     * This version of the method allows for relaxation of the assumption that the templates
     * are found in the bundle in which this class is found (org.eclipse.imp.metatooling).
     * 
     * @param fileName      Unqualified name of the new file being created
     * @param templatePluginId   The id of the plugin that contains the "templates" folder
     *                   in which the named template file is to be found
     * @param templateName   Short (unqualified) name of the template file to be used
     *                   in creating the new file
     * @param folder      Name of the folder in which the new file will be created
     *                   (presumably a package folder)
     * @param projectSourceLocation   The location of source packages/folders within the project
     * @param replacements  A Map of meta-variable substitutions to apply to the template
     * @param project      The project in which the new file will be created
     * @param monitor      A monitor
     * @return             A handle to the file created
     * @throws CoreException
     *    
     */
    public static IFile createFileFromTemplate(String fileName, String templateBundleId, String templateName,
            String folder, String projectSourceLocation, Map<String, String> replacements, IProject project,
            IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName("createFileFromTemplate:  Creating " + fileName);

        String packagePath = projectSourceLocation + folder.replace('.', '/');
        IPath specFilePath = new Path(packagePath + "/" + fileName);
        final IFile file = project.getFile(specFilePath);

        String templateContents = new String(getTemplateFileContents(templateBundleId, templateName));
        String contents = performSubstitutions(templateContents, replacements);

        if (fileName.endsWith(".java")) {
            contents = formatJavaCode(contents);
        }

        if (file.exists()) {
            file.setContents(new ByteArrayInputStream(contents.getBytes()), true, true, monitor);
        } else {
            createSubFolders(packagePath, project, monitor);
            file.create(new ByteArrayInputStream(contents.getBytes()), true, monitor);
        }
        //   monitor.worked(1);
        return file;
    }

    /**
     * Extends a file of the given name from the named template in the given folder in the
     * given project. Subjects the template's contents to meta-variable substitution.
     * @param fileName
     * @param templateName
     * @param folder
     * @param replacements a Map of meta-variable substitutions to apply to the template
     * @param project
     * @param monitor
     * @return a handle to the extended file
     * @throws CoreException
     * 
     * SMS 19 Jun 2007:  Added to support extension of existing files with updates by
     *                 later introduced language services (e.g., of language plugin file
     *                 with elements related to the preference service--which isn't actually
     *                 done yet, but which might reasonably be introduced)
     */
    protected static IFile extendFileFromTemplate(String fileName, String templateName, String folder,
            String projectSourceLocation, Map<String, String> replacements, IProject project,
            IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName("WizardUtilities.extendFileFromTemplate:  Extending " + fileName);

        final IFile file = project.getFile(new Path(projectSourceLocation + folder + "/" + fileName));
        if (!file.exists()) {
            throw new IllegalArgumentException();
        }
        file.refreshLocal(1, monitor);

        byte[] fileBytes = null;
        try {
            fileBytes = new byte[file.getContents().available()];
            file.getContents().read(fileBytes);
        } catch (IOException e) {
            ErrorHandler.reportError(
                    "WizardUtilities.extendFileFromTemplate(..):  IOException gettting contents of file = "
                            + fileName,
                    false, e);
        }
        String fileContents = new String(fileBytes);
        fileContents = fileContents.substring(0, fileContents.lastIndexOf("}")) + "\n";

        String extensionContents = new String(WizardUtilities.getTemplateFileContents(templateName));
        extensionContents = WizardUtilities.performSubstitutions(extensionContents, replacements);

        // Assume that the extension will properly close the class
        String newFileContents = fileContents + extensionContents;

        if (fileName.endsWith(".java")) {
            newFileContents = WizardUtilities.formatJavaCode(newFileContents);
        }

        file.setContents(new ByteArrayInputStream(newFileContents.getBytes()), true, true, monitor);

        return file;
    }

    /**
     * Like createFileFromTemplate, but does not attempt to perform any meta-variable substitutions.
     * Useful for binary files (e.g. images) that are to be copied as-is to the user's workspace.
     * The name of the source file is used for that of the target file.
     */
    protected static IFile copyLiteralFile(String fileName, String folder, IProject project,
            IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName("Creating " + fileName);

        final IFile file = project.getFile(new Path(folder + "/" + fileName));
        byte[] fileContents = WizardUtilities.getTemplateFileContents(fileName);

        if (file.exists()) {
            file.setContents(new ByteArrayInputStream(fileContents), true, true, monitor);
        } else {
            WizardUtilities.createSubFolders(folder, project, monitor);
            file.create(new ByteArrayInputStream(fileContents), true, monitor);
        }
        //   monitor.worked(1);
        return file;
    }

    /**
     * Like createFileFromTemplate, but does not attempt to perform any meta-variable substitutions.
     * Useful for binary files (e.g. images) that are to be copied as-is to the user's workspace.
     * This version allows the name of the target file to be specified independently of the source file.
     */
    protected static IFile copyLiteralFile(String inFileName, String outFileName, String folder, IProject project,
            IProgressMonitor monitor) throws CoreException {
        monitor.setTaskName("Creating " + outFileName + " as a copy of " + inFileName);

        final IFile file = project.getFile(new Path(folder + "/" + outFileName));
        byte[] fileContents = WizardUtilities.getTemplateFileContents(inFileName);

        if (file.exists()) {
            file.setContents(new ByteArrayInputStream(fileContents), true, true, monitor);
        } else {
            WizardUtilities.createSubFolders(folder, project, monitor);
            file.create(new ByteArrayInputStream(fileContents), true, monitor);
        }
        //   monitor.worked(1);
        return file;
    }

    public static void addBuilder(IProject project, String id) throws CoreException {
        IProjectDescription desc = project.getDescription();
        ICommand[] commands = desc.getBuildSpec();
        for (int i = 0; i < commands.length; ++i) {
            if (commands[i].getBuilderName().equals(id)) {
                return;
            }
        }
        //add builder to project
        ICommand command = desc.newCommand();
        command.setBuilderName(id);
        ICommand[] nc = new ICommand[commands.length + 1];
        // Add it before other builders.
        System.arraycopy(commands, 0, nc, 1, commands.length);
        nc[0] = command;
        desc.setBuildSpec(nc);
        project.setDescription(desc, null);
    }

    public static void enableBuilders(IProgressMonitor monitor, final IProject project, final String[] builderIDs) {
        monitor.setTaskName("Enabling builders...");
        Job job = new WorkspaceJob("Enabling builders...") {
            public IStatus runInWorkspace(IProgressMonitor monitor) {
                try {
                    for (int i = 0; i < builderIDs.length; i++) {
                        addBuilder(project, builderIDs[i]);
                    }
                } catch (Throwable e) {
                    e.printStackTrace();
                }
                return Status.OK_STATUS;
            }
        };
        job.schedule();
    }

    /**
     * Opens the given file in the appropriate editor for editing.<br>
     * If the file contains a comment "// START_HERE", the cursor will
     * be positioned just after that.
     * @param monitor
     * @param file
     */
    public static void editFile(IProgressMonitor monitor, final IFile file, Shell shell) {
        monitor.setTaskName("Opening file for editing...");
        shell.getDisplay().asyncExec(new Runnable() {
            public void run() {
                IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                try {
                    IEditorPart editorPart = IDE.openEditor(page, file, true);
                    AbstractTextEditor editor = (AbstractTextEditor) editorPart;
                    IFileEditorInput fileInput = (IFileEditorInput) editorPart.getEditorInput();
                    String contents = StreamUtils.readStreamContents(file.getContents(), file.getCharset());
                    int cursor = contents.indexOf(START_HERE);

                    if (cursor >= 0) {
                        TextSelection textSel = new TextSelection(
                                editor.getDocumentProvider().getDocument(fileInput), cursor, START_HERE.length());
                        editor.getEditorSite().getSelectionProvider().setSelection(textSel);
                    }
                } catch (PartInitException e) {
                } catch (CoreException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
        monitor.worked(1);
    }

    public static void createSubFolders(String folder, IProject project, IProgressMonitor monitor)
            throws CoreException {
        String[] subFolderNames = folder.split("[\\" + File.separator + "\\/]");
        String subFolderStr = "";

        for (int i = 0; i < subFolderNames.length; i++) {
            String childPath = subFolderStr + "/" + subFolderNames[i];
            Path subFolderPath = new Path(childPath);
            IFolder subFolder = project.getFolder(subFolderPath);

            if (!subFolder.exists())
                subFolder.create(true, true, monitor);
            subFolderStr = childPath;
        }
    }

    public static byte[] getTemplateFileContents(String filePath) {
        try {
            String path = null;

            if (!new File(filePath).exists()) {
                // Assume that the given filename is relative to the
                // standard templates directory
                Bundle bundle = Platform.getBundle(getTemplateBundleID());
                URL templateURL = FileLocator.find(bundle, new Path("/templates/" + filePath), null);
                if (templateURL == null) {
                    ErrorHandler.reportError("Unable to find template file: " + filePath, true);
                    return new byte[0];
                }
                URL url = FileLocator.toFileURL(templateURL);
                path = url.getPath();
            } else {
                path = filePath;
            }

            FileInputStream fis = new FileInputStream(path);
            DataInputStream is = new DataInputStream(fis);
            byte bytes[] = new byte[fis.available()];

            is.readFully(bytes);
            is.close();
            fis.close();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return ("// missing template file: " + filePath).getBytes();
        }
    }

    /**
     * @return the path to the standard IMP template folder, in platform-specific format
     */
    public static String getStandardTemplateFolderLocation() {
        try {
            // Initially, perform path computations in platform-independent format
            Bundle bundle = Platform.getBundle(getTemplateBundleID());
            URL templateURL = FileLocator.find(bundle, new Path("/templates/"), null);
            if (templateURL == null) {
                ErrorHandler.reportError("Unable to find template folder", true);
                return null;
            }
            URL url = FileLocator.toFileURL(templateURL);
            IPath path = new Path(url.getPath());

            return path.toOSString(); // Now, turn the path into platform-specific format
        } catch (Exception e) {
            e.printStackTrace();
            ErrorHandler.reportError("Exception finding template folder", true);
            return null;
        }
    }

    public static String getStandardTemplateFileName(IMPWizard wizard, String componentID) {
        if (wizard instanceof NewTokenColorer) {
            return "colorer_simple.java";
        }
        if (wizard instanceof NewTreeModelBuilder) {
            if (componentID.equals("TreeModelBuilder")) {
                return "treeModelBuilder.java";
            } else if (componentID.equals("LabelProvider")) {
                return ("labelProvider.java");
            }
        }
        if (wizard instanceof NewFoldingUpdater) {
            return "folder.java";
        }
        if (wizard instanceof NewHoverHelper) {
            if (componentID.equals("ReferenceResolver")) {
                return "referenceResolver.java";
            } else if (componentID.equals("DocumentationProvider")) {
                return "documentationProvider.java";
            } else {
                return "hoverHelper.java";
            }
        }
        if (wizard instanceof NewReferenceResolver) {
            return "referenceResolver.java";
        }
        if (wizard instanceof NewDocumentationProvider) {
            return "documentationProvider.java";
        }
        if (wizard instanceof NewContentProposer) {
            return "contentProposer.java";
        }
        if (wizard instanceof NewOccurrenceMarker) {
            return "occurrenceMarker.java";
        }
        if (wizard instanceof NewEditorActionsContributor) {
            return "editorActionsContributor.java";
        }
        if (wizard instanceof NewEditorService) {
            return "editorService.java";
        }
        if (wizard instanceof NewBuilder) {
            return "builder.java";
        }
        if (wizard instanceof NewNatureEnabler) {
            return "natureEnabler.java";
        }
        if (wizard instanceof NewCompiler) {
            return "compiler.java";
        }
        if (wizard instanceof NewEditorAnnotationCreator) {
            return "editorAnnotationCreator.java";
        }

        return null;
    }

    /**
     * Gets the contents of a named template file from the "templates" folder
     * of a plugin with a given plugin id.  Created for use with the version of
     * createFileFromTemplate(..) that also takes a plugin id.
     * 
     * @param templateBundleId   The id of the plugin that contains the templates
     *                      folder in which the template is to be found
     * @param fileName         The name of the template file for which contents
     *                      are to be returned
     * @return               The contents of the named template file
     */
    public static byte[] getTemplateFileContents(String templateBundleId, final String fileName) {
        try {
            Bundle bundle = Platform.getBundle(templateBundleId);
            URL templateURL = FileLocator.find(bundle, new Path("/templates/" + fileName), null);

            //            if (templateURL == null) {
            //                ErrorHandler.reportError("Unable to find template file: " + fileName, true);
            //                return new byte[0];
            //            }

            URL url = null;
            String path = null;
            if (templateURL != null) {
                url = FileLocator.toFileURL(templateURL);
                path = url.getPath();
            } else {
                // SMS 8 Aug 2008:  This is kind of a hack until the handling
                // of template names gets resolved again:
                // Pretend that the filename is a complete path
                path = fileName;
            }

            FileInputStream fis = new FileInputStream(path);
            DataInputStream is = new DataInputStream(fis);
            byte bytes[] = new byte[fis.available()];

            is.readFully(bytes);
            is.close();
            fis.close();
            return bytes;
        } catch (FileNotFoundException e) {
            final Display display = PlatformUI.getWorkbench().getDisplay();
            display.asyncExec(new Runnable() {
                public void run() {
                    MessageDialog.openError(display.getActiveShell(), "Unable to find code template",
                            "Error when attempting to locate code template " + fileName);
                }
            });
        } catch (Exception e) {
            MessageDialog.openError(null, "Unable to find code template",
                    "Error when attempting to locate code template " + fileName);
            e.printStackTrace();
        }
        return ("// missing template file: " + fileName).getBytes();
    }

    public static void replace(StringBuffer sb, String target, String substitute) {
        for (int index = sb.indexOf(target); index != -1; index = sb.indexOf(target))
            sb.replace(index, index + target.length(), substitute);
    }

    public static String performSubstitutions(String contents, Map<String, String> replacements) {
        StringBuffer buffer = new StringBuffer(contents);

        for (Iterator<String> iter = replacements.keySet().iterator(); iter.hasNext();) {
            String key = (String) iter.next();
            String value = (String) replacements.get(key);

            if (value != null)
                replace(buffer, key, value);
        }
        return buffer.toString();
    }

    public static String formatJavaCode(String contents) {
        CodeFormatter formatter = org.eclipse.jdt.core.ToolFactory.createCodeFormatter(JavaCore.getOptions());
        TextEdit te = formatter.format(CodeFormatter.K_COMPILATION_UNIT, contents, 0, contents.length(), 0, "\n");
        // SMS 15 Jan 2007:  this seems to happen sometimes; not sure why
        if (te == null)
            return contents;

        IDocument l_doc = new Document(contents);
        try {
            te.apply(l_doc);
        } catch (MalformedTreeException e) {
            e.printStackTrace();
        } catch (BadLocationException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            // Can happen that te is null
            e.printStackTrace();
        }
        contents = l_doc.get();
        return contents;
    }
}