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