Java tutorial
/** * Aptana Studio * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.projects.wizards; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; 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.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.osgi.util.NLS; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.dialogs.WizardNewProjectReferencePage; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.ide.undo.CreateProjectOperation; import org.eclipse.ui.ide.undo.WorkspaceUndoUtil; import org.eclipse.ui.statushandlers.IStatusAdapterConstants; import org.eclipse.ui.statushandlers.StatusAdapter; import org.eclipse.ui.statushandlers.StatusManager; import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard; import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard; import com.aptana.core.logging.IdeLog; import com.aptana.core.projects.templates.IProjectTemplate; import com.aptana.core.projects.templates.ProjectTemplate; import com.aptana.core.projects.templates.TemplateType; import com.aptana.core.util.ArrayUtil; import com.aptana.core.util.ProcessStatus; import com.aptana.core.util.ResourceUtil; import com.aptana.core.util.StringUtil; import com.aptana.git.core.model.GitExecutable; import com.aptana.git.ui.CloneJob; import com.aptana.projects.ProjectsPlugin; import com.aptana.projects.internal.wizards.Messages; import com.aptana.projects.listeners.IStudioProjectListener; import com.aptana.projects.listeners.StudioProjectListenersManager; import com.aptana.projects.templates.IDefaultProjectTemplate; import com.aptana.ui.util.UIUtils; import com.aptana.usage.FeatureEvent; import com.aptana.usage.StudioAnalytics; /** * New Project Wizard base class. * * @author Nam Le <nle@appcelerator.com> */ public abstract class AbstractNewProjectWizard extends BasicNewResourceWizard implements IExecutableExtension { public static final String TEMPLATE_SELECTION_PAGE_NAME = "templateSelectionPage"; //$NON-NLS-1$ // The pages in the wizard. DO NOT ACCESS VALUES FROM THEM OUTSIDE performFinish()! protected IWizardProjectCreationPage mainPage; protected ProjectTemplateSelectionPage templatesPage; protected WizardNewProjectReferencePage referencePage; protected String projectTemplateId; protected IProjectTemplate selectedTemplate; protected IConfigurationElement configElement; protected IProject newProject; private URI location; // null if defaults are used (under workspace) private IPath destPath; // absolute path to project we're creating. private IProject[] refProjects; // Specifies the steps to use in step indicator composite protected String[] stepNames; /** * Constructs a new Web Project Wizard. */ public AbstractNewProjectWizard() { initDialogSettings(); } /** * Returns the project nature-id's. * * @return The natures to be set to the project. */ protected abstract String[] getProjectNatures(); /** * Returns the project builder-id's. * * @return The builders to be set to the project. */ protected abstract String[] getProjectBuilders(); /** * Returns a description string for the project creation operation. * * @return a description string */ protected abstract String getProjectCreationDescription(); /** * Return an array of the template types that should be displayed for this wizard. * * @return */ protected abstract TemplateType[] getProjectTemplateTypes(); /** * Initialize the wizard's dialog-settings.<br> * Subclasses should override to provide specific initialization. */ protected void initDialogSettings() { IDialogSettings workbenchSettings = ProjectsPlugin.getDefault().getDialogSettings(); IDialogSettings section = workbenchSettings.getSection("BasicNewProjectResourceWizard");//$NON-NLS-1$ if (section == null) { section = workbenchSettings.addNewSection("BasicNewProjectResourceWizard");//$NON-NLS-1$ } setDialogSettings(section); } /** * Add pages to the wizard.<br> * By default, we don't add the reference page to the base Web Project (subclasses may override). * * @see org.eclipse.jface.wizard.Wizard#addPages() */ @Override public void addPages() { super.addPages(); TemplateType[] templateTypes = getProjectTemplateTypes(); validateProjectTemplate(templateTypes); LinkedHashMap<String, IStepIndicatorWizardPage> stepPages = new LinkedHashMap<String, IStepIndicatorWizardPage>(); // Add the template selection page List<IProjectTemplate> templates = ProjectsPlugin.getDefault().getTemplatesManager() .getTemplates(templateTypes); if (hasNonDefaultTemplates(templates) && selectedTemplate == null) { addPage(templatesPage = new ProjectTemplateSelectionPage(TEMPLATE_SELECTION_PAGE_NAME, templates)); stepPages.put(templatesPage.getStepName(), templatesPage); } // Add the main page where we set up the project name/location addPage(mainPage = createMainPage()); if (mainPage instanceof IStepIndicatorWizardPage) { stepPages.put(((IStepIndicatorWizardPage) mainPage).getStepName(), (IStepIndicatorWizardPage) mainPage); } // Add contributed pages ProjectWizardContributionManager projectWizardContributionManager = ProjectsPlugin.getDefault() .getProjectWizardContributionManager(); IWizardPage[] extraPages = projectWizardContributionManager.createPages(getProjectNatures()); if (!ArrayUtil.isEmpty(extraPages)) { for (IWizardPage page : extraPages) { addPage(page); if (page instanceof IStepIndicatorWizardPage) { stepPages.put(((IStepIndicatorWizardPage) page).getStepName(), (IStepIndicatorWizardPage) page); } } } // Set up the steps stepNames = stepPages.keySet().toArray(new String[stepPages.size()]); if (stepNames.length > 1) { for (IStepIndicatorWizardPage page : stepPages.values()) { page.initStepIndicator(stepNames); } } // Finalize pages using contributors projectWizardContributionManager.finalizeWizardPages(getPages(), getProjectNatures()); } protected IWizardProjectCreationPage createMainPage() { CommonWizardNewProjectCreationPage mainPage = new CommonWizardNewProjectCreationPage("basicNewProjectPage", //$NON-NLS-1$ selectedTemplate); mainPage.setTitle(Messages.NewProjectWizard_ProjectPage_Title); mainPage.setDescription(Messages.NewProjectWizard_ProjectPage_Description); return mainPage; } @Override public boolean performFinish() { newProject = mainPage.getProjectHandle(); destPath = mainPage.getLocationPath(); location = null; if (!mainPage.useDefaults()) { location = mainPage.getLocationURI(); } else { destPath = destPath.append(newProject.getName()); } if (templatesPage != null) { selectedTemplate = templatesPage.getSelectedTemplate(); } if (referencePage != null) { refProjects = referencePage.getReferencedProjects(); } boolean success = false; try { getContainer().run(true, true, new IRunnableWithProgress() { public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { IWorkspaceRunnable runnable = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { SubMonitor subMonitor = SubMonitor.convert(monitor, 6); try { createNewProject(subMonitor.newChild(4)); } catch (InvocationTargetException e) { throw new CoreException(new Status(IStatus.ERROR, ProjectsPlugin.PLUGIN_ID, 0, e.getMessage(), e.getTargetException())); } // Allow the project contributors to do work ProjectWizardContributionManager projectWizardContributionManager = ProjectsPlugin .getDefault().getProjectWizardContributionManager(); final IStatus contributorStatus = projectWizardContributionManager .performProjectFinish(newProject, subMonitor.newChild(1)); if (contributorStatus != null && !contributorStatus.isOK()) { // FIXME This UI code shouldn't be here, throw an exception up and handle it! // Show the error. Should we cancel project creation? UIUtils.getDisplay().syncExec(new Runnable() { public void run() { MessageDialog.openError(UIUtils.getActiveWorkbenchWindow().getShell(), Messages.AbstractNewProjectWizard_ProjectListenerErrorTitle, contributorStatus.getMessage()); } }); } // Perform post project hooks IStudioProjectListener[] projectListeners = new IStudioProjectListener[0]; IProjectDescription description = newProject.getDescription(); if (description != null) { projectListeners = StudioProjectListenersManager.getManager() .getProjectListeners(description.getNatureIds()); } int listenerSize = projectListeners.length; SubMonitor hookMonitor = SubMonitor.convert(subMonitor.newChild(1), Messages.AbstractNewProjectWizard_ProjectListener_TaskName, Math.max(1, listenerSize)); for (IStudioProjectListener projectListener : projectListeners) { if (projectListener != null) { final IStatus status = projectListener.projectCreated(newProject, hookMonitor.newChild(1)); // Show a dialog if there are failures if (status != null && status.matches(IStatus.ERROR)) { // FIXME This UI code shouldn't be here, throw an exception up and handle it! UIUtils.getDisplay().syncExec(new Runnable() { public void run() { String message = status.getMessage(); if (status instanceof ProcessStatus) { message = ((ProcessStatus) status).getStdErr(); } MessageDialog.openError( UIUtils.getActiveWorkbenchWindow().getShell(), Messages.AbstractNewProjectWizard_ProjectListenerErrorTitle, message); } }); } } } } }; try { ResourcesPlugin.getWorkspace().run(runnable, monitor); } catch (CoreException e) { throw new InvocationTargetException(e, Messages.AbstractNewProjectWizard_ProjectListener_NoDescriptor_Error); } } }); success = true; } catch (InterruptedException e) { StatusManager.getManager().handle( new Status(IStatus.ERROR, ProjectsPlugin.PLUGIN_ID, e.getMessage(), e), StatusManager.BLOCK); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof ExecutionException && t.getCause() instanceof CoreException) { CoreException cause = (CoreException) t.getCause(); StatusAdapter status; if (cause.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) { status = new StatusAdapter(new Status(IStatus.WARNING, ProjectsPlugin.PLUGIN_ID, NLS.bind(Messages.NewProjectWizard_Warning_DirectoryExists, mainPage.getProjectHandle().getName()), cause)); } else { status = new StatusAdapter(new Status(cause.getStatus().getSeverity(), ProjectsPlugin.PLUGIN_ID, Messages.NewProjectWizard_CreationProblem, cause)); } status.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, Messages.NewProjectWizard_CreationProblem); StatusManager.getManager().handle(status, StatusManager.BLOCK); } else { StatusAdapter status = new StatusAdapter(new Status(IStatus.WARNING, ProjectsPlugin.PLUGIN_ID, 0, NLS.bind(Messages.NewProjectWizard_InternalError, t.getMessage()), t)); status.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, Messages.NewProjectWizard_CreationProblem); StatusManager.getManager().handle(status, StatusManager.LOG | StatusManager.BLOCK); } } if (!success) { return false; } // TODO Run all of this in a job? updatePerspective(); selectAndReveal(newProject); openIndexFile(); sendProjectCreateEvent(); return true; } /** * Returns the {@link IProject} reference that was created by this wizard. Note that the result may be * <code>null</code> if called before the project was created. * * @return An {@link IProject} (can be <code>null</code>). */ public IProject getCreatedProject() { return newProject; } protected abstract String getProjectCreateEventName(); protected void sendProjectCreateEvent() { Map<String, String> payload = generatePayload(); StudioAnalytics.getInstance().sendEvent(new FeatureEvent(getProjectCreateEventName(), payload)); } /** * Generates the payload used for analytics on project creation events. * * @return */ protected Map<String, String> generatePayload() { Map<String, String> payload = new HashMap<String, String>(); payload.put("name", newProject.getName()); //$NON-NLS-1$ if (selectedTemplate != null) { String id = selectedTemplate.getId(); if (StringUtil.isEmpty(id)) { payload.put("template", "custom.template-" + selectedTemplate.getDisplayName()); //$NON-NLS-1$ //$NON-NLS-2$ } else { payload.put("template", id); //$NON-NLS-1$ } } return payload; } /* * (non-Javadoc) * @see * org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement * , java.lang.String, java.lang.Object) */ public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { configElement = config; if (ProjectTemplateSelectionPage.COMMAND_PROJECT_FROM_TEMPLATE_PROJECT_TEMPLATE_NAME.equals(propertyName)) { if (data instanceof String) { projectTemplateId = (String) data; } } } protected void validateProjectTemplate(TemplateType[] templateType) { selectedTemplate = null; if (projectTemplateId != null) { List<IProjectTemplate> templates = ProjectsPlugin.getDefault().getTemplatesManager() .getTemplates(templateType); for (IProjectTemplate template : templates) { if (template.getId() != null && template.getId().equals(projectTemplateId)) { selectedTemplate = template; return; } } } } /* * (non-Javadoc) * @see org.eclipse.ui.wizards.newresource.BasicNewResourceWizard#init(org.eclipse.ui.IWorkbench, * org.eclipse.jface.viewers.IStructuredSelection) */ @Override public void init(IWorkbench workbench, IStructuredSelection currentSelection) { super.init(workbench, currentSelection); setNeedsProgressMonitor(true); } private void updatePerspective() { BasicNewProjectResourceWizard.updatePerspective(configElement); } /** * Creates a new project resource with the selected name. * <p> * In normal usage, this method is invoked after the user has pressed Finish on the wizard; the enablement of the * Finish button implies that all controls on the pages currently contain valid values. * </p> * <p> * Note that this wizard caches the new project once it has been successfully created; subsequent invocations of * this method will answer the same project resource without attempting to create it again. * </p> * * @param monitor * TODO * @return the created project resource, or <code>null</code> if the project was not created */ protected IProject createNewProject(IProgressMonitor monitor) throws InvocationTargetException { SubMonitor sub = SubMonitor.convert(monitor, 100); // Project description creation IProjectDescription description = ResourceUtil.getProjectDescription(destPath, getProjectNatures(), getProjectBuilders()); description.setName(newProject.getName()); description.setLocationURI(location); // Update the referenced project in case it was initialized. if (refProjects != null && refProjects.length > 0) { description.setReferencedProjects(refProjects); } sub.worked(10); if (!applySourcedProjectFilesAfterProjectCreated() && isCloneFromGit()) { cloneFromGit(newProject, description, sub.newChild(90)); } else { doBasicCreateProject(newProject, description, sub.newChild(75)); if (!applySourcedProjectFilesAfterProjectCreated() && selectedTemplate != null && !isCloneFromGit()) { selectedTemplate.apply(newProject, true); } } return newProject; } /** * If a wizard needs to apply project files from an additional source (git clone or template) after a project has * been generated (say by a script/process), this should return true. * * @return */ protected boolean applySourcedProjectFilesAfterProjectCreated() { return false; } protected boolean isCloneFromGit() { // FIXME Move this logic into the IProjectTemplate#apply method? return selectedTemplate != null && !selectedTemplate.getLocation().endsWith(".zip"); //$NON-NLS-1$ } /** * Clone a project from a GIT repository. * * @param newProjectHandle * @param description * @param monitor * @throws InvocationTargetException */ protected void cloneFromGit(IProject newProjectHandle, IProjectDescription description, IProgressMonitor monitor) throws InvocationTargetException { doCloneFromGit(selectedTemplate.getLocation(), newProjectHandle, description, monitor); } /** * Performs a git clone to a temporary location and then copies the files over top the already generated project. * This is because git cannot clone into an existing directory. * * @param monitor * @throws Exception */ protected void cloneAfter(IProgressMonitor monitor) throws Exception { SubMonitor sub = SubMonitor.convert(monitor, Messages.AbstractNewProjectWizard_CloningFromGitMsg, 100); // clone to tmp dir then copy files over top the project! File tmpFile = File.createTempFile("delete_me", "tmp"); //$NON-NLS-1$ //$NON-NLS-2$ File dest = new File(tmpFile.getParent(), "git_clone_tmp"); //$NON-NLS-1$ GitExecutable.instance().clone(selectedTemplate.getLocation(), Path.fromOSString(dest.getAbsolutePath()), true, sub.newChild(85)); IFileStore tmpClone = EFS.getStore(dest.toURI()); // Wipe the .git folder before copying? Wipe the .project file before copying? IFileStore dotGit = tmpClone.getChild(".git"); //$NON-NLS-1$ dotGit.delete(EFS.NONE, sub.newChild(2)); IFileStore dotProject = tmpClone.getChild(IProjectDescription.DESCRIPTION_FILE_NAME); if (dotProject.fetchInfo().exists()) { dotProject.delete(EFS.NONE, sub.newChild(1)); } // OK, copy the cloned template's contents over IFileStore projectStore = EFS.getStore(newProject.getLocationURI()); tmpClone.copy(projectStore, EFS.OVERWRITE, sub.newChild(9)); // Now get rid of the temp clone! tmpClone.delete(EFS.NONE, sub.newChild(3)); sub.done(); } /** * Perform a basic project creation. * * @param project * @param description * @param subMonitor * @throws CoreException */ private void doBasicCreateProject(IProject project, final IProjectDescription description, IProgressMonitor monitor) throws InvocationTargetException { CreateProjectOperation op = new CreateProjectOperation(description, getProjectCreationDescription()); try { // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=219901 // directly execute the operation so that the undo state is // not preserved. Making this undoable resulted in too many // accidental file deletions. op.execute(monitor, WorkspaceUndoUtil.getUIInfoAdapter(getShell())); } catch (ExecutionException e) { throw new InvocationTargetException(e); } } /** * Do a GIT clone from a given source URI * * @param sourceURI * @param projectHandle * @param projectDescription * @param monitor * @throws InvocationTargetException */ protected void doCloneFromGit(String sourceURI, final IProject projectHandle, final IProjectDescription projectDescription, IProgressMonitor monitor) throws InvocationTargetException { SubMonitor sub = SubMonitor.convert(monitor, 100); CloneJob job = new CloneJob(sourceURI, destPath.toOSString(), true, true); // We're executing inside the wizard's container already, run sync. IStatus status = job.run(sub.newChild(100)); if (!status.isOK()) { if (status instanceof ProcessStatus) { ProcessStatus ps = (ProcessStatus) status; String stderr = ps.getStdErr(); throw new InvocationTargetException( new CoreException(new Status(status.getSeverity(), status.getPlugin(), stderr))); } throw new InvocationTargetException(new CoreException(status)); } sub.done(); } protected void openIndexFile() { IFile indexFile = newProject.getFile("index.html"); //$NON-NLS-1$ if (indexFile.exists()) { IWorkbenchPage page = UIUtils.getActivePage(); if (page != null) { try { IDE.openEditor(page, indexFile); } catch (PartInitException e) { IdeLog.logError(ProjectsPlugin.getDefault(), Messages.NewProjectWizard_ERR_OpeningIndex, e); } } } } protected IPath getDestinationPath() { return destPath; } /** * @deprecated Please use {@link IProjectTemplate#apply(IProject, boolean)} * @param template * @param project * @param preExistingResources * A possible conflicting list of resources that the extraction should notify about to the user. */ public static void extractZip(IProjectTemplate template, IProject project, Set<IPath> preExistingResources) { template.apply(project, !preExistingResources.isEmpty()); } /** * @deprecated Please use {@link IProjectTemplate#apply(IProject, boolean)} * @param template * @param project * @param promptForOverwrite */ public static void extractZip(IProjectTemplate template, IProject project, boolean promptForOverwrite) { template.apply(project, promptForOverwrite); } /** * Extracts a zip into a given project. * * @deprecated Please use {@link IProjectTemplate#apply(IProject, boolean)} * @param zipPath * @param project * @param promptForOverwrite * Indicate that we should display a prompt in case the zip overwrites some of the existing project * files. * @param preExistingResources * A defined list of resources that will be used when prompting for overwrite conflicts. In case of an * empty list, the function will prompt on any overwritten file. * @param isReplacingParameters */ public static void extractZip(final File zipPath, final IProject project, boolean promptForOverwrite, Set<IPath> preExistingResources, final boolean isReplacingParameters) { new ProjectTemplate(zipPath.getAbsolutePath(), null, "", isReplacingParameters, "", null, "").apply(project, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ promptForOverwrite); } private static boolean hasNonDefaultTemplates(List<IProjectTemplate> templates) { for (IProjectTemplate template : templates) { if (!(template instanceof IDefaultProjectTemplate)) { return true; } } return false; } }