/*=============================================================================#
# Copyright (c) 2005-2016 Stephan Wahlbrink (WalWare.de) 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:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.statet.ext.ui.wizards;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
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.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
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.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.ContainerGenerator;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;
import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
import de.walware.ecommons.ltk.ui.util.LTKWorkbenchUIUtil;
import de.walware.ecommons.ui.util.UIAccess;
import de.walware.statet.base.internal.ui.StatetUIPlugin;
public abstract class NewElementWizard extends Wizard implements INewWizard, IExecutableExtension {
protected static class NewFileCreator {
protected IPath fContainerPath;
protected String fResourceName;
private IContentType contentType;
protected IRegion fInitialSelection;
/** No direct access, use getFileHandle() */
private IFile fCachedFileHandle;
public NewFileCreator(final IPath containerPath, final String resourceName) {
fContainerPath = containerPath;
fResourceName = resourceName;
}
public NewFileCreator(final IPath containerPath, final String resourceName,
IContentType contentType) {
this(containerPath, resourceName);
this.contentType= (contentType != null) ? contentType :
Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
}
/**
* Return the filehandle of the new file.
* The Filehandle is cached. File can exists or not exists.
* @return
*/
public IFile getFileHandle() {
if (fCachedFileHandle == null) {
final IPath newFilePath = fContainerPath.append(fResourceName);
fCachedFileHandle = createFileHandle(newFilePath);
}
return fCachedFileHandle;
}
/**
* Creates a file resource handle for the file with the given workspace path.
* This method does not create the file resource; this is the responsibility
* of <code>createFile</code>.
*
* @param filePath the path of the file resource to create a handle for
* @return the new file resource handle
* @see #createFile
*/
protected IFile createFileHandle(final IPath filePath) {
return ResourcesPlugin.getWorkspace().getRoot().getFile(filePath);
}
/**
* Creates a new file resource in the selected container and with the selected
* name. Creates any missing resource containers along the path; does nothing if
* the container resources already exist.
* <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
* on this page currently contain valid values.
* </p>
* <p>
* Note that this page caches the new file once it has been successfully
* created; subsequent invocations of this method will answer the same
* file resource without attempting to create it again.
* </p>
* <p>
* This method should be called within a workspace modify operation since
* it creates resources.
* </p>
*
* @return the created file resource, or <code>null</code> if the file
* was not created
* @throws InterruptedException
* @throws InvocationTargetException
* @throws CoreException
*/
public void createFile(final IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException, CoreException {
final IPath containerPath = fContainerPath;
final IFile newFileHandle = getFileHandle();
assert (containerPath != null);
assert (newFileHandle != null);
try {
final SubMonitor progress= SubMonitor.convert(monitor,
NLS.bind(StatetWizardsMessages.NewElement_CreateFile_task, newFileHandle.getName()),
5);
final InputStream initialContents= getInitialFileContentStream(newFileHandle,
progress.newChild(1) );
final ContainerGenerator generator = new ContainerGenerator(containerPath);
generator.generateContainer(progress.newChild(2));
doCreateFile(newFileHandle, initialContents, progress.newChild(3));
}
finally {
monitor.done();
}
}
/**
* Creates a file resource given the file handle and contents.
*
* @param fileHandle the file handle to create a file resource with
* @param contents the initial contents of the new file resource, or
* <code>null</code> if none (equivalent to an empty stream)
* @param progress the progress monitor to show visual progress with
* @exception CoreException if the operation fails
* @exception OperationCanceledException if the operation is canceled
*/
private static void doCreateFile(final IFile fileHandle, InputStream contents,
final SubMonitor progress) throws CoreException {
if (contents == null) {
contents = new ByteArrayInputStream(new byte[0]);
}
try {
progress.beginTask(null, 1000);
// Create a new file resource in the workspace
final IPath path = fileHandle.getFullPath();
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
final int numSegments = path.segmentCount();
if (numSegments > 2 && !root.getFolder(path.removeLastSegments(1)).exists()) {
// If the direct parent of the path doesn't exist, try to create the
// necessary directories.
for (int i = numSegments - 2; i > 0; i--) {
final IFolder folder = root.getFolder(path.removeLastSegments(i));
if (!folder.exists()) {
folder.create(false, true, progress.newChild(500/(numSegments-2)));
}
}
}
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
fileHandle.create(contents, false, progress.newChild(500));
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
}
catch (final CoreException e) {
// If the file already existed locally, just refresh to get contents
if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
fileHandle.refreshLocal(IResource.DEPTH_ZERO, null);
}
else {
throw e;
}
}
}
/**
* Returns a stream containing the initial contents to be given to new file resource
* instances. <b>Subclasses</b> may wish to override. This default implementation
* provides no initial contents.
* @param subMonitor
*
* @return initial contents to be given to new file resource instances
*/
protected InputStream getInitialFileContentStream(final IFile newFileHandle,
final SubMonitor progress) {
final String content = getInitialFileContent(newFileHandle, progress);
if (content == null) {
return null;
}
try {
// encoding of content type
final IContentType contentType= getContentType(newFileHandle);
if (contentType != null) {
final String charset = contentType.getDefaultCharset();
if (charset != null) {
return new ByteArrayInputStream(content.getBytes(charset));
}
}
// encoding of container
final String charset = newFileHandle.getCharset(true);
if (charset != null) {
return new ByteArrayInputStream(content.getBytes(charset));
}
}
catch (final UnsupportedEncodingException e) {
}
catch (final CoreException e) {
}
return new ByteArrayInputStream(content.getBytes());
}
/**
* Returns the content type of the new file.
* Used e.g. to lookup the encoding.
* @return id
*/
public IContentType getContentType(final IFile newFileHandle) {
return this.contentType;
}
/**
* Returns a stream containing the initial contents to be given to new file resource
* instances. <b>Subclasses</b> may wish to override. This default implementation
* provides no initial contents.
* @param progress
*
* @return initial contents to be given to new file resource instances
*/
protected String getInitialFileContent(final IFile newFileHandle,
final SubMonitor progress) {
return null;
}
public IRegion getInitialSelection() {
return fInitialSelection;
}
}
protected class ProjectCreator {
/** Name of project */
protected String fProjectName;
/** Path of project, null for default location */
protected IPath fNewPath;
private IProject fCachedProjectHandle;
private final IProject[] fRefProjects;
private final IWorkingSet[] fWorkingSets;
public ProjectCreator(final String name, final IPath path,
final IProject[] projects, final IWorkingSet[] workingSets) {
fProjectName = name;
fNewPath = path;
fRefProjects = projects;
fWorkingSets = workingSets;
}
/**
* Returns a project resource handle for the current project name field value.
* <p>
* Note: Handle is cached. This method does not create the project resource;
* this is the responsibility of <code>IProject::create</code>.
* </p>
*
* @return the project resource handle
*/
public IProject getProjectHandle() {
if (fCachedProjectHandle == null) {
fCachedProjectHandle = ResourcesPlugin.getWorkspace().getRoot().getProject(fProjectName);
}
return fCachedProjectHandle;
}
/**
* 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>
* @return
*
* @return the created project resource, or <code>null</code> if the
* project was not created
* @throws CoreException
*/
public IProject createProject(final IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException, CoreException {
final IProject projectHandle = getProjectHandle();
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IProjectDescription description = workspace.newProjectDescription(projectHandle.getName());
description.setLocation(fNewPath);
// update the referenced project if provided
if (fRefProjects != null && fRefProjects.length > 0) {
description.setReferencedProjects(fRefProjects);
}
try {
Assert.isNotNull(projectHandle);
monitor.beginTask(StatetWizardsMessages.NewElement_CreateProject_task, 2500);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
doCreateProject(projectHandle, description, new SubProgressMonitor(monitor, 1000));
doConfigProject(projectHandle, new SubProgressMonitor(monitor, 1000));
doAddToWorkingSets(projectHandle, new SubProgressMonitor(monitor, 500));
return projectHandle;
}
finally {
monitor.done();
}
}
private void doCreateProject(final IProject project, final IProjectDescription description, final IProgressMonitor monitor)
throws CoreException {
// run the new project creation operation
monitor.beginTask("Install Project", 1000); //$NON-NLS-1$
project.create(description, new SubProgressMonitor(monitor, 500));
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
project.open(new SubProgressMonitor(monitor, 500));
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
protected void doConfigProject(final IProject project, final IProgressMonitor monitor) throws CoreException {
}
private void doAddToWorkingSets(final IProject project, final IProgressMonitor monitor) {
if (fWorkingSets != null && fWorkingSets.length > 0) {
monitor.beginTask(StatetWizardsMessages.NewElement_AddProjectToWorkingSet_task, 1);
getWorkbench().getWorkingSetManager().addToWorkingSets(project, fWorkingSets);
}
monitor.done();
}
}
private IWorkbench fWorkbench;
private IStructuredSelection fSelection;
private IConfigurationElement fConfigElement;
public NewElementWizard() {
setNeedsProgressMonitor(true);
}
/**
* Stores the configuration element for the wizard. The config element will
* be used in <code>performFinish</code> to set the result perspective.
*/
@Override
public void setInitializationData(final IConfigurationElement config, final String propertyName, final Object data) {
fConfigElement = config;
}
/**
* Subclasses should override to perform the actions of the wizard.
* This method is run in the wizard container's context as a workspace runnable.
* @param monitor
* @throws InterruptedException
* @throws CoreException
* @throws InvocationTargetException
*/
protected abstract void doFinish(IProgressMonitor monitor) throws InterruptedException, CoreException, InvocationTargetException;
/**
* @return the scheduling rule for creating the element.
*/
protected ISchedulingRule getSchedulingRule() {
return ResourcesPlugin.getWorkspace().getRoot(); // look all by default
}
/**
* @return true if the runnable should be run in a separate thread, and false to run in the same thread
*/
protected boolean canRunForked() {
return true;
}
// public abstract IJavaElement getCreatedElement();
@Override
public void init(final IWorkbench workbench, final IStructuredSelection currentSelection) {
fWorkbench = workbench;
fSelection = currentSelection;
}
@Override
public boolean performFinish() {
final WorkspaceModifyOperation op = new WorkspaceModifyOperation(getSchedulingRule()) {
@Override
protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException {
try {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
doFinish(monitor);
}
catch (final InterruptedException e) {
throw new OperationCanceledException(e.getMessage());
}
catch (final CoreException e) {
throw new InvocationTargetException(e);
}
finally {
monitor.done();
}
}
};
try {
getContainer().run(canRunForked(), true, op);
} catch (final InvocationTargetException e) {
handleFinishException(getShell(), e);
return false;
} catch (final InterruptedException e) {
return false;
}
return true;
}
protected void handleFinishException(final Shell shell, final InvocationTargetException e) {
StatusManager.getManager().handle(new Status(Status.ERROR, StatetUIPlugin.PLUGIN_ID, -1,
StatetWizardsMessages.NewElement_error_DuringOperation_message, e),
StatusManager.LOG | StatusManager.SHOW);
}
public IStructuredSelection getSelection() {
return fSelection;
}
public IWorkbench getWorkbench() {
return fWorkbench;
}
/* Helper methods for subclasses **********************************************/
/**
* Returns the scheduling rule to use when creating the resource at
* the given container path. The rule should be the creation rule for
* the top-most non-existing parent.
* @param resource The resource being created
* @return The scheduling rule for creating the given resource
*/
protected ISchedulingRule createRule(IResource resource) {
IResource parent = resource.getParent();
while (parent != null) {
if (parent.exists()) {
return resource.getWorkspace().getRuleFactory().createRule(resource);
}
resource = parent;
parent = parent.getParent();
}
return resource.getWorkspace().getRoot();
}
protected void openResource(final IFile resource) {
final IWorkbenchPage activePage = UIAccess.getActiveWorkbenchPage(true);
if (activePage != null) {
LTKWorkbenchUIUtil.openEditor(activePage, resource, null);
}
}
protected void openResource(final NewFileCreator file) {
if (file.getFileHandle() == null) {
return;
}
final IWorkbenchPage activePage = UIAccess.getActiveWorkbenchPage(true);
if (activePage != null) {
LTKWorkbenchUIUtil.openEditor(activePage, file.getFileHandle(), file.getInitialSelection());
}
}
protected void selectAndReveal(final IResource newResource) {
BasicNewResourceWizard.selectAndReveal(newResource, fWorkbench.getActiveWorkbenchWindow());
}
protected void updatePerspective() {
BasicNewProjectResourceWizard.updatePerspective(fConfigElement);
}
}