From 802de810020fba3f86282cd1d66597a2a41698e3 Mon Sep 17 00:00:00 2001 From: Tor Norbye Date: Fri, 15 Jun 2012 17:42:41 -0700 Subject: Preview support in the templates This changeset adds a Preview page to the end of the template wizards which shows the changes to be applied to the project - first the files which were edited (merged), such as the manifest file in the case of a new activity, and second the text files to be created, and third the binary files to be created. In addition, the user can now uncheck any of these changes, if for example the manifest file edit isn't wanted. Furthermore, the now that the changes are computed up front, the phase of applying the changes is run in the background with a progress bar in the New-wizard. There's also some consolidation of the various template wizards to handle things like the upgrade-page and the dependency-page in one place. Infrastructure wise this changes the template instantiation from being based on File manipulation to using the refactoring infrastructure's change support, which should be more reliable. It also fixes a bug where projects were marked as library projects which should not have be. Change-Id: I496761f01c7ec28bf9170e4d1041211e3ebe285b --- .../META-INF/MANIFEST.MF | 3 +- .../com/android/ide/eclipse/adt/AdtConstants.java | 2 + .../src/com/android/ide/eclipse/adt/AdtPlugin.java | 20 +- .../src/com/android/ide/eclipse/adt/AdtUtils.java | 52 +++++ .../assetstudio/ConfigureAssetSetPage.java | 13 +- .../assetstudio/CreateAssetSetWizardState.java | 3 + .../wizards/newproject/NewProjectCreator.java | 10 +- .../wizards/templates/CreateFileChange.java | 107 +++++++++ .../wizards/templates/NewActivityWizard.java | 103 +++------ .../wizards/templates/NewProjectWizard.java | 186 +++++++++------- .../wizards/templates/NewTemplatePage.java | 4 +- .../wizards/templates/NewTemplateWizard.java | 97 ++------ .../wizards/templates/NewTemplateWizardState.java | 24 ++ .../wizards/templates/TemplateHandler.java | 248 +++++++++++++-------- .../wizards/templates/TemplatePreviewPage.java | 46 ++++ .../internal/wizards/templates/TemplateWizard.java | 187 ++++++++++++++++ 16 files changed, 756 insertions(+), 349 deletions(-) create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/CreateFileChange.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplatePreviewPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateWizard.java diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index dc443db..eb5edb2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -52,7 +52,8 @@ Require-Bundle: com.android.ide.eclipse.base, org.eclipse.jdt.junit.runtime, org.eclipse.ltk.core.refactoring, org.eclipse.ltk.ui.refactoring, - org.eclipse.core.expressions + org.eclipse.core.expressions, + org.eclipse.compare Eclipse-LazyStart: true Export-Package: com.android.ide.common.layout;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.log;x-friends:="com.android.ide.eclipse.tests", diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java index 9c1040e..1aa4221 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java @@ -127,6 +127,8 @@ public class AdtConstants { public static final String DOT_SVG = ".svg"; //$NON-NLS-1$ /** Dot-Extension for template files */ public static final String DOT_FTL = ".ftl"; //$NON-NLS-1$ + /** Dot-Extension of text files, i.e. ".txt" */ + public final static String DOT_TXT = ".txt"; //$NON-NLS-1$ /** Name of the android sources directory */ public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 42423b1..af8cb38 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -22,6 +22,8 @@ import static com.android.sdklib.SdkConstants.PLATFORM_LINUX; import static com.android.sdklib.SdkConstants.PLATFORM_WINDOWS; import com.android.AndroidConstants; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.log.ILogger; import com.android.ide.common.resources.ResourceFile; import com.android.ide.common.sdk.LoadStatus; @@ -49,6 +51,7 @@ import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; +import com.google.common.io.Closeables; import org.eclipse.core.commands.Command; import org.eclipse.core.resources.IFile; @@ -429,12 +432,16 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger, ISdkLog { * @param file the file to be read * @return the String read from the file, or null if there was an error */ - public static String readFile(IFile file) { + @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet + @Nullable + public static String readFile(@NonNull IFile file) { InputStream contents = null; + InputStreamReader reader = null; try { contents = file.getContents(); String charset = file.getCharset(); - return readFile(new InputStreamReader(contents, charset)); + reader = new InputStreamReader(contents, charset); + return readFile(reader); } catch (CoreException e) { // pass -- ignore files we can't read } catch (IOException e) { @@ -447,13 +454,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger, ISdkLog { // which is handled by this IOException catch. } finally { - try { - if (contents != null) { - contents.close(); - } - } catch (IOException e) { - // ignore - } + Closeables.closeQuietly(reader); + Closeables.closeQuietly(contents); } return null; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java index 18d488d..37b569d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java @@ -31,9 +31,13 @@ import com.android.sdklib.IAndroidTarget; import com.android.util.XmlUtils; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; +import com.google.common.io.ByteStreams; +import com.google.common.io.Closeables; import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -67,6 +71,7 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; import java.io.File; +import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; @@ -986,4 +991,51 @@ public class AdtUtils { return combined; } + + /** + * Reads the contents of an {@link IFile} and return it as a byte array + * + * @param file the file to be read + * @return the String read from the file, or null if there was an error + */ + @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet + @Nullable + public static byte[] readData(@NonNull IFile file) { + InputStream contents = null; + try { + contents = file.getContents(); + return ByteStreams.toByteArray(contents); + } catch (Exception e) { + // Pass -- just return null + } finally { + Closeables.closeQuietly(contents); + } + + return null; + } + + /** + * Ensure that a given folder (and all its parents) are created + * + * @param container the container to ensure exists + * @throws CoreException if an error occurs + */ + public static void ensureExists(@Nullable IContainer container) throws CoreException { + if (container == null || container.exists()) { + return; + } + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IFolder folder = root.getFolder(container.getFullPath()); + ensureExists(folder); + } + + private static void ensureExists(IFolder folder) throws CoreException { + if (folder != null && !folder.exists()) { + IContainer parent = folder.getParent(); + if (parent instanceof IFolder) { + ensureExists((IFolder) parent); + } + folder.create(false, false, null); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java index 187120c..0c9e5b7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java @@ -700,9 +700,11 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen if (source == mCropRadio) { mCropRadio.setSelection(true); // Ensure that you can't toggle it off mCenterRadio.setSelection(false); + mValues.crop = true; } else if (source == mCenterRadio) { mCenterRadio.setSelection(true); mCropRadio.setSelection(false); + mValues.crop = false; } if (source == mSquareRadio) { mValues.shape = GraphicGenerator.Shape.SQUARE; @@ -1117,15 +1119,8 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen generator = new LauncherIconGenerator(); LauncherIconGenerator.LauncherOptions launcherOptions = new LauncherIconGenerator.LauncherOptions(); - if (mCircleButton.getSelection()) { - launcherOptions.shape = GraphicGenerator.Shape.CIRCLE; - } else if (mSquareRadio.getSelection()) { - launcherOptions.shape = GraphicGenerator.Shape.SQUARE; - } else { - assert mNoShapeRadio.getSelection(); - launcherOptions.shape = GraphicGenerator.Shape.NONE; - } - launcherOptions.crop = mCropRadio.getSelection(); + launcherOptions.shape = mValues.shape; + launcherOptions.crop = mValues.crop; if (SUPPORT_LAUNCHER_ICON_TYPES) { launcherOptions.style = mFancyRadio.getSelection() ? diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java index 624a99a..62176ca 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/CreateAssetSetWizardState.java @@ -69,6 +69,9 @@ public class CreateAssetSetWizardState { /** The background shape */ public Shape shape = Shape.SQUARE; + /** Whether the image should be cropped */ + public boolean crop; + /** The background color to use for the shape (unless the shape is {@link Shape#NONE} */ public RGB background = new RGB(0xff, 0x00, 0x00); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java index ad34309..83ea758 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java @@ -641,7 +641,7 @@ public class NewProjectCreator { } public interface ProjectPopulator { - public void populate(IProject project); + public void populate(IProject project) throws InvocationTargetException; } /** @@ -693,7 +693,11 @@ public class NewProjectCreator { } if (projectPopulator != null) { - projectPopulator.populate(project); + try { + projectPopulator.populate(project); + } catch (InvocationTargetException ite) { + AdtPlugin.log(ite, null); + } } // Setup class path: mark folders as source folders @@ -768,7 +772,7 @@ public class NewProjectCreator { // Necessary for existing projects and good for new ones to. ProjectHelper.fixProject(project); - Boolean isLibraryProject = parameters.containsKey(PARAM_IS_LIBRARY); + Boolean isLibraryProject = (Boolean) parameters.get(PARAM_IS_LIBRARY); if (isLibraryProject != null && isLibraryProject.booleanValue() && Sdk.getCurrent() != null && project.isOpen()) { ProjectState state = Sdk.getProjectState(project); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/CreateFileChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/CreateFileChange.java new file mode 100644 index 0000000..3b41c36 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/CreateFileChange.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.templates; + +import com.android.annotations.NonNull; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtUtils; +import com.google.common.io.Closeables; +import com.google.common.io.Files; +import com.google.common.io.InputSupplier; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.resource.ResourceChange; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; + +/** Change which lazily copies a file */ +public class CreateFileChange extends ResourceChange { + private String mName; + private final IPath mPath; + private final File mSource; + + CreateFileChange(@NonNull String name, @NonNull IPath workspacePath, File source) { + mName = name; + mPath = workspacePath; + mSource = source; + } + + @Override + protected IResource getModifiedResource() { + return ResourcesPlugin.getWorkspace().getRoot().getFile(mPath); + } + + @Override + public String getName() { + return mName; + } + + @Override + public RefactoringStatus isValid(IProgressMonitor pm) + throws CoreException, OperationCanceledException { + RefactoringStatus result = new RefactoringStatus(); + IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(mPath); + URI location = file.getLocationURI(); + if (location == null) { + result.addFatalError("Unknown location " + file.getFullPath().toString()); + return result; + } + return result; + } + + @SuppressWarnings("resource") // Eclipse doesn't know about Guava's Closeables.closeQuietly + @Override + public Change perform(IProgressMonitor pm) throws CoreException { + InputSupplier supplier = Files.newInputStreamSupplier(mSource); + InputStream is = null; + try { + pm.beginTask("Creating file", 3); + IFile file = (IFile) getModifiedResource(); + + IContainer parent = file.getParent(); + if (parent != null && !parent.exists()) { + IFolder folder = ResourcesPlugin.getWorkspace().getRoot().getFolder( + parent.getFullPath()); + AdtUtils.ensureExists(folder); + } + + is = supplier.getInput(); + file.create(is, false, new SubProgressMonitor(pm, 1)); + pm.worked(1); + } catch (Exception ioe) { + AdtPlugin.log(ioe, null); + } finally { + Closeables.closeQuietly(is); + pm.done(); + } + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java index 52d7cec..92e9333 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java @@ -19,27 +19,17 @@ import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectW import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API_LEVEL; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_TARGET_API; -import static org.eclipse.core.resources.IResource.DEPTH_INFINITE; -import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.annotations.NonNull; import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.INewWizard; +import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ui.IWorkbench; -import java.io.File; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -49,15 +39,10 @@ import java.util.Set; * second page, but beyond that it runs the normal template wizard when it comes * time to create the template. */ -public class NewActivityWizard extends Wizard implements INewWizard { - private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ - - private IWorkbench mWorkbench; - private UpdateToolsPage mUpdatePage; +public class NewActivityWizard extends TemplateWizard { private NewTemplatePage mTemplatePage; private ActivityPage mActivityPage; private NewProjectWizardState mValues; - protected InstallDependencyPage mDependencyPage; private NewTemplateWizardState mActivityValues; /** Creates a new {@link NewActivityWizard} */ @@ -66,16 +51,9 @@ public class NewActivityWizard extends Wizard implements INewWizard { @Override public void init(IWorkbench workbench, IStructuredSelection selection) { - mWorkbench = workbench; + super.init(workbench, selection); setWindowTitle("New Activity"); - setHelpAvailable(false); - ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); - setDefaultPageImageDescriptor(desc); - - if (!UpdateToolsPage.isUpToDate()) { - mUpdatePage = new UpdateToolsPage(); - } mValues = new NewProjectWizardState(); mActivityPage = new ActivityPage(mValues); @@ -89,22 +67,11 @@ public class NewActivityWizard extends Wizard implements INewWizard { @Override public void addPages() { - if (mUpdatePage != null) { - addPage(mUpdatePage); - } - + super.addPages(); addPage(mActivityPage); } @Override - public IWizardPage getStartingPage() { - if (mUpdatePage != null && mUpdatePage.isPageComplete()) { - return mActivityPage; - } - return super.getStartingPage(); - } - - @Override public IWizardPage getNextPage(IWizardPage page) { if (page == mActivityPage) { if (mTemplatePage == null) { @@ -122,16 +89,16 @@ public class NewActivityWizard extends Wizard implements INewWizard { TemplateMetadata template = mActivityValues.getTemplateHandler().getTemplate(); if (template != null) { if (InstallDependencyPage.isInstalled(template.getDependencies())) { - return null; + return getPreviewPage(mActivityValues); } else { - if (mDependencyPage == null) { - mDependencyPage = new InstallDependencyPage(); - addPage(mDependencyPage); - } - mDependencyPage.setTemplate(template); - return mDependencyPage; + return getDependencyPage(template, true); } } + } else { + TemplateMetadata template = mActivityValues.getTemplateHandler().getTemplate(); + if (template != null && page == getDependencyPage(template, false)) { + return getPreviewPage(mActivityValues); + } } return super.getNextPage(page); @@ -149,40 +116,20 @@ public class NewActivityWizard extends Wizard implements INewWizard { } @Override - public boolean performFinish() { - try { - Shell shell = getShell(); - if (shell != null) { - shell.setVisible(false); - } - IProject project = mActivityValues.project; - File outputPath = AdtUtils.getAbsolutePath(project).toFile(); - assert mValues.createActivity; - NewTemplateWizardState activityValues = mValues.activityValues; - Map parameters = activityValues.parameters; - ManifestInfo manifest = ManifestInfo.get(project); - parameters.put(ATTR_PACKAGE_NAME, manifest.getPackage()); - parameters.put(ATTR_MIN_API, manifest.getMinSdkVersion()); - parameters.put(ATTR_MIN_API_LEVEL, manifest.getMinSdkName()); - parameters.put(ATTR_TARGET_API, manifest.getTargetSdkVersion()); - TemplateHandler activityTemplate = activityValues.getTemplateHandler(); - activityTemplate.setBackupMergedFiles(false); - activityTemplate.render(outputPath, parameters); - List filesToOpen = activityTemplate.getFilesToOpen(); - - try { - project.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } + @NonNull + protected IProject getProject() { + return mActivityValues.project; + } - // Open the primary file/files - NewTemplateWizard.openFiles(project, filesToOpen, mWorkbench); + @Override + @NonNull + protected List getFilesToOpen() { + TemplateHandler activityTemplate = mActivityValues.getTemplateHandler(); + return activityTemplate.getFilesToOpen(); + } - return true; - } catch (Exception ioe) { - AdtPlugin.log(ioe, null); - return false; - } + @Override + protected List computeChanges() { + return mActivityValues.computeChanges(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java index 312fbcf..563fcbf 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java @@ -17,13 +17,12 @@ package com.android.ide.eclipse.adt.internal.wizards.templates; import static org.eclipse.core.resources.IResource.DEPTH_INFINITE; +import com.android.annotations.NonNull; import com.android.assetstudiolib.GraphicGenerator; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.assetstudio.AssetType; import com.android.ide.eclipse.adt.internal.assetstudio.ConfigureAssetSetPage; import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator; import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator.ProjectPopulator; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; @@ -38,13 +37,11 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; -import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.swt.graphics.RGB; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.INewWizard; import org.eclipse.ui.IWorkbench; import java.awt.image.BufferedImage; @@ -53,6 +50,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -63,63 +61,41 @@ import javax.imageio.ImageIO; /** * Wizard for creating new projects */ -public class NewProjectWizard extends Wizard implements INewWizard { +public class NewProjectWizard extends TemplateWizard { private static final String ATTR_COPY_ICONS = "copyIcons"; //$NON-NLS-1$ static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$ static final String ATTR_MIN_API = "minApi"; //$NON-NLS-1$ static final String ATTR_MIN_API_LEVEL = "minApiLevel"; //$NON-NLS-1$ static final String ATTR_PACKAGE_NAME = "packageName"; //$NON-NLS-1$ static final String ATTR_APP_TITLE = "appTitle"; //$NON-NLS-1$ - private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ - private IWorkbench mWorkbench; - private UpdateToolsPage mUpdatePage; private NewProjectPage mMainPage; private ActivityPage mActivityPage; private NewTemplatePage mTemplatePage; - protected InstallDependencyPage mDependencyPage; private ConfigureAssetSetPage mIconPage; private NewProjectWizardState mValues; + /** The project being created */ + private IProject mProject; @Override public void init(IWorkbench workbench, IStructuredSelection selection) { - mWorkbench = workbench; + super.init(workbench, selection); setWindowTitle("New Android App"); - setHelpAvailable(false); - setImageDescriptor(); - - if (!UpdateToolsPage.isUpToDate()) { - mUpdatePage = new UpdateToolsPage(); - } mValues = new NewProjectWizardState(); mMainPage = new NewProjectPage(mValues); mActivityPage = new ActivityPage(mValues); } - /** - * Adds pages to this wizard. - */ @Override public void addPages() { - if (mUpdatePage != null) { - addPage(mUpdatePage); - } - + super.addPages(); addPage(mMainPage); addPage(mActivityPage); } @Override - public IWizardPage getStartingPage() { - if (mUpdatePage != null && mUpdatePage.isPageComplete()) { - return mMainPage; - } - return super.getStartingPage(); - } - - @Override public IWizardPage getNextPage(IWizardPage page) { if (page == mMainPage) { if (mValues.createIcon) { @@ -177,17 +153,12 @@ public class NewProjectWizard extends Wizard implements INewWizard { TemplateMetadata template = mValues.activityValues.getTemplateHandler().getTemplate(); if (template != null && !InstallDependencyPage.isInstalled(template.getDependencies())) { - if (mDependencyPage == null) { - mDependencyPage = new InstallDependencyPage(); - addPage(mDependencyPage); - } - mDependencyPage.setTemplate(template); - return mDependencyPage; + return getDependencyPage(template, true); } } if (page == mTemplatePage || !mValues.createActivity && page == mActivityPage - || page == mDependencyPage) { + || page == getDependencyPage(null, false)) { return null; } @@ -224,15 +195,41 @@ public class NewProjectWizard extends Wizard implements INewWizard { } @Override - public boolean performFinish() { + @NonNull + protected IProject getProject() { + return mProject; + } + + @Override + @NonNull + protected List getFilesToOpen() { + return mValues.template.getFilesToOpen(); + } + + @Override + protected List computeChanges() { + final TemplateHandler template = mValues.template; + // We'll be merging in an activity template, but don't create *~ backup files + // of the merged files (such as the manifest file) in that case. + // (NOTE: After the change from direct file manipulation to creating a list of Change + // objects, this no longer applies - but the code is kept around a little while longer + // in case we want to generate change objects that makes backups of merged files) + template.setBackupMergedFiles(false); + + // Generate basic output skeleton + Map paramMap = new HashMap(); + addProjectInfo(paramMap); + + return template.render(mProject, paramMap); + } + + @Override + protected boolean performFinish(final IProgressMonitor monitor) + throws InvocationTargetException { try { - Shell shell = getShell(); - if (shell != null) { - shell.setVisible(false); - } IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); String name = mValues.projectName; - final IProject newProject = root.getProject(name); + mProject = root.getProject(name); final TemplateHandler template = mValues.template; // We'll be merging in an activity template, but don't create *~ backup files @@ -241,13 +238,13 @@ public class NewProjectWizard extends Wizard implements INewWizard { ProjectPopulator projectPopulator = new ProjectPopulator() { @Override - public void populate(IProject project) { + public void populate(IProject project) throws InvocationTargetException { // Copy in the proguard file; templates don't provide this one. // add the default proguard config File libFolder = new File(AdtPlugin.getOsSdkToolsFolder(), SdkConstants.FD_LIB); try { - assert project == newProject; + assert project == mProject; NewProjectCreator.addLocalFile(project, new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE), // Write ProGuard config files with the extension .pro which @@ -258,42 +255,54 @@ public class NewProjectWizard extends Wizard implements INewWizard { AdtPlugin.log(e, null); } - // Generate basic output skeleton - Map paramMap = new HashMap(); - paramMap.put(ATTR_PACKAGE_NAME, mValues.packageName); - paramMap.put(ATTR_APP_TITLE, mValues.applicationName); - paramMap.put(ATTR_MIN_API, mValues.minSdk); - paramMap.put(ATTR_MIN_API_LEVEL, mValues.minSdkLevel); - paramMap.put(ATTR_TARGET_API, 15); - paramMap.put(ATTR_COPY_ICONS, !mValues.createIcon); + try { + mProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } - File outputPath = AdtUtils.getAbsolutePath(newProject).toFile(); - template.render(outputPath, paramMap); + // Render the project template + List changes = computeChanges(); + if (!changes.isEmpty()) { + monitor.beginTask("Creating project...", changes.size()); + try { + CompositeChange composite = new CompositeChange("", + changes.toArray(new Change[changes.size()])); + composite.perform(monitor); + } catch (CoreException e) { + AdtPlugin.log(e, null); + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } - if (mValues.createIcon) { - generateIcons(newProject); + if (mValues.createIcon) { // TODO: Set progress + generateIcons(mProject); } + // Render the embedded activity template template if (mValues.createActivity) { - generateActivity(template, paramMap, outputPath); + final TemplateHandler activityTemplate = + mValues.activityValues.getTemplateHandler(); + // We'll be merging in an activity template, but don't create + // *~ backup files of the merged files (such as the manifest file) + // in that case. + activityTemplate.setBackupMergedFiles(false); + generateActivity(template, project, monitor); } } }; - IProgressMonitor monitor = new NullProgressMonitor(); - NewProjectCreator.create(monitor, newProject, mValues.target, projectPopulator, + NewProjectCreator.create(monitor, mProject, mValues.target, projectPopulator, mValues.isLibrary); try { - newProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); + mProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); } catch (CoreException e) { AdtPlugin.log(e, null); } - // Open the primary file/files - final List filesToOpen = template.getFilesToOpen(); - NewTemplateWizard.openFiles(newProject, filesToOpen, mWorkbench); - return true; } catch (Exception ioe) { AdtPlugin.log(ioe, null); @@ -353,30 +362,41 @@ public class NewProjectWizard extends Wizard implements INewWizard { * activity needs but that we don't need to ask about when creating a new * project */ - private void generateActivity(final TemplateHandler template, - Map paramMap, File outputPath) { + private void generateActivity(TemplateHandler projectTemplate, IProject project, + IProgressMonitor monitor) throws InvocationTargetException { assert mValues.createActivity; NewTemplateWizardState activityValues = mValues.activityValues; Map parameters = activityValues.parameters; - parameters.put(ATTR_PACKAGE_NAME, paramMap.get(ATTR_PACKAGE_NAME)); - parameters.put(ATTR_APP_TITLE, paramMap.get(ATTR_APP_TITLE)); - parameters.put(ATTR_MIN_API, paramMap.get(ATTR_MIN_API)); - parameters.put(ATTR_MIN_API_LEVEL, paramMap.get(ATTR_MIN_API_LEVEL)); - parameters.put(ATTR_TARGET_API, paramMap.get(ATTR_TARGET_API)); + addProjectInfo(parameters); TemplateHandler activityTemplate = activityValues.getTemplateHandler(); activityTemplate.setBackupMergedFiles(false); - activityTemplate.render(outputPath, parameters); + List changes = activityTemplate.render(project, parameters); + if (!changes.isEmpty()) { + monitor.beginTask("Creating template...", changes.size()); + try { + CompositeChange composite = new CompositeChange("", + changes.toArray(new Change[changes.size()])); + composite.perform(monitor); + } catch (CoreException e) { + AdtPlugin.log(e, null); + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } + List filesToOpen = activityTemplate.getFilesToOpen(); - template.getFilesToOpen().addAll(filesToOpen); + projectTemplate.getFilesToOpen().addAll(filesToOpen); } - /** - * Returns an image descriptor for the wizard logo. - */ - private void setImageDescriptor() { - ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); - setDefaultPageImageDescriptor(desc); + private void addProjectInfo(Map parameters) { + parameters.put(ATTR_PACKAGE_NAME, mValues.packageName); + parameters.put(ATTR_APP_TITLE, mValues.applicationName); + parameters.put(ATTR_MIN_API, mValues.minSdk); + parameters.put(ATTR_MIN_API_LEVEL, mValues.minSdkLevel); + parameters.put(ATTR_TARGET_API, 15); + parameters.put(ATTR_COPY_ICONS, !mValues.createIcon); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java index ea9774e..6178c3e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java @@ -496,7 +496,7 @@ public class NewTemplatePage extends WizardPage // -- validate project if (mChooseProject && mValues.project == null) { - status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID, + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Please select an Android project."); } @@ -507,7 +507,7 @@ public class NewTemplatePage extends WizardPage String value = parameter.value == null ? "" : parameter.value.toString(); String error = validator.isValid(value); if (error != null) { - status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID, error); + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); if (decoration != null) { updateDecorator(decoration, status, parameter.help); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java index 666e2f6..43e437b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java @@ -19,47 +19,35 @@ import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectW import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API_LEVEL; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_TARGET_API; -import static org.eclipse.core.resources.IResource.DEPTH_INFINITE; import com.android.annotations.NonNull; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.ui.INewWizard; +import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PartInitException; import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard; import java.io.File; import java.util.List; -import java.util.Map; import java.util.Set; /** * Template wizard which creates parameterized templates */ -public class NewTemplateWizard extends Wizard implements INewWizard { +public class NewTemplateWizard extends TemplateWizard { /** Template name and location under $sdk/templates for the default activity */ static final String BLANK_ACTIVITY = "activities/BlankActivity"; //$NON-NLS-1$ /** Template name and location under $sdk/templates for the custom view template */ static final String CUSTOM_VIEW = "other/CustomView"; //$NON-NLS-1$ - private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ - protected IWorkbench mWorkbench; protected NewTemplatePage mMainPage; - protected UpdateToolsPage mUpdatePage; - protected InstallDependencyPage mDependencyPage; protected NewTemplateWizardState mValues; private final String mTemplateName; @@ -69,20 +57,15 @@ public class NewTemplateWizard extends Wizard implements INewWizard { @Override public void init(IWorkbench workbench, IStructuredSelection selection) { - mWorkbench = workbench; - - setHelpAvailable(false); - setImageDescriptor(); + super.init(workbench, selection); mValues = new NewTemplateWizardState(); File template = TemplateHandler.getTemplateLocation(mTemplateName); - mValues.setTemplateLocation(template); - hideBuiltinParameters(); - - if (!UpdateToolsPage.isUpToDate()) { - mUpdatePage = new UpdateToolsPage(); + if (template != null) { + mValues.setTemplateLocation(template); } + hideBuiltinParameters(); List projects = AdtUtils.getSelectedProjects(selection); if (projects.size() == 1) { @@ -107,80 +90,44 @@ public class NewTemplateWizard extends Wizard implements INewWizard { @Override public void addPages() { - if (mUpdatePage != null) { - addPage(mUpdatePage); - } - + super.addPages(); addPage(mMainPage); } @Override public IWizardPage getNextPage(IWizardPage page) { + TemplateMetadata template = mValues.getTemplateHandler().getTemplate(); if (page == mMainPage) { - TemplateMetadata template = mValues.getTemplateHandler().getTemplate(); if (template != null) { if (InstallDependencyPage.isInstalled(template.getDependencies())) { - return null; + return getPreviewPage(mValues); } else { - if (mDependencyPage == null) { - mDependencyPage = new InstallDependencyPage(); - addPage(mDependencyPage); - } - mDependencyPage.setTemplate(template); - return mDependencyPage; + return getDependencyPage(template, true); } } + } else if (page == getDependencyPage(template, false)) { + return getPreviewPage(mValues); } return super.getNextPage(page); } @Override - public IWizardPage getStartingPage() { - if (mUpdatePage != null && mUpdatePage.isPageComplete()) { - return mMainPage; - } - return super.getStartingPage(); + @NonNull + protected IProject getProject() { + return mValues.project; } @Override - public boolean performFinish() { - try { - Map parameters = mValues.parameters; - IProject project = mValues.project; - - ManifestInfo manifest = ManifestInfo.get(project); - parameters.put(ATTR_PACKAGE_NAME, manifest.getPackage()); - parameters.put(ATTR_MIN_API, manifest.getMinSdkVersion()); - parameters.put(ATTR_MIN_API_LEVEL, manifest.getMinSdkName()); - parameters.put(ATTR_TARGET_API, manifest.getTargetSdkVersion()); - - File outputPath = AdtUtils.getAbsolutePath(project).toFile(); - TemplateHandler handler = mValues.getTemplateHandler(); - handler.render(outputPath, parameters); - - try { - project.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - - List filesToOpen = handler.getFilesToOpen(); - NewTemplateWizard.openFiles(project, filesToOpen, mWorkbench); - - return true; - } catch (Exception ioe) { - AdtPlugin.log(ioe, null); - return false; - } + @NonNull + protected List getFilesToOpen() { + TemplateHandler activityTemplate = mValues.getTemplateHandler(); + return activityTemplate.getFilesToOpen(); } - /** - * Returns an image descriptor for the wizard logo. - */ - private void setImageDescriptor() { - ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); - setDefaultPageImageDescriptor(desc); + @Override + protected List computeChanges() { + return mValues.computeChanges(); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java index aaf1a6a..2ea101a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java @@ -16,15 +16,23 @@ package com.android.ide.eclipse.adt.internal.wizards.templates; +import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API; +import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API_LEVEL; +import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME; +import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_TARGET_API; import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard.BLANK_ACTIVITY; import com.android.annotations.NonNull; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import org.eclipse.core.resources.IProject; +import org.eclipse.ltk.core.refactoring.Change; import java.io.File; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -89,4 +97,20 @@ public class NewTemplateWizardState { File getTemplateLocation() { return mTemplateLocation; } + + /** Computes the changes this wizard will make */ + @NonNull + List computeChanges() { + if (project == null) { + return Collections.emptyList(); + } + + ManifestInfo manifest = ManifestInfo.get(project); + parameters.put(ATTR_PACKAGE_NAME, manifest.getPackage()); + parameters.put(ATTR_MIN_API, manifest.getMinSdkVersion()); + parameters.put(ATTR_MIN_API_LEVEL, manifest.getMinSdkName()); + parameters.put(ATTR_TARGET_API, manifest.getTargetSdkVersion()); + + return getTemplateHandler().render(project, parameters); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java index 16b38e3..c145f2c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java @@ -15,8 +15,14 @@ */ package com.android.ide.eclipse.adt.internal.wizards.templates; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_AIDL; import static com.android.ide.eclipse.adt.AdtConstants.DOT_FTL; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_RS; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_SVG; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_TXT; import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; import static com.android.ide.eclipse.adt.internal.wizards.templates.InstallDependencyPage.SUPPORT_LIBRARY_NAME; import static com.android.sdklib.SdkConstants.FD_EXTRAS; import static com.android.sdklib.SdkConstants.FD_NATIVE_LIBS; @@ -25,6 +31,7 @@ import static com.android.sdklib.SdkConstants.FD_TOOLS; import com.android.annotations.NonNull; import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtUtils; import com.android.ide.eclipse.adt.internal.actions.AddCompatibilityJarAction; @@ -47,11 +54,21 @@ import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import freemarker.template.TemplateException; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.NullChange; +import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.swt.SWT; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.w3c.dom.Document; @@ -159,6 +176,14 @@ class TemplateHandler { @NonNull private final File mRootPath; + /** The changes being processed by the template handler */ + private List mMergeChanges; + private List mTextChanges; + private List mOtherChanges; + + /** The project to write the template into */ + private IProject mProject; + /** The template loader which is responsible for finding (and sharing) template files */ private final MyTemplateLoader mLoader; @@ -200,10 +225,14 @@ class TemplateHandler { mBackupMergedFiles = backupMergedFiles; } - public void render(final File outputPath, Map args) { - if (!outputPath.exists()) { - outputPath.mkdirs(); - } + @NonNull + public List render(IProject project, Map args) { + mOpen.clear(); + + mProject = project; + mMergeChanges = new ArrayList(); + mTextChanges = new ArrayList(); + mOtherChanges = new ArrayList(); // Render the instruction list template. Map paramMap = createParameterMap(args); @@ -211,7 +240,15 @@ class TemplateHandler { freemarker.setObjectWrapper(new DefaultObjectWrapper()); freemarker.setTemplateLoader(mLoader); - processVariables(freemarker, TEMPLATE_XML, paramMap, outputPath); + processVariables(freemarker, TEMPLATE_XML, paramMap); + + // Add the changes in the order where merges are shown first, then text files, + // and finally other files (like jars and icons which don't have previews). + List changes = new ArrayList(); + changes.addAll(mMergeChanges); + changes.addAll(mTextChanges); + changes.addAll(mOtherChanges); + return changes; } Map createParameterMap(Map args) { @@ -389,7 +426,7 @@ class TemplateHandler { /** Read the given FreeMarker file and process the variable definitions */ private void processVariables(final Configuration freemarker, - String file, final Map paramMap, final File outputPath) { + String file, final Map paramMap) { try { String xml; if (file.endsWith(DOT_XML)) { @@ -430,12 +467,12 @@ class TemplateHandler { // Handle evaluation of variables String path = attributes.getValue(ATTR_FILE); if (path != null) { - processVariables(freemarker, path, paramMap, outputPath); + processVariables(freemarker, path, paramMap); } // else: root element } else if (TAG_EXECUTE.equals(name)) { String path = attributes.getValue(ATTR_FILE); if (path != null) { - execute(freemarker, path, paramMap, outputPath); + execute(freemarker, path, paramMap); } } else if (TAG_DEPENDENCY.equals(name)) { String dependencyName = attributes.getValue(ATTR_NAME); @@ -444,8 +481,7 @@ class TemplateHandler { // by the wizard File path = AddCompatibilityJarAction.getCompatJarFile(); if (path != null) { - File to = new File(outputPath, FD_NATIVE_LIBS - + File.separator + path.getName()); + IPath to = getTargetPath(FD_NATIVE_LIBS +'/' + path.getName()); try { copy(path, to); } catch (IOException ioe) { @@ -466,7 +502,7 @@ class TemplateHandler { } private boolean canOverwrite(File file) { - if (file.exists() && !file.isDirectory()) { + if (file.exists()) { // Warn that the file already exists and ask the user what to do if (!mYesToAll) { MessageDialog dialog = new MessageDialog(null, "File Already Exists", null, @@ -511,8 +547,7 @@ class TemplateHandler { private void execute( final Configuration freemarker, String file, - final Map paramMap, - final File outputPath) { + final Map paramMap) { try { mLoader.setTemplateFile(new File(mRootPath, file)); Template freemarkerTemplate = freemarker.getTemplate(file); @@ -545,11 +580,11 @@ class TemplateHandler { toPath = attributes.getValue(ATTR_FROM); toPath = AdtUtils.stripSuffix(toPath, DOT_FTL); } - File to = new File(outputPath, toPath); + IPath to = getTargetPath(toPath); if (instantiate) { instantiate(freemarker, paramMap, fromPath, to); } else { - copyBundledResource(fromPath, to); + copyTemplateResource(fromPath, to); } } else if (TAG_MERGE.equals(name)) { String fromPath = attributes.getValue(ATTR_FROM); @@ -559,7 +594,7 @@ class TemplateHandler { toPath = AdtUtils.stripSuffix(toPath, DOT_FTL); } // Resources in template.xml are located within root/ - File to = new File(outputPath, toPath); + IPath to = getTargetPath(toPath); merge(freemarker, paramMap, fromPath, to); } else if (name.equals(TAG_OPEN)) { // The relative path here is within the output directory: @@ -591,23 +626,44 @@ class TemplateHandler { return new File(mRootPath, DATA_ROOT + File.separator + fromPath); } + @NonNull + private IPath getTargetPath(@NonNull String relative) { + if (relative.indexOf('\\') != -1) { + relative = relative.replace('\\', '/'); + } + return new Path(relative); + } + + @NonNull + private IFile getTargetFile(@NonNull IPath path) { + return mProject.getFile(path); + } + private void merge( @NonNull final Configuration freemarker, @NonNull final Map paramMap, @NonNull String relativeFrom, - @NonNull File to) throws IOException, TemplateException { - if (!to.exists()) { + @NonNull IPath toPath) throws IOException, TemplateException { + + String currentXml = null; + + IFile to = getTargetFile(toPath); + if (to.exists()) { + currentXml = AdtPlugin.readFile(to); + } + + if (currentXml == null) { // The target file doesn't exist: don't merge, just copy boolean instantiate = relativeFrom.endsWith(DOT_FTL); if (instantiate) { - instantiate(freemarker, paramMap, relativeFrom, to); + instantiate(freemarker, paramMap, relativeFrom, toPath); } else { - copyBundledResource(relativeFrom, to); + copyTemplateResource(relativeFrom, toPath); } return; } - if (!to.getPath().endsWith(DOT_XML)) { + if (!to.getFileExtension().equals(EXT_XML)) { throw new RuntimeException("Only XML files can be merged at this point: " + to); } @@ -628,21 +684,21 @@ class TemplateHandler { } } - String currentXml = Files.toString(to, Charsets.UTF_8); Document currentManifest = DomUtilities.parseStructuredDocument(currentXml); Document fragment = DomUtilities.parseStructuredDocument(xml); XmlFormatStyle formatStyle = XmlFormatStyle.MANIFEST; boolean modified; boolean ok; - if (to.getName().equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) { + String fileName = to.getName(); + if (fileName.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) { modified = ok = mergeManifest(currentManifest, fragment); } else { // Merge plain XML files - ResourceFolderType folderType = - ResourceFolderType.getFolderType(to.getParentFile().getName()); + String parentFolderName = to.getParent().getName(); + ResourceFolderType folderType = ResourceFolderType.getFolderType(parentFolderName); if (folderType != null) { - formatStyle = XmlFormatStyle.getForFolderType(folderType); + formatStyle = XmlFormatStyle.getForFile(toPath); } else { formatStyle = XmlFormatStyle.FILE; } @@ -652,58 +708,34 @@ class TemplateHandler { } // Finally write out the merged file (formatting etc) + String contents = null; if (ok) { if (modified) { XmlPrettyPrinter printer = new XmlPrettyPrinter( XmlFormatPreferences.create(), formatStyle, null); StringBuilder sb = new StringBuilder(2 ); printer.prettyPrint(-1, currentManifest, null, null, sb, false /*openTagOnly*/); - String contents = sb.toString(); - writeString(to, contents, false); + contents = sb.toString(); } } else { // Just insert into file along with comment, using the "standard" conflict // syntax that many tools and editors recognize. String sep = AdtUtils.getLineSeparator(); - String contents = + contents = "<<<<<<< Original" + sep + currentXml + sep + "=======" + sep + xml + ">>>>>>> Added" + sep; - writeString(to, contents, false); } - } - /** - * Writes the given contents into the given file (unless that file already - * contains the given contents), and if the file exists ask user whether - * the file should be overwritten (unless the user has already answered "Yes to All" - * or "Cancel" (no to all). - */ - private void writeString(File destination, String contents, boolean confirmOverwrite) - throws IOException { - // First make sure that the files aren't identical, in which case we can do - // nothing (and not involve user) - if (!(destination.exists() - && isIdentical(contents.getBytes(Charsets.UTF_8), destination))) { - // And if the file does exist (and is now known to be different), - // ask user whether it should be replaced (canOverwrite will also - // return true if the file doesn't exist) - if (confirmOverwrite) { - if (!canOverwrite(destination)) { - return; - } - } else { - if (destination.exists()) { - if (mBackupMergedFiles) { - makeBackup(destination); - } else { - destination.delete(); - } - } - } - Files.write(contents, destination, Charsets.UTF_8); + if (contents != null) { + TextFileChange change = new TextFileChange("Merge " + fileName, to); + MultiTextEdit rootEdit = new MultiTextEdit(); + rootEdit.addChild(new ReplaceEdit(0, currentXml.length(), contents)); + change.setEdit(rootEdit); + change.setTextType(AdtConstants.EXT_XML); + mMergeChanges.add(change); } } @@ -830,18 +862,13 @@ class TemplateHandler { @NonNull final Configuration freemarker, @NonNull final Map paramMap, @NonNull String relativeFrom, - @NonNull File to) throws IOException, TemplateException { - File parentFile = to.getParentFile(); - if (!parentFile.exists()) { - parentFile.mkdirs(); - } - + @NonNull IPath to) throws IOException, TemplateException { // For now, treat extension-less files as directories... this isn't quite right // so I should refine this! Maybe with a unique attribute in the template file? boolean isDirectory = relativeFrom.indexOf('.') == -1; if (isDirectory) { // It's a directory - copyBundledResource(relativeFrom, to); + copyTemplateResource(relativeFrom, to); } else { File from = getFullPath(relativeFrom); mLoader.setTemplateFile(from); @@ -852,13 +879,32 @@ class TemplateHandler { String contents = out.toString(); if (relativeFrom.endsWith(DOT_XML)) { - XmlFormatStyle formatStyle = XmlFormatStyle.getForFile(new Path(to.getPath())); + XmlFormatStyle formatStyle = XmlFormatStyle.getForFile(to); XmlFormatPreferences prefs = XmlFormatPreferences.create(); contents = XmlPrettyPrinter.prettyPrint(contents, prefs, formatStyle, null); } - writeString(to, contents, true); + IFile targetFile = getTargetFile(to); + TextFileChange change = createTextChange(targetFile); + MultiTextEdit rootEdit = new MultiTextEdit(); + rootEdit.addChild(new InsertEdit(0, contents)); + change.setEdit(rootEdit); + mTextChanges.add(change); + } + } + + private static TextFileChange createTextChange(IFile targetFile) { + String fileName = targetFile.getName(); + String message; + if (targetFile.exists()) { + message = String.format("Replace %1$s", fileName); + } else { + message = String.format("Create %1$s", fileName); } + + TextFileChange change = new TextFileChange(message, targetFile); + change.setTextType(fileName.substring(fileName.lastIndexOf('.') + 1)); + return change; } /** @@ -871,19 +917,18 @@ class TemplateHandler { return mOpen; } - /** Copy a bundled resource (part of the plugin .jar file) into the given file system path */ - private final void copyBundledResource( + /** Copy a template resource */ + private final void copyTemplateResource( @NonNull String relativeFrom, - @NonNull File output) throws IOException { + @NonNull IPath output) throws IOException { File from = getFullPath(relativeFrom); copy(from, output); } /** Returns true if the given file contains the given bytes */ - private static boolean isIdentical(@Nullable byte[] data, @NonNull File dest) - throws IOException { - assert dest.isFile(); - byte[] existing = Files.toByteArray(dest); + private static boolean isIdentical(@Nullable byte[] data, @NonNull IFile dest) { + assert dest.exists(); + byte[] existing = AdtUtils.readData(dest); return Arrays.equals(existing, data); } @@ -892,30 +937,55 @@ class TemplateHandler { * source is allowed to be a directory, in which case the whole directory is * copied recursively) */ - private void copy(File src, File dest) throws IOException { - if (src.isDirectory()){ - if (!dest.exists() && !dest.mkdirs()) { - throw new IOException("Could not create directory " + dest); - } + private void copy(File src, IPath path) throws IOException { + if (src.isDirectory()) { File[] children = src.listFiles(); if (children != null) { for (File child : children) { - copy(child, new File(dest, child.getName())); + copy(child, path.append(child.getName())); } } } else { - if (dest.exists() && isIdentical(Files.toByteArray(src), dest)) { + IResource dest = mProject.getFile(path); + if (dest.exists() && !(dest instanceof IFile)) {// Don't attempt to overwrite a folder + assert false : dest.getClass().getName(); return; } - if (!canOverwrite(dest)) { - return; + IFile file = (IFile) dest; + String targetName = path.lastSegment(); + if (dest instanceof IFile) { + if (dest.exists() && isIdentical(Files.toByteArray(src), file)) { + String label = String.format( + "Not overwriting %1$s because the files are identical", targetName); + NullChange change = new NullChange(label); + change.setEnabled(false); + mOtherChanges.add(change); + return; + } } - File parent = dest.getParentFile(); - if (parent != null && !parent.exists()) { - parent.mkdirs(); + if (targetName.endsWith(DOT_XML) + || targetName.endsWith(DOT_JAVA) + || targetName.endsWith(DOT_TXT) + || targetName.endsWith(DOT_RS) + || targetName.endsWith(DOT_AIDL) + || targetName.endsWith(DOT_SVG)) { + + String newFile = Files.toString(src, Charsets.UTF_8); + if (targetName.endsWith(DOT_XML)) { + newFile = XmlPrettyPrinter.prettyPrint(newFile, + XmlFormatPreferences.create(), XmlFormatStyle.getForFile(path), + null /*lineSeparator*/); + } + + TextFileChange addFile = createTextChange(file); + addFile.setEdit(new InsertEdit(0, newFile)); + mTextChanges.add(addFile); + } else { + // Write binary file: Need custom change for that + IPath workspacePath = mProject.getFullPath().append(path); + mOtherChanges.add(new CreateFileChange(targetName, workspacePath, src)); } - Files.copy(src, dest); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplatePreviewPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplatePreviewPage.java new file mode 100644 index 0000000..c3d28fc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplatePreviewPage.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.wizards.templates; + +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.internal.ui.refactoring.PreviewWizardPage; + +import java.util.List; + +@SuppressWarnings("restriction") // Refactoring UI +class TemplatePreviewPage extends PreviewWizardPage { + private final NewTemplateWizardState mValues; + + TemplatePreviewPage(NewTemplateWizardState values) { + super(true); + mValues = values; + setTitle("Preview"); + setDescription("Optionally review pending changes"); + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + List changes = mValues.computeChanges(); + CompositeChange root = new CompositeChange("Create template", + changes.toArray(new Change[changes.size()])); + setChange(root); + } + + super.setVisible(visible); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateWizard.java new file mode 100644 index 0000000..a062665 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateWizard.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.wizards.templates; + +import static org.eclipse.core.resources.IResource.DEPTH_INFINITE; + +import com.android.annotations.NonNull; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +abstract class TemplateWizard extends Wizard implements INewWizard { + private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ + protected IWorkbench mWorkbench; + private UpdateToolsPage mUpdatePage; + private InstallDependencyPage mDependencyPage; + private TemplatePreviewPage mPreviewPage; + + protected TemplateWizard() { + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + mWorkbench = workbench; + + setHelpAvailable(false); + ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + + if (!UpdateToolsPage.isUpToDate()) { + mUpdatePage = new UpdateToolsPage(); + } + + setNeedsProgressMonitor(true); + } + + @Override + public void addPages() { + super.addPages(); + if (mUpdatePage != null) { + addPage(mUpdatePage); + } + } + + @Override + public IWizardPage getStartingPage() { + if (mUpdatePage != null && mUpdatePage.isPageComplete()) { + return getNextPage(mUpdatePage); + } + + return super.getStartingPage(); + } + + protected WizardPage getPreviewPage(NewTemplateWizardState values) { + if (mPreviewPage == null) { + mPreviewPage = new TemplatePreviewPage(values); + addPage(mPreviewPage); + } + + return mPreviewPage; + } + + protected WizardPage getDependencyPage(TemplateMetadata template, boolean create) { + if (!create) { + return mDependencyPage; + } + + if (mDependencyPage == null) { + mDependencyPage = new InstallDependencyPage(); + addPage(mDependencyPage); + } + mDependencyPage.setTemplate(template); + return mDependencyPage; + } + + /** + * Returns the project where the template is being inserted + * + * @return the project to insert the template into + */ + @NonNull + protected abstract IProject getProject(); + + /** + * Returns the list of files to open, which might be empty. This method will + * only be called after {@link #computeChanges()} has been called. + * + * @return a list of files to open + */ + @NonNull + protected abstract List getFilesToOpen(); + + /** + * Computes the changes to the {@link #getProject()} this template should + * perform + * + * @return the changes to perform + */ + protected abstract List computeChanges(); + + protected boolean performFinish(IProgressMonitor monitor) throws InvocationTargetException { + List changes = computeChanges(); + if (!changes.isEmpty()) { + monitor.beginTask("Creating template...", changes.size()); + try { + CompositeChange composite = new CompositeChange("", + changes.toArray(new Change[changes.size()])); + composite.perform(monitor); + } catch (CoreException e) { + AdtPlugin.log(e, null); + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } + + // TBD: Is this necessary now that we're using IFile objects? + try { + getProject().refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return true; + } + + @Override + public boolean performFinish() { + final AtomicBoolean success = new AtomicBoolean(); + try { + getContainer().run(true, false, new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, + InterruptedException { + boolean ok = performFinish(monitor); + success.set(ok); + } + }); + } catch (InvocationTargetException e) { + AdtPlugin.log(e, null); + return false; + } catch (InterruptedException e) { + AdtPlugin.log(e, null); + return false; + } + + if (success.get()) { + // Open the primary file/files + NewTemplateWizard.openFiles(getProject(), getFilesToOpen(), mWorkbench); + + return true; + } else { + return false; + } + } +} -- cgit v1.1