• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
init(IWorkbench workbench, IStructuredSelection selection)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      */
createMainPage(NewXmlFileWizard.Values values)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
addPages()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
performFinish()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 
createXmlFile()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 */
createXmlFile(IFile file, String xmlns, String root, String rootAttributes, String child, ResourceFolderType folderType)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      */
canCreateXmlFile(ResourceFolderType folderType)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      */
createXmlFile(IProject project, IFile file, ResourceFolderType folderType)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      */
createWsParentDirectory(IContainer wsPath)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      */
setImageDescriptor()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} */
NewLayoutWizard()343         public NewLayoutWizard() {
344         }
345 
346         @Override
init(IWorkbench workbench, IStructuredSelection selection)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} */
NewValuesWizard()361         public NewValuesWizard() {
362         }
363 
364         @Override
init(IWorkbench workbench, IStructuredSelection selection)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          */
getFileName()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          */
getDestinationFile()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