Home | History | Annotate | Download | only in newxmlfile
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 
     19 package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
     20 
     21 import com.android.ide.common.resources.configuration.FolderConfiguration;
     22 import com.android.ide.eclipse.adt.AdtConstants;
     23 import com.android.ide.eclipse.adt.AdtPlugin;
     24 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     25 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     26 import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
     27 import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
     28 import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
     29 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     30 import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo;
     31 import com.android.resources.ResourceFolderType;
     32 import com.android.util.Pair;
     33 
     34 import org.eclipse.core.resources.IContainer;
     35 import org.eclipse.core.resources.IFile;
     36 import org.eclipse.core.resources.IFolder;
     37 import org.eclipse.core.resources.IProject;
     38 import org.eclipse.core.resources.IResource;
     39 import org.eclipse.core.runtime.CoreException;
     40 import org.eclipse.core.runtime.IPath;
     41 import org.eclipse.core.runtime.IStatus;
     42 import org.eclipse.core.runtime.Path;
     43 import org.eclipse.jface.resource.ImageDescriptor;
     44 import org.eclipse.jface.text.IRegion;
     45 import org.eclipse.jface.text.Region;
     46 import org.eclipse.jface.viewers.IStructuredSelection;
     47 import org.eclipse.jface.wizard.Wizard;
     48 import org.eclipse.ui.IEditorPart;
     49 import org.eclipse.ui.INewWizard;
     50 import org.eclipse.ui.IWorkbench;
     51 import org.eclipse.ui.PartInitException;
     52 
     53 import java.io.ByteArrayInputStream;
     54 import java.io.InputStream;
     55 import java.io.UnsupportedEncodingException;
     56 
     57 /**
     58  * The "New Android XML File Wizard" provides the ability to create skeleton XML
     59  * resources files for Android projects.
     60  * <p/>
     61  * The wizard has one page, {@link NewXmlFileCreationPage}, used to select the project,
     62  * the resource folder, resource type and file name. It then creates the XML file.
     63  */
     64 public class NewXmlFileWizard extends Wizard implements INewWizard {
     65     /** The XML header to write at the top of the XML file */
     66     public static final String XML_HEADER_LINE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$
     67 
     68     private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$
     69 
     70     protected static final String MAIN_PAGE_NAME = "newAndroidXmlFilePage"; //$NON-NLS-1$
     71 
     72     private NewXmlFileCreationPage mMainPage;
     73     private ChooseConfigurationPage mConfigPage;
     74     private Values mValues;
     75 
     76     public void init(IWorkbench workbench, IStructuredSelection selection) {
     77         setHelpAvailable(false); // TODO have help
     78         setWindowTitle("New Android XML File");
     79         setImageDescriptor();
     80 
     81         mValues = new Values();
     82         mMainPage = createMainPage(mValues);
     83         mMainPage.setTitle("New Android XML File");
     84         mMainPage.setDescription("Creates a new Android XML file.");
     85         mMainPage.setInitialSelection(selection);
     86 
     87         mConfigPage = new ChooseConfigurationPage(mValues);
     88     }
     89 
     90     /**
     91      * Creates the wizard page.
     92      * <p/>
     93      * Please do NOT override this method.
     94      * <p/>
     95      * This is protected so that it can be overridden by unit tests.
     96      * However the contract of this class is private and NO ATTEMPT will be made
     97      * to maintain compatibility between different versions of the plugin.
     98      */
     99     protected NewXmlFileCreationPage createMainPage(NewXmlFileWizard.Values values) {
    100         return new NewXmlFileCreationPage(MAIN_PAGE_NAME, values);
    101     }
    102 
    103     // -- Methods inherited from org.eclipse.jface.wizard.Wizard --
    104     //
    105     // The Wizard class implements most defaults and boilerplate code needed by
    106     // IWizard
    107 
    108     /**
    109      * Adds pages to this wizard.
    110      */
    111     @Override
    112     public void addPages() {
    113         addPage(mMainPage);
    114         addPage(mConfigPage);
    115 
    116     }
    117 
    118     /**
    119      * Performs any actions appropriate in response to the user having pressed
    120      * the Finish button, or refuse if finishing now is not permitted: here, it
    121      * actually creates the workspace project and then switch to the Java
    122      * perspective.
    123      *
    124      * @return True
    125      */
    126     @Override
    127     public boolean performFinish() {
    128         final Pair<IFile, IRegion> created = createXmlFile();
    129         if (created == null) {
    130             return false;
    131         } else {
    132             // Open the file
    133             // This has to be delayed in order for focus handling to work correctly
    134             AdtPlugin.getDisplay().asyncExec(new Runnable() {
    135                 public void run() {
    136                     IFile file = created.getFirst();
    137                     IRegion region = created.getSecond();
    138                     try {
    139                         IEditorPart editor = AdtPlugin.openFile(file, null,
    140                                 false /*showEditorTab*/);
    141                         if (editor instanceof AndroidXmlEditor) {
    142                             final AndroidXmlEditor xmlEditor = (AndroidXmlEditor)editor;
    143                             if (!xmlEditor.hasMultiplePages()) {
    144                                 xmlEditor.show(region.getOffset(), region.getLength(),
    145                                         true /* showEditorTab */);
    146                             }
    147                         }
    148                     } catch (PartInitException e) {
    149                         AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$
    150                                 file.getFullPath().toString());
    151                     }
    152                 }});
    153 
    154             return true;
    155         }
    156     }
    157 
    158     // -- Custom Methods --
    159 
    160     private Pair<IFile, IRegion> createXmlFile() {
    161         IFile file = mValues.getDestinationFile();
    162         TypeInfo type = mValues.type;
    163         if (type == null) {
    164             // this is not expected to happen
    165             String name = file.getFullPath().toString();
    166             AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing type", name);  //$NON-NLS-1$
    167             return null;
    168         }
    169         String xmlns = type.getXmlns();
    170         String root = mMainPage.getRootElement();
    171         if (root == null) {
    172             // this is not expected to happen
    173             AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing root element", //$NON-NLS-1$
    174                     file.toString());
    175             return null;
    176         }
    177 
    178         String attrs = type.getDefaultAttrs(mValues.project, root);
    179         String child = type.getChild(mValues.project, root);
    180         return createXmlFile(file, xmlns, root, attrs, child, type.getResFolderType());
    181     }
    182 
    183     /** Creates a new file using the given root element, namespace and root attributes */
    184     private static Pair<IFile, IRegion> createXmlFile(IFile file, String xmlns,
    185             String root, String rootAttributes, String child, ResourceFolderType folderType) {
    186         String name = file.getFullPath().toString();
    187         boolean need_delete = false;
    188 
    189         if (file.exists()) {
    190             if (!AdtPlugin.displayPrompt("New Android XML File",
    191                 String.format("Do you want to overwrite the file %1$s ?", name))) {
    192                 // abort if user selects cancel.
    193                 return null;
    194             }
    195             need_delete = true;
    196         } else {
    197             createWsParentDirectory(file.getParent());
    198         }
    199 
    200         StringBuilder sb = new StringBuilder(XML_HEADER_LINE);
    201 
    202         sb.append('<').append(root);
    203         if (xmlns != null) {
    204             sb.append('\n').append("  xmlns:android=\"").append(xmlns).append('"');  //$NON-NLS-1$
    205         }
    206 
    207         if (rootAttributes != null) {
    208             sb.append("\n  ");                       //$NON-NLS-1$
    209             sb.append(rootAttributes.replace("\n", "\n  "));  //$NON-NLS-1$ //$NON-NLS-2$
    210         }
    211 
    212         sb.append(">\n");                            //$NON-NLS-1$
    213 
    214         if (child != null) {
    215             sb.append(child);
    216         }
    217 
    218         boolean autoFormat = AdtPrefs.getPrefs().getUseCustomXmlFormatter();
    219 
    220         // Insert an indented caret. Since the markup here will be reformatted, we need to
    221         // insert text tokens that the formatter will preserve, which we can then turn back
    222         // into indentation and a caret offset:
    223         final String indentToken = "${indent}"; //$NON-NLS-1$
    224         final String caretToken = "${caret}";   //$NON-NLS-1$
    225         sb.append(indentToken);
    226         sb.append(caretToken);
    227         if (!autoFormat) {
    228             sb.append('\n');
    229         }
    230 
    231         sb.append("</").append(root).append(">\n");  //$NON-NLS-1$ //$NON-NLS-2$
    232 
    233         XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
    234         String fileContents;
    235         if (!autoFormat) {
    236             fileContents = sb.toString();
    237         } else {
    238             XmlFormatStyle style = XmlFormatStyle.getForFolderType(folderType);
    239             fileContents = XmlPrettyPrinter.prettyPrint(sb.toString(), formatPrefs,
    240                                 style, null /*lineSeparator*/);
    241         }
    242 
    243         // Remove marker tokens and replace them with whitespace
    244         fileContents = fileContents.replace(indentToken, formatPrefs.getOneIndentUnit());
    245         int caretOffset = fileContents.indexOf(caretToken);
    246         if (caretOffset != -1) {
    247             fileContents = fileContents.replace(caretToken, ""); //$NON-NLS-1$
    248         }
    249 
    250         String error = null;
    251         try {
    252             byte[] buf = fileContents.getBytes("UTF8");    //$NON-NLS-1$
    253             InputStream stream = new ByteArrayInputStream(buf);
    254             if (need_delete) {
    255                 file.delete(IResource.KEEP_HISTORY | IResource.FORCE, null /*monitor*/);
    256             }
    257             file.create(stream, true /*force*/, null /*progress*/);
    258             IRegion region = caretOffset != -1 ? new Region(caretOffset, 0) : null;
    259             return Pair.of(file, region);
    260         } catch (UnsupportedEncodingException e) {
    261             error = e.getMessage();
    262         } catch (CoreException e) {
    263             error = e.getMessage();
    264         }
    265 
    266         error = String.format("Failed to generate %1$s: %2$s", name, error);
    267         AdtPlugin.displayError("New Android XML File", error);
    268         return null;
    269     }
    270 
    271     /**
    272      * Returns true if the New XML Wizard can create new files of the given
    273      * {@link ResourceFolderType}
    274      *
    275      * @param folderType the folder type to create a file for
    276      * @return true if this wizard can create new files for the given folder type
    277      */
    278     public static boolean canCreateXmlFile(ResourceFolderType folderType) {
    279         TypeInfo typeInfo = NewXmlFileCreationPage.getTypeInfo(folderType);
    280         return typeInfo != null && (typeInfo.getDefaultRoot(null /*project*/) != null ||
    281                 typeInfo.getRootSeed() instanceof String);
    282     }
    283 
    284     /**
    285      * Creates a new XML file using the template according to the given folder type
    286      *
    287      * @param project the project to create the file in
    288      * @param file the file to be created
    289      * @param folderType the type of folder to look up a template for
    290      * @return the created file
    291      */
    292     public static Pair<IFile, IRegion> createXmlFile(IProject project, IFile file,
    293             ResourceFolderType folderType) {
    294         TypeInfo type = NewXmlFileCreationPage.getTypeInfo(folderType);
    295         String xmlns = type.getXmlns();
    296         String root = type.getDefaultRoot(project);
    297         if (root == null) {
    298             root = type.getRootSeed().toString();
    299         }
    300         String attrs = type.getDefaultAttrs(project, root);
    301         return createXmlFile(file, xmlns, root, attrs, null, folderType);
    302     }
    303 
    304     /**
    305      * Creates all the directories required for the given path.
    306      *
    307      * @param wsPath the path to create all the parent directories for
    308      * @return true if all the parent directories were created
    309      */
    310     public static boolean createWsParentDirectory(IContainer wsPath) {
    311         if (wsPath.getType() == IResource.FOLDER) {
    312             if (wsPath.exists()) {
    313                 return true;
    314             }
    315 
    316             IFolder folder = (IFolder) wsPath;
    317             try {
    318                 if (createWsParentDirectory(wsPath.getParent())) {
    319                     folder.create(true /* force */, true /* local */, null /* monitor */);
    320                     return true;
    321                 }
    322             } catch (CoreException e) {
    323                 e.printStackTrace();
    324             }
    325         }
    326 
    327         return false;
    328     }
    329 
    330     /**
    331      * Returns an image descriptor for the wizard logo.
    332      */
    333     private void setImageDescriptor() {
    334         ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE);
    335         setDefaultPageImageDescriptor(desc);
    336     }
    337 
    338     /**
    339      * Specific New XML File wizard tied to the {@link ResourceFolderType#LAYOUT} type
    340      */
    341     public static class NewLayoutWizard extends NewXmlFileWizard {
    342         /** Creates a new {@link NewLayoutWizard} */
    343         public NewLayoutWizard() {
    344         }
    345 
    346         @Override
    347         public void init(IWorkbench workbench, IStructuredSelection selection) {
    348             super.init(workbench, selection);
    349             setWindowTitle("New Android Layout XML File");
    350             super.mMainPage.setTitle("New Android Layout XML File");
    351             super.mMainPage.setDescription("Creates a new Android Layout XML file.");
    352             super.mMainPage.setInitialFolderType(ResourceFolderType.LAYOUT);
    353         }
    354     }
    355 
    356     /**
    357      * Specific New XML File wizard tied to the {@link ResourceFolderType#VALUES} type
    358      */
    359     public static class NewValuesWizard extends NewXmlFileWizard {
    360         /** Creates a new {@link NewValuesWizard} */
    361         public NewValuesWizard() {
    362         }
    363 
    364         @Override
    365         public void init(IWorkbench workbench, IStructuredSelection selection) {
    366             super.init(workbench, selection);
    367             setWindowTitle("New Android Values XML File");
    368             super.mMainPage.setTitle("New Android Values XML File");
    369             super.mMainPage.setDescription("Creates a new Android Values XML file.");
    370             super.mMainPage.setInitialFolderType(ResourceFolderType.VALUES);
    371         }
    372     }
    373 
    374     /** Value object which holds the current state of the wizard pages */
    375     public static class Values {
    376         /** The currently selected project, or null */
    377         public IProject project;
    378         /** The root name of the XML file to create, or null */
    379         public String name;
    380         /** The type of XML file to create */
    381         public TypeInfo type;
    382         /** The path within the project to create the new file in */
    383         public String folderPath;
    384         /** The currently chosen configuration */
    385         public FolderConfiguration configuration = new FolderConfiguration();
    386 
    387         /**
    388          * Returns the destination filename or an empty string.
    389          *
    390          * @return the filename, never null.
    391          */
    392         public String getFileName() {
    393             String fileName;
    394             if (name == null) {
    395                 fileName = ""; //$NON-NLS-1$
    396             } else {
    397                 fileName = name.trim();
    398                 if (fileName.length() > 0 && fileName.indexOf('.') == -1) {
    399                     fileName = fileName + AdtConstants.DOT_XML;
    400                 }
    401             }
    402 
    403             return fileName;
    404         }
    405 
    406         /**
    407          * Returns a {@link IFile} for the destination file.
    408          * <p/>
    409          * Returns null if the project, filename or folder are invalid and the
    410          * destination file cannot be determined.
    411          * <p/>
    412          * The {@link IFile} is a resource. There might or might not be an
    413          * actual real file.
    414          *
    415          * @return an {@link IFile} for the destination file
    416          */
    417         public IFile getDestinationFile() {
    418             String fileName = getFileName();
    419             if (project != null && folderPath != null && folderPath.length() > 0
    420                     && fileName.length() > 0) {
    421                 IPath dest = new Path(folderPath).append(fileName);
    422                 IFile file = project.getFile(dest);
    423                 return file;
    424             }
    425             return null;
    426         }
    427     }
    428 }
    429