diff options
author | Tor Norbye <tnorbye@google.com> | 2012-06-18 10:31:49 -0700 |
---|---|---|
committer | android code review <noreply-gerritcodereview@google.com> | 2012-06-18 10:31:49 -0700 |
commit | d6215b7fcc4e732e7cfd297c8260a1bead9a6bc7 (patch) | |
tree | cb501d970ace59729ac0b96d21e2c670fcb1b71f | |
parent | a3c24ed7a9205f9bbe28128247469009a62da161 (diff) | |
parent | 802de810020fba3f86282cd1d66597a2a41698e3 (diff) | |
download | sdk-d6215b7fcc4e732e7cfd297c8260a1bead9a6bc7.zip sdk-d6215b7fcc4e732e7cfd297c8260a1bead9a6bc7.tar.gz sdk-d6215b7fcc4e732e7cfd297c8260a1bead9a6bc7.tar.bz2 |
Merge "Preview support in the templates"
16 files changed, 756 insertions, 349 deletions
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<FileInputStream> 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<String, Object> 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<String> 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<String> getFilesToOpen() { + TemplateHandler activityTemplate = mActivityValues.getTemplateHandler(); + return activityTemplate.getFilesToOpen(); + } - return true; - } catch (Exception ioe) { - AdtPlugin.log(ioe, null); - return false; - } + @Override + protected List<Change> 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<String> getFilesToOpen() { + return mValues.template.getFilesToOpen(); + } + + @Override + protected List<Change> 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<String, Object> paramMap = new HashMap<String, Object>(); + 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<String, Object> paramMap = new HashMap<String, Object>(); - 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<Change> 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<String> 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<String, Object> paramMap, File outputPath) { + private void generateActivity(TemplateHandler projectTemplate, IProject project, + IProgressMonitor monitor) throws InvocationTargetException { assert mValues.createActivity; NewTemplateWizardState activityValues = mValues.activityValues; Map<String, Object> 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<Change> 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<String> 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<String, Object> 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<IProject> 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<String, Object> 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<String> filesToOpen = handler.getFilesToOpen(); - NewTemplateWizard.openFiles(project, filesToOpen, mWorkbench); - - return true; - } catch (Exception ioe) { - AdtPlugin.log(ioe, null); - return false; - } + @NonNull + protected List<String> 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<Change> 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<Change> 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<Change> mMergeChanges; + private List<Change> mTextChanges; + private List<Change> 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<String, Object> args) { - if (!outputPath.exists()) { - outputPath.mkdirs(); - } + @NonNull + public List<Change> render(IProject project, Map<String, Object> args) { + mOpen.clear(); + + mProject = project; + mMergeChanges = new ArrayList<Change>(); + mTextChanges = new ArrayList<Change>(); + mOtherChanges = new ArrayList<Change>(); // Render the instruction list template. Map<String, Object> 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<Change> changes = new ArrayList<Change>(); + changes.addAll(mMergeChanges); + changes.addAll(mTextChanges); + changes.addAll(mOtherChanges); + return changes; } Map<String, Object> createParameterMap(Map<String, Object> 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<String, Object> paramMap, final File outputPath) { + String file, final Map<String, Object> 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: <globals> 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<String, Object> paramMap, - final File outputPath) { + final Map<String, Object> 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<String, Object> 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<String, Object> 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<Change> 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 <b>after</b> {@link #computeChanges()} has been called. + * + * @return a list of files to open + */ + @NonNull + protected abstract List<String> getFilesToOpen(); + + /** + * Computes the changes to the {@link #getProject()} this template should + * perform + * + * @return the changes to perform + */ + protected abstract List<Change> computeChanges(); + + protected boolean performFinish(IProgressMonitor monitor) throws InvocationTargetException { + List<Change> 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; + } + } +} |