diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java | 1239 |
1 files changed, 0 insertions, 1239 deletions
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 deleted file mode 100644 index 8e11841b4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java +++ /dev/null @@ -1,1239 +0,0 @@ -/* - * 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 com.android.SdkConstants.ATTR_PACKAGE; -import static com.android.SdkConstants.DOT_AIDL; -import static com.android.SdkConstants.DOT_FTL; -import static com.android.SdkConstants.DOT_JAVA; -import static com.android.SdkConstants.DOT_RS; -import static com.android.SdkConstants.DOT_SVG; -import static com.android.SdkConstants.DOT_TXT; -import static com.android.SdkConstants.DOT_XML; -import static com.android.SdkConstants.EXT_XML; -import static com.android.SdkConstants.FD_NATIVE_LIBS; -import static com.android.SdkConstants.XMLNS_PREFIX; -import static com.android.ide.eclipse.adt.internal.wizards.templates.InstallDependencyPage.SUPPORT_LIBRARY_NAME; -import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateManager.getTemplateRootFolder; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.annotations.VisibleForTesting; -import com.android.ide.common.xml.XmlFormatStyle; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AdtUtils; -import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction; -import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences; -import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter; -import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; -import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback; -import com.android.manifmerger.ManifestMerger; -import com.android.manifmerger.MergerLog; -import com.android.resources.ResourceFolderType; -import com.android.utils.SdkUtils; -import com.google.common.base.Charsets; -import com.google.common.collect.Lists; -import com.google.common.io.Files; - -import freemarker.cache.TemplateLoader; -import freemarker.template.Configuration; -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.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Path; -import org.eclipse.core.runtime.Status; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.ToolFactory; -import org.eclipse.jdt.core.formatter.CodeFormatter; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -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.eclipse.text.edits.TextEdit; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; -import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Handler which manages instantiating FreeMarker templates, copying resources - * and merging into existing files - */ -class TemplateHandler { - /** Highest supported format; templates with a higher number will be skipped - * <p> - * <ul> - * <li> 1: Initial format, supported by ADT 20 and up. - * <li> 2: ADT 21 and up. Boolean variables that have a default value and are not - * edited by the user would end up as strings in ADT 20; now they are always - * proper Booleans. Templates which rely on this should specify format >= 2. - * <li> 3: The wizard infrastructure passes the {@code isNewProject} boolean variable - * to indicate whether a wizard is created as part of a new blank project - * <li> 4: The templates now specify dependencies in the recipe file. - * </ul> - */ - static final int CURRENT_FORMAT = 4; - - /** - * Special marker indicating that this path refers to the special shared - * resource directory rather than being somewhere inside the root/ directory - * where all template specific resources are found - */ - private static final String VALUE_TEMPLATE_DIR = "$TEMPLATEDIR"; //$NON-NLS-1$ - - /** - * Directory within the template which contains the resources referenced - * from the template.xml file - */ - private static final String DATA_ROOT = "root"; //$NON-NLS-1$ - - /** - * Shared resource directory containing common resources shared among - * multiple templates - */ - private static final String RESOURCE_ROOT = "resources"; //$NON-NLS-1$ - - /** Reserved filename which describes each template */ - static final String TEMPLATE_XML = "template.xml"; //$NON-NLS-1$ - - // Various tags and attributes used in the template metadata files - template.xml, - // globals.xml.ftl, recipe.xml.ftl, etc. - - static final String TAG_MERGE = "merge"; //$NON-NLS-1$ - static final String TAG_EXECUTE = "execute"; //$NON-NLS-1$ - static final String TAG_GLOBALS = "globals"; //$NON-NLS-1$ - static final String TAG_GLOBAL = "global"; //$NON-NLS-1$ - static final String TAG_PARAMETER = "parameter"; //$NON-NLS-1$ - static final String TAG_COPY = "copy"; //$NON-NLS-1$ - static final String TAG_INSTANTIATE = "instantiate"; //$NON-NLS-1$ - static final String TAG_OPEN = "open"; //$NON-NLS-1$ - static final String TAG_THUMB = "thumb"; //$NON-NLS-1$ - static final String TAG_THUMBS = "thumbs"; //$NON-NLS-1$ - static final String TAG_DEPENDENCY = "dependency"; //$NON-NLS-1$ - static final String TAG_ICONS = "icons"; //$NON-NLS-1$ - static final String TAG_FORMFACTOR = "formfactor"; //$NON-NLS-1$ - static final String TAG_CATEGORY = "category"; //$NON-NLS-1$ - static final String ATTR_FORMAT = "format"; //$NON-NLS-1$ - static final String ATTR_REVISION = "revision"; //$NON-NLS-1$ - static final String ATTR_VALUE = "value"; //$NON-NLS-1$ - static final String ATTR_DEFAULT = "default"; //$NON-NLS-1$ - static final String ATTR_SUGGEST = "suggest"; //$NON-NLS-1$ - static final String ATTR_ID = "id"; //$NON-NLS-1$ - static final String ATTR_NAME = "name"; //$NON-NLS-1$ - static final String ATTR_DESCRIPTION = "description";//$NON-NLS-1$ - static final String ATTR_TYPE = "type"; //$NON-NLS-1$ - static final String ATTR_HELP = "help"; //$NON-NLS-1$ - static final String ATTR_FILE = "file"; //$NON-NLS-1$ - static final String ATTR_TO = "to"; //$NON-NLS-1$ - static final String ATTR_FROM = "from"; //$NON-NLS-1$ - static final String ATTR_CONSTRAINTS = "constraints";//$NON-NLS-1$ - static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$ - static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$ - static final String ATTR_SHAPE = "shape"; //$NON-NLS-1$ - static final String ATTR_TRIM = "trim"; //$NON-NLS-1$ - static final String ATTR_PADDING = "padding"; //$NON-NLS-1$ - static final String ATTR_SOURCE_TYPE = "source"; //$NON-NLS-1$ - static final String ATTR_CLIPART_NAME = "clipartName";//$NON-NLS-1$ - static final String ATTR_TEXT = "text"; //$NON-NLS-1$ - static final String ATTR_SRC_DIR = "srcDir"; //$NON-NLS-1$ - static final String ATTR_SRC_OUT = "srcOut"; //$NON-NLS-1$ - static final String ATTR_RES_DIR = "resDir"; //$NON-NLS-1$ - static final String ATTR_RES_OUT = "resOut"; //$NON-NLS-1$ - static final String ATTR_MANIFEST_DIR = "manifestDir";//$NON-NLS-1$ - static final String ATTR_MANIFEST_OUT = "manifestOut";//$NON-NLS-1$ - static final String ATTR_PROJECT_DIR = "projectDir"; //$NON-NLS-1$ - static final String ATTR_PROJECT_OUT = "projectOut"; //$NON-NLS-1$ - static final String ATTR_MAVEN_URL = "mavenUrl"; //$NON-NLS-1$ - static final String ATTR_DEBUG_KEYSTORE_SHA1 = - "debugKeystoreSha1"; //$NON-NLS-1$ - - static final String CATEGORY_ACTIVITIES = "activities";//$NON-NLS-1$ - static final String CATEGORY_PROJECTS = "projects"; //$NON-NLS-1$ - static final String CATEGORY_OTHER = "other"; //$NON-NLS-1$ - - static final String MAVEN_SUPPORT_V4 = "support-v4"; //$NON-NLS-1$ - static final String MAVEN_SUPPORT_V13 = "support-v13"; //$NON-NLS-1$ - static final String MAVEN_APPCOMPAT = "appcompat-v7"; //$NON-NLS-1$ - - /** Default padding to apply in wizards around the thumbnail preview images */ - static final int PREVIEW_PADDING = 10; - - /** Default width to scale thumbnail preview images in wizards to */ - static final int PREVIEW_WIDTH = 200; - - /** - * List of files to open after the wizard has been created (these are - * identified by {@link #TAG_OPEN} elements in the recipe file - */ - private final List<String> mOpen = Lists.newArrayList(); - - /** - * List of actions to perform after the wizard has finished. - */ - protected List<Runnable> mFinalizingActions = Lists.newArrayList(); - - /** Path to the directory containing the templates */ - @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; - - /** Agree to all file-overwrites from now on? */ - private boolean mYesToAll = false; - - /** Is writing the template cancelled? */ - private boolean mNoToAll = false; - - /** - * Should files that we merge contents into be backed up? If yes, will - * create emacs-style tilde-file backups (filename.xml~) - */ - private boolean mBackupMergedFiles = true; - - /** - * Template metadata - */ - private TemplateMetadata mTemplate; - - private final TemplateManager mManager; - - /** Creates a new {@link TemplateHandler} for the given root path */ - static TemplateHandler createFromPath(File rootPath) { - return new TemplateHandler(rootPath, new TemplateManager()); - } - - /** Creates a new {@link TemplateHandler} for the template name, which should - * be relative to the templates directory */ - static TemplateHandler createFromName(String category, String name) { - TemplateManager manager = new TemplateManager(); - - // Use the TemplateManager iteration which should merge contents between the - // extras/templates/ and tools/templates folders and pick the most recent version - List<File> templates = manager.getTemplates(category); - for (File file : templates) { - if (file.getName().equals(name) && category.equals(file.getParentFile().getName())) { - return new TemplateHandler(file, manager); - } - } - - return new TemplateHandler(new File(getTemplateRootFolder(), - category + File.separator + name), manager); - } - - private TemplateHandler(File rootPath, TemplateManager manager) { - mRootPath = rootPath; - mManager = manager; - mLoader = new MyTemplateLoader(); - mLoader.setPrefix(mRootPath.getPath()); - } - - public TemplateManager getManager() { - return mManager; - } - - public void setBackupMergedFiles(boolean backupMergedFiles) { - mBackupMergedFiles = backupMergedFiles; - } - - @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); - Configuration freemarker = new Configuration(); - freemarker.setObjectWrapper(new DefaultObjectWrapper()); - freemarker.setTemplateLoader(mLoader); - - 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) { - final Map<String, Object> paramMap = createBuiltinMap(); - - // Wizard parameters supplied by user, specific to this template - paramMap.putAll(args); - - return paramMap; - } - - /** Data model for the templates */ - static Map<String, Object> createBuiltinMap() { - // Create the data model. - final Map<String, Object> paramMap = new HashMap<String, Object>(); - - // Builtin conversion methods - paramMap.put("slashedPackageName", new FmSlashedPackageNameMethod()); //$NON-NLS-1$ - paramMap.put("camelCaseToUnderscore", new FmCamelCaseToUnderscoreMethod()); //$NON-NLS-1$ - paramMap.put("underscoreToCamelCase", new FmUnderscoreToCamelCaseMethod()); //$NON-NLS-1$ - paramMap.put("activityToLayout", new FmActivityToLayoutMethod()); //$NON-NLS-1$ - paramMap.put("layoutToActivity", new FmLayoutToActivityMethod()); //$NON-NLS-1$ - paramMap.put("classToResource", new FmClassNameToResourceMethod()); //$NON-NLS-1$ - paramMap.put("escapeXmlAttribute", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ - paramMap.put("escapeXmlText", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ - paramMap.put("escapeXmlString", new FmEscapeXmlStringMethod()); //$NON-NLS-1$ - paramMap.put("extractLetters", new FmExtractLettersMethod()); //$NON-NLS-1$ - - // This should be handled better: perhaps declared "required packages" as part of the - // inputs? (It would be better if we could conditionally disable template based - // on availability) - Map<String, String> builtin = new HashMap<String, String>(); - builtin.put("templatesRes", VALUE_TEMPLATE_DIR); //$NON-NLS-1$ - paramMap.put("android", builtin); //$NON-NLS-1$ - - return paramMap; - } - - static void addDirectoryParameters(Map<String, Object> parameters, IProject project) { - IPath srcDir = project.getFile(SdkConstants.SRC_FOLDER).getProjectRelativePath(); - parameters.put(ATTR_SRC_DIR, srcDir.toString()); - - IPath resDir = project.getFile(SdkConstants.RES_FOLDER).getProjectRelativePath(); - parameters.put(ATTR_RES_DIR, resDir.toString()); - - IPath manifestDir = project.getProjectRelativePath(); - parameters.put(ATTR_MANIFEST_DIR, manifestDir.toString()); - parameters.put(ATTR_MANIFEST_OUT, manifestDir.toString()); - - parameters.put(ATTR_PROJECT_DIR, manifestDir.toString()); - parameters.put(ATTR_PROJECT_OUT, manifestDir.toString()); - - parameters.put(ATTR_DEBUG_KEYSTORE_SHA1, ""); - } - - @Nullable - public TemplateMetadata getTemplate() { - if (mTemplate == null) { - mTemplate = mManager.getTemplate(mRootPath); - } - - return mTemplate; - } - - @NonNull - public String getResourcePath(String templateName) { - return new File(mRootPath.getPath(), templateName).getPath(); - } - - /** - * Load a text resource for the given relative path within the template - * - * @param relativePath relative path within the template - * @return the string contents of the template text file - */ - @Nullable - public String readTemplateTextResource(@NonNull String relativePath) { - try { - return Files.toString(new File(mRootPath, - relativePath.replace('/', File.separatorChar)), Charsets.UTF_8); - } catch (IOException e) { - AdtPlugin.log(e, null); - return null; - } - } - - @Nullable - public String readTemplateTextResource(@NonNull File file) { - assert file.isAbsolute(); - try { - return Files.toString(file, Charsets.UTF_8); - } catch (IOException e) { - AdtPlugin.log(e, null); - return null; - } - } - - /** - * Reads the contents of a resource - * - * @param relativePath the path relative to the template directory - * @return the binary data read from the file - */ - @Nullable - public byte[] readTemplateResource(@NonNull String relativePath) { - try { - return Files.toByteArray(new File(mRootPath, relativePath)); - } catch (IOException e) { - AdtPlugin.log(e, null); - return null; - } - } - - /** - * Most recent thrown exception during template instantiation. This should - * basically always be null. Used by unit tests to see if any template - * instantiation recorded a failure. - */ - @VisibleForTesting - public static Exception sMostRecentException; - - /** Read the given FreeMarker file and process the variable definitions */ - private void processVariables(final Configuration freemarker, - String file, final Map<String, Object> paramMap) { - try { - String xml; - if (file.endsWith(DOT_XML)) { - // Just read the file - xml = readTemplateTextResource(file); - if (xml == null) { - return; - } - } else { - mLoader.setTemplateFile(new File(mRootPath, file)); - Template inputsTemplate = freemarker.getTemplate(file); - StringWriter out = new StringWriter(); - inputsTemplate.process(paramMap, out); - out.flush(); - xml = out.toString(); - } - - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - saxParser.parse(new ByteArrayInputStream(xml.getBytes()), new DefaultHandler() { - @Override - public void startElement(String uri, String localName, String name, - Attributes attributes) - throws SAXException { - if (TAG_PARAMETER.equals(name)) { - String id = attributes.getValue(ATTR_ID); - if (!paramMap.containsKey(id)) { - String value = attributes.getValue(ATTR_DEFAULT); - Object mapValue = value; - if (value != null && !value.isEmpty()) { - String type = attributes.getValue(ATTR_TYPE); - if ("boolean".equals(type)) { //$NON-NLS-1$ - mapValue = Boolean.valueOf(value); - } - } - paramMap.put(id, mapValue); - } - } else if (TAG_GLOBAL.equals(name)) { - String id = attributes.getValue(ATTR_ID); - if (!paramMap.containsKey(id)) { - paramMap.put(id, TypedVariable.parseGlobal(attributes)); - } - } else if (TAG_GLOBALS.equals(name)) { - // Handle evaluation of variables - String path = attributes.getValue(ATTR_FILE); - if (path != null) { - 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); - } - } else if (TAG_DEPENDENCY.equals(name)) { - String dependencyName = attributes.getValue(ATTR_NAME); - if (dependencyName.equals(SUPPORT_LIBRARY_NAME)) { - // We assume the revision requirement has been satisfied - // by the wizard - File path = AddSupportJarAction.getSupportJarFile(); - if (path != null) { - IPath to = getTargetPath(FD_NATIVE_LIBS +'/' + path.getName()); - try { - copy(path, to); - } catch (IOException ioe) { - AdtPlugin.log(ioe, null); - } - } - } - } else if (!name.equals("template") && !name.equals(TAG_CATEGORY) && - !name.equals(TAG_FORMFACTOR) && !name.equals("option") && - !name.equals(TAG_THUMBS) && !name.equals(TAG_THUMB) && - !name.equals(TAG_ICONS)) { - System.err.println("WARNING: Unknown template directive " + name); - } - } - }); - } catch (Exception e) { - sMostRecentException = e; - AdtPlugin.log(e, null); - } - } - - @SuppressWarnings("unused") - private boolean canOverwrite(File file) { - 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, - String.format( - "%1$s already exists.\nWould you like to replace it?", - file.getPath()), - MessageDialog.QUESTION, new String[] { - // Yes will be moved to the end because it's the default - "Yes", "No", "Cancel", "Yes to All" - }, 0); - int result = dialog.open(); - switch (result) { - case 0: - // Yes - break; - case 3: - // Yes to all - mYesToAll = true; - break; - case 1: - // No - return false; - case SWT.DEFAULT: - case 2: - // Cancel - mNoToAll = true; - return false; - } - } - - if (mBackupMergedFiles) { - return makeBackup(file); - } else { - return file.delete(); - } - } - - return true; - } - - /** Executes the given recipe file: copying, merging, instantiating, opening files etc */ - private void execute( - final Configuration freemarker, - String file, - final Map<String, Object> paramMap) { - try { - mLoader.setTemplateFile(new File(mRootPath, file)); - Template freemarkerTemplate = freemarker.getTemplate(file); - - StringWriter out = new StringWriter(); - freemarkerTemplate.process(paramMap, out); - out.flush(); - String xml = out.toString(); - - // Parse and execute the resulting instruction list. - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - - saxParser.parse(new ByteArrayInputStream(xml.getBytes()), - new DefaultHandler() { - @Override - public void startElement(String uri, String localName, String name, - Attributes attributes) - throws SAXException { - if (mNoToAll) { - return; - } - - try { - boolean instantiate = TAG_INSTANTIATE.equals(name); - if (TAG_COPY.equals(name) || instantiate) { - String fromPath = attributes.getValue(ATTR_FROM); - String toPath = attributes.getValue(ATTR_TO); - if (toPath == null || toPath.isEmpty()) { - toPath = attributes.getValue(ATTR_FROM); - toPath = AdtUtils.stripSuffix(toPath, DOT_FTL); - } - IPath to = getTargetPath(toPath); - if (instantiate) { - instantiate(freemarker, paramMap, fromPath, to); - } else { - copyTemplateResource(fromPath, to); - } - } else if (TAG_MERGE.equals(name)) { - String fromPath = attributes.getValue(ATTR_FROM); - String toPath = attributes.getValue(ATTR_TO); - if (toPath == null || toPath.isEmpty()) { - toPath = attributes.getValue(ATTR_FROM); - toPath = AdtUtils.stripSuffix(toPath, DOT_FTL); - } - // Resources in template.xml are located within root/ - IPath to = getTargetPath(toPath); - merge(freemarker, paramMap, fromPath, to); - } else if (name.equals(TAG_OPEN)) { - // The relative path here is within the output directory: - String relativePath = attributes.getValue(ATTR_FILE); - if (relativePath != null && !relativePath.isEmpty()) { - mOpen.add(relativePath); - } - } else if (TAG_DEPENDENCY.equals(name)) { - String dependencyUrl = attributes.getValue(ATTR_MAVEN_URL); - File path; - if (dependencyUrl.contains(MAVEN_SUPPORT_V4)) { - // We assume the revision requirement has been satisfied - // by the wizard - path = AddSupportJarAction.getSupportJarFile(); - } else if (dependencyUrl.contains(MAVEN_SUPPORT_V13)) { - path = AddSupportJarAction.getSupport13JarFile(); - } else if (dependencyUrl.contains(MAVEN_APPCOMPAT)) { - path = null; - mFinalizingActions.add(new Runnable() { - @Override - public void run() { - AddSupportJarAction.installAppCompatLibrary(mProject, true); - } - }); - } else { - path = null; - System.err.println("WARNING: Unknown dependency type"); - } - - if (path != null) { - IPath to = getTargetPath(FD_NATIVE_LIBS +'/' + path.getName()); - try { - copy(path, to); - } catch (IOException ioe) { - AdtPlugin.log(ioe, null); - } - } - } else if (!name.equals("recipe") && !name.equals(TAG_DEPENDENCY)) { //$NON-NLS-1$ - System.err.println("WARNING: Unknown template directive " + name); - } - } catch (Exception e) { - sMostRecentException = e; - AdtPlugin.log(e, null); - } - } - }); - - } catch (Exception e) { - sMostRecentException = e; - AdtPlugin.log(e, null); - } - } - - @NonNull - private File getFullPath(@NonNull String fromPath) { - if (fromPath.startsWith(VALUE_TEMPLATE_DIR)) { - return new File(getTemplateRootFolder(), RESOURCE_ROOT + File.separator - + fromPath.substring(VALUE_TEMPLATE_DIR.length() + 1).replace('/', - File.separatorChar)); - } - 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 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, toPath); - } else { - copyTemplateResource(relativeFrom, toPath); - } - return; - } - - if (!to.getFileExtension().equals(EXT_XML)) { - throw new RuntimeException("Only XML files can be merged at this point: " + to); - } - - String xml = null; - File from = getFullPath(relativeFrom); - if (relativeFrom.endsWith(DOT_FTL)) { - // Perform template substitution of the template prior to merging - mLoader.setTemplateFile(from); - Template template = freemarker.getTemplate(from.getName()); - Writer out = new StringWriter(); - template.process(paramMap, out); - out.flush(); - xml = out.toString(); - } else { - xml = readTemplateTextResource(from); - if (xml == null) { - return; - } - } - - Document currentDocument = DomUtilities.parseStructuredDocument(currentXml); - assert currentDocument != null : currentXml; - Document fragment = DomUtilities.parseStructuredDocument(xml); - assert fragment != null : xml; - - XmlFormatStyle formatStyle = XmlFormatStyle.MANIFEST; - boolean modified; - boolean ok; - String fileName = to.getName(); - if (fileName.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) { - modified = ok = mergeManifest(currentDocument, fragment); - } else { - // Merge plain XML files - String parentFolderName = to.getParent().getName(); - ResourceFolderType folderType = ResourceFolderType.getFolderType(parentFolderName); - if (folderType != null) { - formatStyle = EclipseXmlPrettyPrinter.getForFile(toPath); - } else { - formatStyle = XmlFormatStyle.FILE; - } - - modified = mergeResourceFile(currentDocument, fragment, folderType, paramMap); - ok = true; - } - - // Finally write out the merged file (formatting etc) - String contents = null; - if (ok) { - if (modified) { - contents = EclipseXmlPrettyPrinter.prettyPrint(currentDocument, - EclipseXmlFormatPreferences.create(), formatStyle, null, - currentXml.endsWith("\n")); //$NON-NLS-1$ - } - } else { - // Just insert into file along with comment, using the "standard" conflict - // syntax that many tools and editors recognize. - String sep = SdkUtils.getLineSeparator(); - contents = - "<<<<<<< Original" + sep - + currentXml + sep - + "=======" + sep - + xml - + ">>>>>>> Added" + sep; - } - - 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(SdkConstants.EXT_XML); - mMergeChanges.add(change); - } - } - - /** Merges the given resource file contents into the given resource file - * @param paramMap */ - private static boolean mergeResourceFile(Document currentDocument, Document fragment, - ResourceFolderType folderType, Map<String, Object> paramMap) { - boolean modified = false; - - // Copy namespace declarations - NamedNodeMap attributes = fragment.getDocumentElement().getAttributes(); - if (attributes != null) { - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Attr attribute = (Attr) attributes.item(i); - if (attribute.getName().startsWith(XMLNS_PREFIX)) { - currentDocument.getDocumentElement().setAttribute(attribute.getName(), - attribute.getValue()); - } - } - } - - // For layouts for example, I want to *append* inside the root all the - // contents of the new file. - // But for resources for example, I want to combine elements which specify - // the same name or id attribute. - // For elements like manifest files we need to insert stuff at the right - // location in a nested way (activities in the application element etc) - // but that doesn't happen for the other file types. - Element root = fragment.getDocumentElement(); - NodeList children = root.getChildNodes(); - List<Node> nodes = new ArrayList<Node>(children.getLength()); - for (int i = children.getLength() - 1; i >= 0; i--) { - Node child = children.item(i); - nodes.add(child); - root.removeChild(child); - } - Collections.reverse(nodes); - - root = currentDocument.getDocumentElement(); - - if (folderType == ResourceFolderType.VALUES) { - // Try to merge items of the same name - Map<String, Node> old = new HashMap<String, Node>(); - NodeList newSiblings = root.getChildNodes(); - for (int i = newSiblings.getLength() - 1; i >= 0; i--) { - Node child = newSiblings.item(i); - if (child.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) child; - String name = getResourceId(element); - if (name != null) { - old.put(name, element); - } - } - } - - for (Node node : nodes) { - if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - String name = getResourceId(element); - Node replace = name != null ? old.get(name) : null; - if (replace != null) { - // There is an existing item with the same id: just replace it - // ACTUALLY -- let's NOT change it. - // Let's say you've used the activity wizard once, and it - // emits some configuration parameter as a resource that - // it depends on, say "padding". Then the user goes and - // tweaks the padding to some other number. - // Now running the wizard a *second* time for some new activity, - // we should NOT go and set the value back to the template's - // default! - //root.replaceChild(node, replace); - - // ... ON THE OTHER HAND... What if it's a parameter class - // (where the template rewrites a common attribute). Here it's - // really confusing if the new parameter is not set. This is - // really an error in the template, since we shouldn't have conflicts - // like that, but we need to do something to help track this down. - AdtPlugin.log(null, - "Warning: Ignoring name conflict in resource file for name %1$s", - name); - } else { - root.appendChild(node); - modified = true; - } - } - } - } else { - // In other file types, such as layouts, just append all the new content - // at the end. - for (Node node : nodes) { - root.appendChild(node); - modified = true; - } - } - return modified; - } - - /** Merges the given manifest fragment into the given manifest file */ - private static boolean mergeManifest(Document currentManifest, Document fragment) { - // TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create - // and maintain error markers. - - // Transfer package element from manifest to merged in root; required by - // manifest merger - Element fragmentRoot = fragment.getDocumentElement(); - Element manifestRoot = currentManifest.getDocumentElement(); - if (fragmentRoot == null || manifestRoot == null) { - return false; - } - String pkg = fragmentRoot.getAttribute(ATTR_PACKAGE); - if (pkg == null || pkg.isEmpty()) { - pkg = manifestRoot.getAttribute(ATTR_PACKAGE); - if (pkg != null && !pkg.isEmpty()) { - fragmentRoot.setAttribute(ATTR_PACKAGE, pkg); - } - } - - ManifestMerger merger = new ManifestMerger( - MergerLog.wrapSdkLog(AdtPlugin.getDefault()), - new AdtManifestMergeCallback()).setExtractPackagePrefix(true); - return currentManifest != null && - fragment != null && - merger.process(currentManifest, fragment); - } - - /** - * Makes a backup of the given file, if it exists, by renaming it to name~ - * (and removing an old name~ file if it exists) - */ - private static boolean makeBackup(File file) { - if (!file.exists()) { - return true; - } - if (file.isDirectory()) { - return false; - } - - File backupFile = new File(file.getParentFile(), file.getName() + '~'); - if (backupFile.exists()) { - backupFile.delete(); - } - return file.renameTo(backupFile); - } - - private static String getResourceId(Element element) { - String name = element.getAttribute(ATTR_NAME); - if (name == null) { - name = element.getAttribute(ATTR_ID); - } - - return name; - } - - /** Instantiates the given template file into the given output file */ - private void instantiate( - @NonNull final Configuration freemarker, - @NonNull final Map<String, Object> paramMap, - @NonNull String relativeFrom, - @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 - copyTemplateResource(relativeFrom, to); - } else { - File from = getFullPath(relativeFrom); - mLoader.setTemplateFile(from); - Template template = freemarker.getTemplate(from.getName()); - Writer out = new StringWriter(1024); - template.process(paramMap, out); - out.flush(); - String contents = out.toString(); - - contents = format(mProject, contents, to); - IFile targetFile = getTargetFile(to); - TextFileChange change = createNewFileChange(targetFile); - MultiTextEdit rootEdit = new MultiTextEdit(); - rootEdit.addChild(new InsertEdit(0, contents)); - change.setEdit(rootEdit); - mTextChanges.add(change); - } - } - - private static String format(IProject project, String contents, IPath to) { - String name = to.lastSegment(); - if (name.endsWith(DOT_XML)) { - XmlFormatStyle formatStyle = EclipseXmlPrettyPrinter.getForFile(to); - EclipseXmlFormatPreferences prefs = EclipseXmlFormatPreferences.create(); - return EclipseXmlPrettyPrinter.prettyPrint(contents, prefs, formatStyle, null); - } else if (name.endsWith(DOT_JAVA)) { - Map<?, ?> options = null; - if (project != null && project.isAccessible()) { - try { - IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - if (javaProject != null) { - options = javaProject.getOptions(true); - } - } catch (CoreException e) { - AdtPlugin.log(e, null); - } - } - if (options == null) { - options = JavaCore.getOptions(); - } - - CodeFormatter formatter = ToolFactory.createCodeFormatter(options); - - try { - IDocument doc = new org.eclipse.jface.text.Document(); - // format the file (the meat and potatoes) - doc.set(contents); - TextEdit edit = formatter.format( - CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.F_INCLUDE_COMMENTS, - contents, 0, contents.length(), 0, null); - if (edit != null) { - edit.apply(doc); - } - - return doc.get(); - } catch (Exception e) { - AdtPlugin.log(e, null); - } - } - - return contents; - } - - private static TextFileChange createNewFileChange(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) { - @Override - protected IDocument acquireDocument(IProgressMonitor pm) throws CoreException { - IDocument document = super.acquireDocument(pm); - - // In our case, we know we *always* use this TextFileChange - // to *create* files, we're not appending to existing files. - // However, due to the following bug we can end up with cached - // contents of previously deleted files that happened to have the - // same file name: - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=390402 - // Therefore, as a workaround, wipe out the cached contents here - if (document.getLength() > 0) { - try { - document.replace(0, document.getLength(), ""); - } catch (BadLocationException e) { - // pass - } - } - - return document; - } - }; - change.setTextType(fileName.substring(fileName.lastIndexOf('.') + 1)); - return change; - } - - /** - * Returns the list of files to open when the template has been created - * - * @return the list of files to open - */ - @NonNull - public List<String> getFilesToOpen() { - return mOpen; - } - - /** - * Returns the list of actions to perform when the template has been created - * - * @return the list of actions to perform - */ - @NonNull - public List<Runnable> getFinalizingActions() { - return mFinalizingActions; - } - - /** Copy a template resource */ - private final void copyTemplateResource( - @NonNull String relativeFrom, - @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 IFile dest) { - assert dest.exists(); - byte[] existing = AdtUtils.readData(dest); - return Arrays.equals(existing, data); - } - - /** - * Copies the given source file into the given destination file (where the - * source is allowed to be a directory, in which case the whole directory is - * copied recursively) - */ - 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, path.append(child.getName())); - } - } - } else { - IResource dest = mProject.getFile(path); - if (dest.exists() && !(dest instanceof IFile)) {// Don't attempt to overwrite a folder - assert false : dest.getClass().getName(); - 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; - } - } - - 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); - newFile = format(mProject, newFile, path); - - TextFileChange addFile = createNewFileChange(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)); - } - } - } - - /** - * A custom {@link TemplateLoader} which locates and provides templates - * within the plugin .jar file - */ - private static final class MyTemplateLoader implements TemplateLoader { - private String mPrefix; - - public void setPrefix(String prefix) { - mPrefix = prefix; - } - - public void setTemplateFile(File file) { - setTemplateParent(file.getParentFile()); - } - - public void setTemplateParent(File parent) { - mPrefix = parent.getPath(); - } - - @Override - public Reader getReader(Object templateSource, String encoding) throws IOException { - URL url = (URL) templateSource; - return new InputStreamReader(url.openStream(), encoding); - } - - @Override - public long getLastModified(Object templateSource) { - return 0; - } - - @Override - public Object findTemplateSource(String name) throws IOException { - String path = mPrefix != null ? mPrefix + '/' + name : name; - File file = new File(path); - if (file.exists()) { - return file.toURI().toURL(); - } - return null; - } - - @Override - public void closeTemplateSource(Object templateSource) throws IOException { - } - } - - /** - * Validates this template to make sure it's supported - * @param currentMinSdk the minimum SDK in the project, or -1 or 0 if unknown (e.g. codename) - * @param buildApi the build API, or -1 or 0 if unknown (e.g. codename) - * - * @return a status object with the error, or null if there is no problem - */ - @SuppressWarnings("cast") // In Eclipse 3.6.2 cast below is needed - @Nullable - public IStatus validateTemplate(int currentMinSdk, int buildApi) { - TemplateMetadata template = getTemplate(); - if (template == null) { - return null; - } - if (!template.isSupported()) { - String versionString = (String) AdtPlugin.getDefault().getBundle().getHeaders().get( - Constants.BUNDLE_VERSION); - Version version = new Version(versionString); - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - String.format("This template requires a more recent version of the " + - "Android Eclipse plugin. Please update from version %1$d.%2$d.%3$d.", - version.getMajor(), version.getMinor(), version.getMicro())); - } - int templateMinSdk = template.getMinSdk(); - if (templateMinSdk > currentMinSdk && currentMinSdk >= 1) { - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - String.format("This template requires a minimum SDK version of at " + - "least %1$d, and the current min version is %2$d", - templateMinSdk, currentMinSdk)); - } - int templateMinBuildApi = template.getMinBuildApi(); - if (templateMinBuildApi > buildApi && buildApi >= 1) { - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - String.format("This template requires a build target API version of at " + - "least %1$d, and the current version is %2$d", - templateMinBuildApi, buildApi)); - } - - return null; - } -} |