• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.ide.eclipse.adt;
18 
19 import static com.android.SdkConstants.TOOLS_PREFIX;
20 import static com.android.SdkConstants.TOOLS_URI;
21 import static org.eclipse.ui.IWorkbenchPage.MATCH_INPUT;
22 
23 import com.android.SdkConstants;
24 import com.android.annotations.NonNull;
25 import com.android.annotations.Nullable;
26 import com.android.ide.common.sdk.SdkVersionInfo;
27 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
28 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
29 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
30 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
31 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
32 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
33 import com.android.resources.ResourceFolderType;
34 import com.android.resources.ResourceType;
35 import com.android.sdklib.AndroidVersion;
36 import com.android.sdklib.IAndroidTarget;
37 import com.android.sdklib.repository.PkgProps;
38 import com.android.utils.XmlUtils;
39 import com.google.common.io.ByteStreams;
40 import com.google.common.io.Closeables;
41 
42 import org.eclipse.core.filebuffers.FileBuffers;
43 import org.eclipse.core.filebuffers.ITextFileBuffer;
44 import org.eclipse.core.filebuffers.ITextFileBufferManager;
45 import org.eclipse.core.filebuffers.LocationKind;
46 import org.eclipse.core.filesystem.URIUtil;
47 import org.eclipse.core.resources.IContainer;
48 import org.eclipse.core.resources.IFile;
49 import org.eclipse.core.resources.IFolder;
50 import org.eclipse.core.resources.IMarker;
51 import org.eclipse.core.resources.IProject;
52 import org.eclipse.core.resources.IResource;
53 import org.eclipse.core.resources.IWorkspace;
54 import org.eclipse.core.resources.IWorkspaceRoot;
55 import org.eclipse.core.resources.ResourcesPlugin;
56 import org.eclipse.core.runtime.CoreException;
57 import org.eclipse.core.runtime.IAdaptable;
58 import org.eclipse.core.runtime.IPath;
59 import org.eclipse.core.runtime.NullProgressMonitor;
60 import org.eclipse.core.runtime.Path;
61 import org.eclipse.core.runtime.Platform;
62 import org.eclipse.jdt.core.IJavaProject;
63 import org.eclipse.jface.text.BadLocationException;
64 import org.eclipse.jface.text.IDocument;
65 import org.eclipse.jface.text.IRegion;
66 import org.eclipse.jface.viewers.ISelection;
67 import org.eclipse.jface.viewers.IStructuredSelection;
68 import org.eclipse.swt.widgets.Display;
69 import org.eclipse.ui.IEditorInput;
70 import org.eclipse.ui.IEditorPart;
71 import org.eclipse.ui.IEditorReference;
72 import org.eclipse.ui.IFileEditorInput;
73 import org.eclipse.ui.IURIEditorInput;
74 import org.eclipse.ui.IWorkbench;
75 import org.eclipse.ui.IWorkbenchPage;
76 import org.eclipse.ui.IWorkbenchPart;
77 import org.eclipse.ui.IWorkbenchWindow;
78 import org.eclipse.ui.PartInitException;
79 import org.eclipse.ui.PlatformUI;
80 import org.eclipse.ui.editors.text.TextFileDocumentProvider;
81 import org.eclipse.ui.part.FileEditorInput;
82 import org.eclipse.ui.texteditor.IDocumentProvider;
83 import org.eclipse.ui.texteditor.ITextEditor;
84 import org.eclipse.wst.sse.core.StructuredModelManager;
85 import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
86 import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
87 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
88 import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
89 import org.w3c.dom.Attr;
90 import org.w3c.dom.Document;
91 import org.w3c.dom.Element;
92 import org.w3c.dom.Node;
93 
94 import java.io.File;
95 import java.io.InputStream;
96 import java.net.URISyntaxException;
97 import java.net.URL;
98 import java.util.ArrayList;
99 import java.util.Collection;
100 import java.util.Collections;
101 import java.util.Iterator;
102 import java.util.List;
103 import java.util.Locale;
104 
105 
106 /** Utility methods for ADT */
107 @SuppressWarnings("restriction") // WST API
108 public class AdtUtils {
109     /**
110      * Creates a Java class name out of the given string, if possible. For
111      * example, "My Project" becomes "MyProject", "hello" becomes "Hello",
112      * "Java's" becomes "Java", and so on.
113      *
114      * @param string the string to be massaged into a Java class
115      * @return the string as a Java class, or null if a class name could not be
116      *         extracted
117      */
118     @Nullable
extractClassName(@onNull String string)119     public static String extractClassName(@NonNull String string) {
120         StringBuilder sb = new StringBuilder(string.length());
121         int n = string.length();
122 
123         int i = 0;
124         for (; i < n; i++) {
125             char c = Character.toUpperCase(string.charAt(i));
126             if (Character.isJavaIdentifierStart(c)) {
127                 sb.append(c);
128                 i++;
129                 break;
130             }
131         }
132         if (sb.length() > 0) {
133             for (; i < n; i++) {
134                 char c = string.charAt(i);
135                 if (Character.isJavaIdentifierPart(c)) {
136                     sb.append(c);
137                 }
138             }
139 
140             return sb.toString();
141         }
142 
143         return null;
144     }
145 
146     /**
147      * Strips off the last file extension from the given filename, e.g.
148      * "foo.backup.diff" will be turned into "foo.backup".
149      * <p>
150      * Note that dot files (e.g. ".profile") will be left alone.
151      *
152      * @param filename the filename to be stripped
153      * @return the filename without the last file extension.
154      */
stripLastExtension(String filename)155     public static String stripLastExtension(String filename) {
156         int dotIndex = filename.lastIndexOf('.');
157         if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently
158             return filename.substring(0, dotIndex);
159         } else {
160             return filename;
161         }
162     }
163 
164     /**
165      * Strips off all extensions from the given filename, e.g. "foo.9.png" will
166      * be turned into "foo".
167      * <p>
168      * Note that dot files (e.g. ".profile") will be left alone.
169      *
170      * @param filename the filename to be stripped
171      * @return the filename without any file extensions
172      */
stripAllExtensions(String filename)173     public static String stripAllExtensions(String filename) {
174         int dotIndex = filename.indexOf('.');
175         if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently
176             return filename.substring(0, dotIndex);
177         } else {
178             return filename;
179         }
180     }
181 
182     /**
183      * Strips the given suffix from the given string, provided that the string ends with
184      * the suffix.
185      *
186      * @param string the full string to strip from
187      * @param suffix the suffix to strip out
188      * @return the string without the suffix at the end
189      */
stripSuffix(@onNull String string, @NonNull String suffix)190     public static String stripSuffix(@NonNull String string, @NonNull String suffix) {
191         if (string.endsWith(suffix)) {
192             return string.substring(0, string.length() - suffix.length());
193         }
194 
195         return string;
196     }
197 
198     /**
199      * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
200      * Returns the string unmodified if the first character is not [a-z].
201      *
202      * @param str The string to capitalize.
203      * @return The capitalized string
204      */
capitalize(String str)205     public static String capitalize(String str) {
206         if (str == null || str.length() < 1 || Character.isUpperCase(str.charAt(0))) {
207             return str;
208         }
209 
210         StringBuilder sb = new StringBuilder();
211         sb.append(Character.toUpperCase(str.charAt(0)));
212         sb.append(str.substring(1));
213         return sb.toString();
214     }
215 
216     /**
217      * Converts a CamelCase word into an underlined_word
218      *
219      * @param string the CamelCase version of the word
220      * @return the underlined version of the word
221      */
camelCaseToUnderlines(String string)222     public static String camelCaseToUnderlines(String string) {
223         if (string.isEmpty()) {
224             return string;
225         }
226 
227         StringBuilder sb = new StringBuilder(2 * string.length());
228         int n = string.length();
229         boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
230         for (int i = 0; i < n; i++) {
231             char c = string.charAt(i);
232             boolean isUpperCase = Character.isUpperCase(c);
233             if (isUpperCase && !lastWasUpperCase) {
234                 sb.append('_');
235             }
236             lastWasUpperCase = isUpperCase;
237             c = Character.toLowerCase(c);
238             sb.append(c);
239         }
240 
241         return sb.toString();
242     }
243 
244     /**
245      * Converts an underlined_word into a CamelCase word
246      *
247      * @param string the underlined word to convert
248      * @return the CamelCase version of the word
249      */
underlinesToCamelCase(String string)250     public static String underlinesToCamelCase(String string) {
251         StringBuilder sb = new StringBuilder(string.length());
252         int n = string.length();
253 
254         int i = 0;
255         boolean upcaseNext = true;
256         for (; i < n; i++) {
257             char c = string.charAt(i);
258             if (c == '_') {
259                 upcaseNext = true;
260             } else {
261                 if (upcaseNext) {
262                     c = Character.toUpperCase(c);
263                 }
264                 upcaseNext = false;
265                 sb.append(c);
266             }
267         }
268 
269         return sb.toString();
270     }
271 
272     /**
273      * Returns the current editor (the currently visible and active editor), or null if
274      * not found
275      *
276      * @return the current editor, or null
277      */
getActiveEditor()278     public static IEditorPart getActiveEditor() {
279         IWorkbenchWindow window = getActiveWorkbenchWindow();
280         if (window != null) {
281             IWorkbenchPage page = window.getActivePage();
282             if (page != null) {
283                 return page.getActiveEditor();
284             }
285         }
286 
287         return null;
288     }
289 
290     /**
291      * Returns the current active workbench, or null if not found
292      *
293      * @return the current window, or null
294      */
295     @Nullable
getActiveWorkbenchWindow()296     public static IWorkbenchWindow getActiveWorkbenchWindow() {
297         IWorkbench workbench = PlatformUI.getWorkbench();
298         IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
299         if (window == null) {
300             IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
301             if (windows.length > 0) {
302                 window = windows[0];
303             }
304         }
305 
306         return window;
307     }
308 
309     /**
310      * Returns the current active workbench page, or null if not found
311      *
312      * @return the current page, or null
313      */
314     @Nullable
getActiveWorkbenchPage()315     public static IWorkbenchPage getActiveWorkbenchPage() {
316         IWorkbenchWindow window = getActiveWorkbenchWindow();
317         if (window != null) {
318             IWorkbenchPage page = window.getActivePage();
319             if (page == null) {
320                 IWorkbenchPage[] pages = window.getPages();
321                 if (pages.length > 0) {
322                     page = pages[0];
323                 }
324             }
325 
326             return page;
327         }
328 
329         return null;
330     }
331 
332     /**
333      * Returns the current active workbench part, or null if not found
334      *
335      * @return the current active workbench part, or null
336      */
337     @Nullable
getActivePart()338     public static IWorkbenchPart getActivePart() {
339         IWorkbenchWindow window = getActiveWorkbenchWindow();
340         if (window != null) {
341             IWorkbenchPage activePage = window.getActivePage();
342             if (activePage != null) {
343                 return activePage.getActivePart();
344             }
345         }
346         return null;
347     }
348 
349     /**
350      * Returns the current text editor (the currently visible and active editor), or null
351      * if not found.
352      *
353      * @return the current text editor, or null
354      */
getActiveTextEditor()355     public static ITextEditor getActiveTextEditor() {
356         IEditorPart editor = getActiveEditor();
357         if (editor != null) {
358             if (editor instanceof ITextEditor) {
359                 return (ITextEditor) editor;
360             } else {
361                 return (ITextEditor) editor.getAdapter(ITextEditor.class);
362             }
363         }
364 
365         return null;
366     }
367 
368     /**
369      * Looks through the open editors and returns the editors that have the
370      * given file as input.
371      *
372      * @param file the file to search for
373      * @param restore whether editors should be restored (if they have an open
374      *            tab, but the editor hasn't been restored since the most recent
375      *            IDE start yet
376      * @return a collection of editors
377      */
378     @NonNull
findEditorsFor(@onNull IFile file, boolean restore)379     public static Collection<IEditorPart> findEditorsFor(@NonNull IFile file, boolean restore) {
380         FileEditorInput input = new FileEditorInput(file);
381         List<IEditorPart> result = null;
382         IWorkbench workbench = PlatformUI.getWorkbench();
383         IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
384         for (IWorkbenchWindow window : windows) {
385             IWorkbenchPage[] pages = window.getPages();
386             for (IWorkbenchPage page : pages) {
387                 IEditorReference[] editors = page.findEditors(input, null,  MATCH_INPUT);
388                 if (editors != null) {
389                     for (IEditorReference reference : editors) {
390                         IEditorPart editor = reference.getEditor(restore);
391                         if (editor != null) {
392                             if (result == null) {
393                                 result = new ArrayList<IEditorPart>();
394                             }
395                             result.add(editor);
396                         }
397                     }
398                 }
399             }
400         }
401 
402         if (result == null) {
403             return Collections.emptyList();
404         }
405 
406         return result;
407     }
408 
409     /**
410      * Attempts to convert the given {@link URL} into a {@link File}.
411      *
412      * @param url the {@link URL} to be converted
413      * @return the corresponding {@link File}, which may not exist
414      */
415     @NonNull
getFile(@onNull URL url)416     public static File getFile(@NonNull URL url) {
417         try {
418             // First try URL.toURI(): this will work for URLs that contain %20 for spaces etc.
419             // Unfortunately, it *doesn't* work for "broken" URLs where the URL contains
420             // spaces, which is often the case.
421             return new File(url.toURI());
422         } catch (URISyntaxException e) {
423             // ...so as a fallback, go to the old url.getPath() method, which handles space paths.
424             return new File(url.getPath());
425         }
426     }
427 
428     /**
429      * Returns the file for the current editor, if any.
430      *
431      * @return the file for the current editor, or null if none
432      */
getActiveFile()433     public static IFile getActiveFile() {
434         IEditorPart editor = getActiveEditor();
435         if (editor != null) {
436             IEditorInput input = editor.getEditorInput();
437             if (input instanceof IFileEditorInput) {
438                 IFileEditorInput fileInput = (IFileEditorInput) input;
439                 return fileInput.getFile();
440             }
441         }
442 
443         return null;
444     }
445 
446     /**
447      * Returns an absolute path to the given resource
448      *
449      * @param resource the resource to look up a path for
450      * @return an absolute file system path to the resource
451      */
452     @NonNull
getAbsolutePath(@onNull IResource resource)453     public static IPath getAbsolutePath(@NonNull IResource resource) {
454         IPath location = resource.getRawLocation();
455         if (location != null) {
456             return location.makeAbsolute();
457         } else {
458             IWorkspace workspace = ResourcesPlugin.getWorkspace();
459             IWorkspaceRoot root = workspace.getRoot();
460             IPath workspacePath = root.getLocation();
461             return workspacePath.append(resource.getFullPath());
462         }
463     }
464 
465     /**
466      * Converts a workspace-relative path to an absolute file path
467      *
468      * @param path the workspace-relative path to convert
469      * @return the corresponding absolute file in the file system
470      */
471     @NonNull
workspacePathToFile(@onNull IPath path)472     public static File workspacePathToFile(@NonNull IPath path) {
473         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
474         IResource res = root.findMember(path);
475         if (res != null) {
476             IPath location = res.getLocation();
477             if (location != null) {
478                 return location.toFile();
479             }
480             return root.getLocation().append(path).toFile();
481         }
482 
483         return path.toFile();
484     }
485 
486     /**
487      * Converts a {@link File} to an {@link IFile}, if possible.
488      *
489      * @param file a file to be converted
490      * @return the corresponding {@link IFile}, or null
491      */
fileToIFile(File file)492     public static IFile fileToIFile(File file) {
493         if (!file.isAbsolute()) {
494             file = file.getAbsoluteFile();
495         }
496 
497         IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
498         IFile[] files = workspace.findFilesForLocationURI(file.toURI());
499         if (files.length > 0) {
500             return files[0];
501         }
502 
503         IPath filePath = new Path(file.getPath());
504         return pathToIFile(filePath);
505     }
506 
507     /**
508      * Converts a {@link File} to an {@link IResource}, if possible.
509      *
510      * @param file a file to be converted
511      * @return the corresponding {@link IResource}, or null
512      */
fileToResource(File file)513     public static IResource fileToResource(File file) {
514         if (!file.isAbsolute()) {
515             file = file.getAbsoluteFile();
516         }
517 
518         IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
519         IFile[] files = workspace.findFilesForLocationURI(file.toURI());
520         if (files.length > 0) {
521             return files[0];
522         }
523 
524         IPath filePath = new Path(file.getPath());
525         return pathToResource(filePath);
526     }
527 
528     /**
529      * Converts a {@link IPath} to an {@link IFile}, if possible.
530      *
531      * @param path a path to be converted
532      * @return the corresponding {@link IFile}, or null
533      */
pathToIFile(IPath path)534     public static IFile pathToIFile(IPath path) {
535         IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
536 
537         IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute()));
538         if (files.length > 0) {
539             return files[0];
540         }
541 
542         IPath workspacePath = workspace.getLocation();
543         if (workspacePath.isPrefixOf(path)) {
544             IPath relativePath = path.makeRelativeTo(workspacePath);
545             IResource member = workspace.findMember(relativePath);
546             if (member instanceof IFile) {
547                 return (IFile) member;
548             }
549         } else if (path.isAbsolute()) {
550             return workspace.getFileForLocation(path);
551         }
552 
553         return null;
554     }
555 
556     /**
557      * Converts a {@link IPath} to an {@link IResource}, if possible.
558      *
559      * @param path a path to be converted
560      * @return the corresponding {@link IResource}, or null
561      */
pathToResource(IPath path)562     public static IResource pathToResource(IPath path) {
563         IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
564 
565         IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute()));
566         if (files.length > 0) {
567             return files[0];
568         }
569 
570         IPath workspacePath = workspace.getLocation();
571         if (workspacePath.isPrefixOf(path)) {
572             IPath relativePath = path.makeRelativeTo(workspacePath);
573             return workspace.findMember(relativePath);
574         } else if (path.isAbsolute()) {
575             return workspace.getFileForLocation(path);
576         }
577 
578         return null;
579     }
580 
581     /**
582      * Returns all markers in a file/document that fit on the same line as the given offset
583      *
584      * @param markerType the marker type
585      * @param file the file containing the markers
586      * @param document the document showing the markers
587      * @param offset the offset to be checked
588      * @return a list (possibly empty but never null) of matching markers
589      */
590     @NonNull
findMarkersOnLine( @onNull String markerType, @NonNull IResource file, @NonNull IDocument document, int offset)591     public static List<IMarker> findMarkersOnLine(
592             @NonNull String markerType,
593             @NonNull IResource file,
594             @NonNull IDocument document,
595             int offset) {
596         List<IMarker> matchingMarkers = new ArrayList<IMarker>(2);
597         try {
598             IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO);
599 
600             // Look for a match on the same line as the caret.
601             IRegion lineInfo = document.getLineInformationOfOffset(offset);
602             int lineStart = lineInfo.getOffset();
603             int lineEnd = lineStart + lineInfo.getLength();
604             int offsetLine = document.getLineOfOffset(offset);
605 
606 
607             for (IMarker marker : markers) {
608                 int start = marker.getAttribute(IMarker.CHAR_START, -1);
609                 int end = marker.getAttribute(IMarker.CHAR_END, -1);
610                 if (start >= lineStart && start <= lineEnd && end > start) {
611                     matchingMarkers.add(marker);
612                 } else if (start == -1 && end == -1) {
613                     // Some markers don't set character range, they only set the line
614                     int line = marker.getAttribute(IMarker.LINE_NUMBER, -1);
615                     if (line == offsetLine + 1) {
616                         matchingMarkers.add(marker);
617                     }
618                 }
619             }
620         } catch (CoreException e) {
621             AdtPlugin.log(e, null);
622         } catch (BadLocationException e) {
623             AdtPlugin.log(e, null);
624         }
625 
626         return matchingMarkers;
627     }
628 
629     /**
630      * Returns the available and open Android projects
631      *
632      * @return the available and open Android projects, never null
633      */
634     @NonNull
getOpenAndroidProjects()635     public static IJavaProject[] getOpenAndroidProjects() {
636         return BaseProjectHelper.getAndroidProjects(new IProjectFilter() {
637             @Override
638             public boolean accept(IProject project) {
639                 return project.isAccessible();
640             }
641         });
642     }
643 
644     /**
645      * Returns a unique project name, based on the given {@code base} file name
646      * possibly with a {@code conjunction} and a new number behind it to ensure
647      * that the project name is unique. For example,
648      * {@code getUniqueProjectName("project", "_")} will return
649      * {@code "project"} if that name does not already exist, and if it does, it
650      * will return {@code "project_2"}.
651      *
652      * @param base the base name to use, such as "foo"
653      * @param conjunction a string to insert between the base name and the
654      *            number.
655      * @return a unique project name based on the given base and conjunction
656      */
657     public static String getUniqueProjectName(String base, String conjunction) {
658         // We're using all workspace projects here rather than just open Android project
659         // via getOpenAndroidProjects because the name cannot conflict with non-Android
660         // or closed projects either
661         IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
662         IProject[] projects = workspaceRoot.getProjects();
663 
664         for (int i = 1; i < 1000; i++) {
665             String name = i == 1 ? base : base + conjunction + Integer.toString(i);
666             boolean found = false;
667             for (IProject project : projects) {
668                 // Need to make case insensitive comparison, since otherwise we can hit
669                 // org.eclipse.core.internal.resources.ResourceException:
670                 // A resource exists with a different case: '/test'.
671                 if (project.getName().equalsIgnoreCase(name)) {
672                     found = true;
673                     break;
674                 }
675             }
676             if (!found) {
677                 return name;
678             }
679         }
680 
681         return base;
682     }
683 
684     /**
685      * Returns the name of the parent folder for the given editor input
686      *
687      * @param editorInput the editor input to check
688      * @return the parent folder, which is never null but may be ""
689      */
690     @NonNull
691     public static String getParentFolderName(@Nullable IEditorInput editorInput) {
692         if (editorInput instanceof IFileEditorInput) {
693              IFile file = ((IFileEditorInput) editorInput).getFile();
694              return file.getParent().getName();
695         }
696 
697         if (editorInput instanceof IURIEditorInput) {
698             IURIEditorInput urlEditorInput = (IURIEditorInput) editorInput;
699             String path = urlEditorInput.getURI().toString();
700             int lastIndex = path.lastIndexOf('/');
701             if (lastIndex != -1) {
702                 int lastLastIndex = path.lastIndexOf('/', lastIndex - 1);
703                 if (lastLastIndex != -1) {
704                     return path.substring(lastLastIndex + 1, lastIndex);
705                 }
706             }
707         }
708 
709         return "";
710     }
711 
712     /**
713      * Returns the XML editor for the given editor part
714      *
715      * @param part the editor part to look up the editor for
716      * @return the editor or null if this part is not an XML editor
717      */
718     @Nullable
719     public static AndroidXmlEditor getXmlEditor(@NonNull IEditorPart part) {
720         if (part instanceof AndroidXmlEditor) {
721             return (AndroidXmlEditor) part;
722         } else if (part instanceof GraphicalEditorPart) {
723             ((GraphicalEditorPart) part).getEditorDelegate().getEditor();
724         }
725 
726         return null;
727     }
728 
729     /**
730      * Sets the given tools: attribute in the given XML editor document, adding
731      * the tools name space declaration if necessary, formatting the affected
732      * document region, and optionally comma-appending to an existing value and
733      * optionally opening and revealing the attribute.
734      *
735      * @param editor the associated editor
736      * @param element the associated element
737      * @param description the description of the attribute (shown in the undo
738      *            event)
739      * @param name the name of the attribute
740      * @param value the attribute value
741      * @param reveal if true, open the editor and select the given attribute
742      *            node
743      * @param appendValue if true, add this value as a comma separated value to
744      *            the existing attribute value, if any
745      */
746     public static void setToolsAttribute(
747             @NonNull final AndroidXmlEditor editor,
748             @NonNull final Element element,
749             @NonNull final String description,
750             @NonNull final String name,
751             @Nullable final String value,
752             final boolean reveal,
753             final boolean appendValue) {
754         editor.wrapUndoEditXmlModel(description, new Runnable() {
755             @Override
756             public void run() {
757                 String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI, null, true);
758                 if (prefix == null) {
759                     // Add in new prefix...
760                     prefix = XmlUtils.lookupNamespacePrefix(element,
761                             TOOLS_URI, TOOLS_PREFIX, true /*create*/);
762                     if (value != null) {
763                         // ...and ensure that the header is formatted such that
764                         // the XML namespace declaration is placed in the right
765                         // position and wrapping is applied etc.
766                         editor.scheduleNodeReformat(editor.getUiRootNode(),
767                                 true /*attributesOnly*/);
768                     }
769                 }
770 
771                 String v = value;
772                 if (appendValue && v != null) {
773                     String prev = element.getAttributeNS(TOOLS_URI, name);
774                     if (prev.length() > 0) {
775                         v = prev + ',' + value;
776                     }
777                 }
778 
779                 // Use the non-namespace form of set attribute since we can't
780                 // reference the namespace until the model has been reloaded
781                 if (v != null) {
782                     element.setAttribute(prefix + ':' + name, v);
783                 } else {
784                     element.removeAttribute(prefix + ':' + name);
785                 }
786 
787                 UiElementNode rootUiNode = editor.getUiRootNode();
788                 if (rootUiNode != null && v != null) {
789                     final UiElementNode uiNode = rootUiNode.findXmlNode(element);
790                     if (uiNode != null) {
791                         editor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
792 
793                         if (reveal) {
794                             // Update editor selection after format
795                             Display display = AdtPlugin.getDisplay();
796                             if (display != null) {
797                                 display.asyncExec(new Runnable() {
798                                     @Override
799                                     public void run() {
800                                         Node xmlNode = uiNode.getXmlNode();
801                                         Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
802                                                 TOOLS_URI, name);
803                                         if (attribute instanceof IndexedRegion) {
804                                             IndexedRegion region = (IndexedRegion) attribute;
805                                             editor.getStructuredTextEditor().selectAndReveal(
806                                                     region.getStartOffset(), region.getLength());
807                                         }
808                                     }
809                                 });
810                             }
811                         }
812                     }
813                 }
814             }
815         });
816     }
817 
818     /**
819      * Returns a string label for the given target, of the form
820      * "API 16: Android 4.1 (Jelly Bean)".
821      *
822      * @param target the target to generate a string from
823      * @return a suitable display string
824      */
825     @NonNull
826     public static String getTargetLabel(@NonNull IAndroidTarget target) {
827         if (target.isPlatform()) {
828             AndroidVersion version = target.getVersion();
829             String codename = target.getProperty(PkgProps.PLATFORM_CODENAME);
830             String release = target.getProperty("ro.build.version.release"); //$NON-NLS-1$
831             if (codename != null) {
832                 return String.format("API %1$d: Android %2$s (%3$s)",
833                         version.getApiLevel(),
834                         release,
835                         codename);
836             }
837             return String.format("API %1$d: Android %2$s", version.getApiLevel(),
838                     release);
839         }
840 
841         return String.format("%1$s (API %2$s)", target.getFullName(),
842                 target.getVersion().getApiString());
843     }
844 
845     /**
846      * Sets the given tools: attribute in the given XML editor document, adding
847      * the tools name space declaration if necessary, and optionally
848      * comma-appending to an existing value.
849      *
850      * @param file the file associated with the element
851      * @param element the associated element
852      * @param description the description of the attribute (shown in the undo
853      *            event)
854      * @param name the name of the attribute
855      * @param value the attribute value
856      * @param appendValue if true, add this value as a comma separated value to
857      *            the existing attribute value, if any
858      */
859     public static void setToolsAttribute(
860             @NonNull final IFile file,
861             @NonNull final Element element,
862             @NonNull final String description,
863             @NonNull final String name,
864             @Nullable final String value,
865             final boolean appendValue) {
866         IModelManager modelManager = StructuredModelManager.getModelManager();
867         if (modelManager == null) {
868             return;
869         }
870 
871         try {
872             IStructuredModel model = null;
873             if (model == null) {
874                 model = modelManager.getModelForEdit(file);
875             }
876             if (model != null) {
877                 try {
878                     model.aboutToChangeModel();
879                     if (model instanceof IDOMModel) {
880                         IDOMModel domModel = (IDOMModel) model;
881                         Document doc = domModel.getDocument();
882                         if (doc != null && element.getOwnerDocument() == doc) {
883                             String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI,
884                                     null, true);
885                             if (prefix == null) {
886                                 // Add in new prefix...
887                                 prefix = XmlUtils.lookupNamespacePrefix(element,
888                                         TOOLS_URI, TOOLS_PREFIX, true);
889                             }
890 
891                             String v = value;
892                             if (appendValue && v != null) {
893                                 String prev = element.getAttributeNS(TOOLS_URI, name);
894                                 if (prev.length() > 0) {
895                                     v = prev + ',' + value;
896                                 }
897                             }
898 
899                             // Use the non-namespace form of set attribute since we can't
900                             // reference the namespace until the model has been reloaded
901                             if (v != null) {
902                                 element.setAttribute(prefix + ':' + name, v);
903                             } else {
904                                 element.removeAttribute(prefix + ':' + name);
905                             }
906                         }
907                     }
908                 } finally {
909                     model.changedModel();
910                     String updated = model.getStructuredDocument().get();
911                     model.releaseFromEdit();
912                     model.save(file);
913 
914                     // Must also force a save on disk since the above model.save(file) often
915                     // (always?) has no effect.
916                     ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
917                     NullProgressMonitor monitor = new NullProgressMonitor();
918                     IPath path = file.getFullPath();
919                     manager.connect(path, LocationKind.IFILE, monitor);
920                     try {
921                         ITextFileBuffer buffer = manager.getTextFileBuffer(path,
922                                 LocationKind.IFILE);
923                         IDocument currentDocument = buffer.getDocument();
924                         currentDocument.set(updated);
925                         buffer.commit(monitor, true);
926                     } finally {
927                         manager.disconnect(path, LocationKind.IFILE,  monitor);
928                     }
929                 }
930             }
931         } catch (Exception e) {
932             AdtPlugin.log(e, null);
933         }
934     }
935 
936     /**
937      * Returns the Android version and code name of the given API level
938      *
939      * @param api the api level
940      * @return a suitable version display name
941      */
942     public static String getAndroidName(int api) {
943         // See http://source.android.com/source/build-numbers.html
944         switch (api) {
945             case 1:  return "API 1: Android 1.0";
946             case 2:  return "API 2: Android 1.1";
947             case 3:  return "API 3: Android 1.5 (Cupcake)";
948             case 4:  return "API 4: Android 1.6 (Donut)";
949             case 5:  return "API 5: Android 2.0 (Eclair)";
950             case 6:  return "API 6: Android 2.0.1 (Eclair)";
951             case 7:  return "API 7: Android 2.1 (Eclair)";
952             case 8:  return "API 8: Android 2.2 (Froyo)";
953             case 9:  return "API 9: Android 2.3 (Gingerbread)";
954             case 10: return "API 10: Android 2.3.3 (Gingerbread)";
955             case 11: return "API 11: Android 3.0 (Honeycomb)";
956             case 12: return "API 12: Android 3.1 (Honeycomb)";
957             case 13: return "API 13: Android 3.2 (Honeycomb)";
958             case 14: return "API 14: Android 4.0 (IceCreamSandwich)";
959             case 15: return "API 15: Android 4.0.3 (IceCreamSandwich)";
960             case 16: return "API 16: Android 4.1 (Jelly Bean)";
961             case 17: return "API 17: Android 4.2 (Jelly Bean)";
962             // If you add more versions here, also update LintUtils#getBuildCodes and
963             // SdkConstants#HIGHEST_KNOWN_API
964 
965             default: {
966                 // Consult SDK manager to see if we know any more (later) names,
967                 // installed by user
968                 Sdk sdk = Sdk.getCurrent();
969                 if (sdk != null) {
970                     for (IAndroidTarget target : sdk.getTargets()) {
971                         if (target.isPlatform()) {
972                             AndroidVersion version = target.getVersion();
973                             if (version.getApiLevel() == api) {
974                                 return getTargetLabel(target);
975                             }
976                         }
977                     }
978                 }
979 
980                 return "API " + api;
981             }
982         }
983     }
984 
985     /**
986      * Returns the highest known API level to this version of ADT. The
987      * {@link #getAndroidName(int)} method will return real names up to and
988      * including this number.
989      *
990      * @return the highest known API number
991      */
992     public static int getHighestKnownApiLevel() {
993         return SdkVersionInfo.HIGHEST_KNOWN_API;
994     }
995 
996     /**
997      * Returns a list of known API names
998      *
999      * @return a list of string API names, starting from 1 and up through the
1000      *         maximum known versions (with no gaps)
1001      */
1002     public static String[] getKnownVersions() {
1003         int max = getHighestKnownApiLevel();
1004         Sdk sdk = Sdk.getCurrent();
1005         if (sdk != null) {
1006             for (IAndroidTarget target : sdk.getTargets()) {
1007                 if (target.isPlatform()) {
1008                     AndroidVersion version = target.getVersion();
1009                     if (!version.isPreview()) {
1010                         max = Math.max(max, version.getApiLevel());
1011                     }
1012                 }
1013             }
1014         }
1015 
1016         String[] versions = new String[max];
1017         for (int api = 1; api <= max; api++) {
1018             versions[api-1] = getAndroidName(api);
1019         }
1020 
1021         return versions;
1022     }
1023 
1024     /**
1025      * Returns the Android project(s) that are selected or active, if any. This
1026      * considers the selection, the active editor, etc.
1027      *
1028      * @param selection the current selection
1029      * @return a list of projects, possibly empty (but never null)
1030      */
1031     @NonNull
1032     public static List<IProject> getSelectedProjects(@Nullable ISelection selection) {
1033         List<IProject> projects = new ArrayList<IProject>();
1034 
1035         if (selection instanceof IStructuredSelection) {
1036             IStructuredSelection structuredSelection = (IStructuredSelection) selection;
1037             // get the unique selected item.
1038             Iterator<?> iterator = structuredSelection.iterator();
1039             while (iterator.hasNext()) {
1040                 Object element = iterator.next();
1041 
1042                 // First look up the resource (since some adaptables
1043                 // provide an IResource but not an IProject, and we can
1044                 // always go from IResource to IProject)
1045                 IResource resource = null;
1046                 if (element instanceof IResource) { // may include IProject
1047                    resource = (IResource) element;
1048                 } else if (element instanceof IAdaptable) {
1049                     IAdaptable adaptable = (IAdaptable)element;
1050                     Object adapter = adaptable.getAdapter(IResource.class);
1051                     resource = (IResource) adapter;
1052                 }
1053 
1054                 // get the project object from it.
1055                 IProject project = null;
1056                 if (resource != null) {
1057                     project = resource.getProject();
1058                 } else if (element instanceof IAdaptable) {
1059                     project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
1060                 }
1061 
1062                 if (project != null && !projects.contains(project)) {
1063                     projects.add(project);
1064                 }
1065             }
1066         }
1067 
1068         if (projects.isEmpty()) {
1069             // Try to look at the active editor instead
1070             IFile file = AdtUtils.getActiveFile();
1071             if (file != null) {
1072                 projects.add(file.getProject());
1073             }
1074         }
1075 
1076         if (projects.isEmpty()) {
1077             // If we didn't find a default project based on the selection, check how many
1078             // open Android projects we can find in the current workspace. If there's only
1079             // one, we'll just select it by default.
1080             IJavaProject[] open = AdtUtils.getOpenAndroidProjects();
1081             for (IJavaProject project : open) {
1082                 projects.add(project.getProject());
1083             }
1084             return projects;
1085         } else {
1086             // Make sure all the projects are Android projects
1087             List<IProject> androidProjects = new ArrayList<IProject>(projects.size());
1088             for (IProject project : projects) {
1089                 if (BaseProjectHelper.isAndroidProject(project)) {
1090                     androidProjects.add(project);
1091                 }
1092             }
1093             return androidProjects;
1094         }
1095     }
1096 
1097     private static Boolean sEclipse4;
1098 
1099     /**
1100      * Returns true if the running Eclipse is version 4.x or later
1101      *
1102      * @return true if the current Eclipse version is 4.x or later, false
1103      *         otherwise
1104      */
1105     public static boolean isEclipse4() {
1106         if (sEclipse4 == null) {
1107             sEclipse4 = Platform.getBundle("org.eclipse.e4.ui.model.workbench") != null; //$NON-NLS-1$
1108         }
1109 
1110         return sEclipse4;
1111     }
1112 
1113     /**
1114      * Reads the contents of an {@link IFile} and return it as a byte array
1115      *
1116      * @param file the file to be read
1117      * @return the String read from the file, or null if there was an error
1118      */
1119     @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet
1120     @Nullable
1121     public static byte[] readData(@NonNull IFile file) {
1122         InputStream contents = null;
1123         try {
1124             contents = file.getContents();
1125             return ByteStreams.toByteArray(contents);
1126         } catch (Exception e) {
1127             // Pass -- just return null
1128         } finally {
1129             Closeables.closeQuietly(contents);
1130         }
1131 
1132         return null;
1133     }
1134 
1135     /**
1136      * Ensure that a given folder (and all its parents) are created. This implements
1137      * the equivalent of {@link File#mkdirs()} for {@link IContainer} folders.
1138      *
1139      * @param container the container to ensure exists
1140      * @throws CoreException if an error occurs
1141      */
1142     public static void ensureExists(@Nullable IContainer container) throws CoreException {
1143         if (container == null || container.exists()) {
1144             return;
1145         }
1146         IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
1147         IFolder folder = root.getFolder(container.getFullPath());
1148         ensureExists(folder);
1149     }
1150 
1151     private static void ensureExists(IFolder folder) throws CoreException {
1152         if (folder != null && !folder.exists()) {
1153             IContainer parent = folder.getParent();
1154             if (parent instanceof IFolder) {
1155                 ensureExists((IFolder) parent);
1156             }
1157             folder.create(false, false, null);
1158         }
1159     }
1160 
1161     /**
1162      * Format the given floating value into an XML string, omitting decimals if
1163      * 0
1164      *
1165      * @param value the value to be formatted
1166      * @return the corresponding XML string for the value
1167      */
1168     public static String formatFloatAttribute(float value) {
1169         if (value != (int) value) {
1170             // Run String.format without a locale, because we don't want locale-specific
1171             // conversions here like separating the decimal part with a comma instead of a dot!
1172             return String.format((Locale) null, "%.2f", value); //$NON-NLS-1$
1173         } else {
1174             return Integer.toString((int) value);
1175         }
1176     }
1177 
1178     /**
1179      * Creates all the directories required for the given path.
1180      *
1181      * @param wsPath the path to create all the parent directories for
1182      * @return true if all the parent directories were created
1183      */
1184     public static boolean createWsParentDirectory(IContainer wsPath) {
1185         if (wsPath.getType() == IResource.FOLDER) {
1186             if (wsPath.exists()) {
1187                 return true;
1188             }
1189 
1190             IFolder folder = (IFolder) wsPath;
1191             try {
1192                 if (createWsParentDirectory(wsPath.getParent())) {
1193                     folder.create(true /* force */, true /* local */, null /* monitor */);
1194                     return true;
1195                 }
1196             } catch (CoreException e) {
1197                 e.printStackTrace();
1198             }
1199         }
1200 
1201         return false;
1202     }
1203 
1204     /**
1205      * Lists the files of the given directory and returns them as an array which
1206      * is never null. This simplifies processing file listings from for each
1207      * loops since {@link File#listFiles} can return null. This method simply
1208      * wraps it and makes sure it returns an empty array instead if necessary.
1209      *
1210      * @param dir the directory to list
1211      * @return the children, or empty if it has no children, is not a directory,
1212      *         etc.
1213      */
1214     @NonNull
1215     public static File[] listFiles(File dir) {
1216         File[] files = dir.listFiles();
1217         if (files != null) {
1218             return files;
1219         } else {
1220             return new File[0];
1221         }
1222     }
1223 
1224     /**
1225      * Closes all open editors that are showing a file for the given project. This method
1226      * should be called when a project is closed or deleted.
1227      * <p>
1228      * This method can be called from any thread, but if it is not called on the GUI thread
1229      * the editor will be closed asynchronously.
1230      *
1231      * @param project the project to close all editors for
1232      * @param save whether unsaved editors should be saved first
1233      */
1234     public static void closeEditors(@NonNull final IProject project, final boolean save) {
1235         final Display display = AdtPlugin.getDisplay();
1236         if (display == null || display.isDisposed()) {
1237             return;
1238         }
1239         if (display.getThread() != Thread.currentThread()) {
1240             display.asyncExec(new Runnable() {
1241                 @Override
1242                 public void run() {
1243                     closeEditors(project, save);
1244                 }
1245             });
1246             return;
1247         }
1248 
1249         // Close editors for removed files
1250         IWorkbench workbench = PlatformUI.getWorkbench();
1251         for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
1252             for (IWorkbenchPage page : window.getPages()) {
1253                 List<IEditorReference> matching = null;
1254                 for (IEditorReference ref : page.getEditorReferences()) {
1255                     boolean close = false;
1256                     try {
1257                         IEditorInput input = ref.getEditorInput();
1258                         if (input instanceof IFileEditorInput) {
1259                             IFileEditorInput fileInput = (IFileEditorInput) input;
1260                             if (project.equals(fileInput.getFile().getProject())) {
1261                                 close = true;
1262                             }
1263                         }
1264                     } catch (PartInitException ex) {
1265                         close = true;
1266                     }
1267                     if (close) {
1268                         if (matching == null) {
1269                             matching = new ArrayList<IEditorReference>(2);
1270                         }
1271                         matching.add(ref);
1272                     }
1273                 }
1274                 if (matching != null) {
1275                     IEditorReference[] refs = new IEditorReference[matching.size()];
1276                     page.closeEditors(matching.toArray(refs), save);
1277                 }
1278             }
1279         }
1280     }
1281 
1282     /**
1283      * Closes all open editors for the given file. Note that a file can be open in
1284      * more than one editor, for example by using Open With on the file to choose different
1285      * editors.
1286      * <p>
1287      * This method can be called from any thread, but if it is not called on the GUI thread
1288      * the editor will be closed asynchronously.
1289      *
1290      * @param file the file whose editors should be closed.
1291      * @param save whether unsaved editors should be saved first
1292      */
1293     public static void closeEditors(@NonNull final IFile file, final boolean save) {
1294         final Display display = AdtPlugin.getDisplay();
1295         if (display == null || display.isDisposed()) {
1296             return;
1297         }
1298         if (display.getThread() != Thread.currentThread()) {
1299             display.asyncExec(new Runnable() {
1300                 @Override
1301                 public void run() {
1302                     closeEditors(file, save);
1303                 }
1304             });
1305             return;
1306         }
1307 
1308         // Close editors for removed files
1309         IWorkbench workbench = PlatformUI.getWorkbench();
1310         for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
1311             for (IWorkbenchPage page : window.getPages()) {
1312                 List<IEditorReference> matching = null;
1313                 for (IEditorReference ref : page.getEditorReferences()) {
1314                     boolean close = false;
1315                     try {
1316                         IEditorInput input = ref.getEditorInput();
1317                         if (input instanceof IFileEditorInput) {
1318                             IFileEditorInput fileInput = (IFileEditorInput) input;
1319                             if (file.equals(fileInput.getFile())) {
1320                                 close = true;
1321                             }
1322                         }
1323                     } catch (PartInitException ex) {
1324                         close = true;
1325                     }
1326                     if (close) {
1327                         // Found
1328                         if (matching == null) {
1329                             matching = new ArrayList<IEditorReference>(2);
1330                         }
1331                         matching.add(ref);
1332                         // We don't break here in case the file is
1333                         // opened multiple times with different editors.
1334                     }
1335                 }
1336                 if (matching != null) {
1337                     IEditorReference[] refs = new IEditorReference[matching.size()];
1338                     page.closeEditors(matching.toArray(refs), save);
1339                 }
1340             }
1341         }
1342     }
1343 
1344     /**
1345      * Returns the offset region of the given 0-based line number in the given
1346      * file
1347      *
1348      * @param file the file to look up the line number in
1349      * @param line the line number (0-based, meaning that the first line is line
1350      *            0)
1351      * @return the corresponding offset range, or null
1352      */
1353     @Nullable
1354     public static IRegion getRegionOfLine(@NonNull IFile file, int line) {
1355         IDocumentProvider provider = new TextFileDocumentProvider();
1356         try {
1357             provider.connect(file);
1358             IDocument document = provider.getDocument(file);
1359             if (document != null) {
1360                 return document.getLineInformation(line);
1361             }
1362         } catch (Exception e) {
1363             AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
1364         } finally {
1365             provider.disconnect(file);
1366         }
1367 
1368         return null;
1369     }
1370 
1371     /**
1372      * Returns all resource variations for the given file
1373      *
1374      * @param file resource file, which should be an XML file in one of the
1375      *            various resource folders, e.g. res/layout, res/values-xlarge, etc.
1376      * @param includeSelf if true, include the file itself in the list,
1377      *            otherwise exclude it
1378      * @return a list of all the resource variations
1379      */
1380     public static List<IFile> getResourceVariations(@Nullable IFile file, boolean includeSelf) {
1381         if (file == null) {
1382             return Collections.emptyList();
1383         }
1384 
1385         // Compute the set of layout files defining this layout resource
1386         List<IFile> variations = new ArrayList<IFile>();
1387         String name = file.getName();
1388         IContainer parent = file.getParent();
1389         if (parent != null) {
1390             IContainer resFolder = parent.getParent();
1391             if (resFolder != null) {
1392                 String parentName = parent.getName();
1393                 String prefix = parentName;
1394                 int qualifiers = prefix.indexOf('-');
1395 
1396                 if (qualifiers != -1) {
1397                     parentName = prefix.substring(0, qualifiers);
1398                     prefix = prefix.substring(0, qualifiers + 1);
1399                 } else {
1400                     prefix = prefix + '-';
1401                 }
1402                 try {
1403                     for (IResource resource : resFolder.members()) {
1404                         String n = resource.getName();
1405                         if ((n.startsWith(prefix) || n.equals(parentName))
1406                                 && resource instanceof IContainer) {
1407                             IContainer layoutFolder = (IContainer) resource;
1408                             IResource r = layoutFolder.findMember(name);
1409                             if (r instanceof IFile) {
1410                                 IFile variation = (IFile) r;
1411                                 if (!includeSelf && file.equals(variation)) {
1412                                     continue;
1413                                 }
1414                                 variations.add(variation);
1415                             }
1416                         }
1417                     }
1418                 } catch (CoreException e) {
1419                     AdtPlugin.log(e, null);
1420                 }
1421             }
1422         }
1423 
1424         return variations;
1425     }
1426 
1427     /**
1428      * Returns whether the current thread is the UI thread
1429      *
1430      * @return true if the current thread is the UI thread
1431      */
1432     public static boolean isUiThread() {
1433         return AdtPlugin.getDisplay() != null
1434                 && AdtPlugin.getDisplay().getThread() == Thread.currentThread();
1435     }
1436 
1437     /**
1438      * Replaces any {@code \\uNNNN} references in the given string with the corresponding
1439      * unicode characters.
1440      *
1441      * @param s the string to perform replacements in
1442      * @return the string with unicode escapes replaced with actual characters
1443      */
1444     @NonNull
1445     public static String replaceUnicodeEscapes(@NonNull String s) {
1446         // Handle unicode escapes
1447         if (s.indexOf("\\u") != -1) { //$NON-NLS-1$
1448             StringBuilder sb = new StringBuilder(s.length());
1449             for (int i = 0, n = s.length(); i < n; i++) {
1450                 char c = s.charAt(i);
1451                 if (c == '\\' && i < n - 1) {
1452                     char next = s.charAt(i + 1);
1453                     if (next == 'u' && i < n - 5) { // case sensitive
1454                         String hex = s.substring(i + 2, i + 6);
1455                         try {
1456                             int unicodeValue = Integer.parseInt(hex, 16);
1457                             sb.append((char) unicodeValue);
1458                             i += 5;
1459                             continue;
1460                         } catch (NumberFormatException nufe) {
1461                             // Invalid escape: Just proceed to literally transcribe it
1462                             sb.append(c);
1463                         }
1464                     } else {
1465                         sb.append(c);
1466                         sb.append(next);
1467                         i++;
1468                         continue;
1469                     }
1470                 } else {
1471                     sb.append(c);
1472                 }
1473             }
1474             s = sb.toString();
1475         }
1476 
1477         return s;
1478     }
1479 
1480     /**
1481      * Looks up the {@link ResourceFolderType} corresponding to a given
1482      * {@link ResourceType}: the folder where those resources can be found.
1483      * <p>
1484      * Note that {@link ResourceType#ID} is a special case: it can not just
1485      * be defined in {@link ResourceFolderType#VALUES}, but it can also be
1486      * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
1487      * {@link ResourceFolderType#MENU} folders.
1488      *
1489      * @param type the resource type
1490      * @return the corresponding resource folder type
1491      */
1492     @NonNull
1493     public static ResourceFolderType getFolderTypeFor(@NonNull ResourceType type) {
1494         switch (type) {
1495             case ANIM:
1496                 return ResourceFolderType.ANIM;
1497             case ANIMATOR:
1498                 return ResourceFolderType.ANIMATOR;
1499             case ARRAY:
1500                 return ResourceFolderType.VALUES;
1501             case COLOR:
1502                 return ResourceFolderType.COLOR;
1503             case DRAWABLE:
1504                 return ResourceFolderType.DRAWABLE;
1505             case INTERPOLATOR:
1506                 return ResourceFolderType.INTERPOLATOR;
1507             case LAYOUT:
1508                 return ResourceFolderType.LAYOUT;
1509             case MENU:
1510                 return ResourceFolderType.MENU;
1511             case MIPMAP:
1512                 return ResourceFolderType.MIPMAP;
1513             case RAW:
1514                 return ResourceFolderType.RAW;
1515             case XML:
1516                 return ResourceFolderType.XML;
1517             case ATTR:
1518             case BOOL:
1519             case DECLARE_STYLEABLE:
1520             case DIMEN:
1521             case FRACTION:
1522             case ID:
1523             case INTEGER:
1524             case PLURALS:
1525             case PUBLIC:
1526             case STRING:
1527             case STYLE:
1528             case STYLEABLE:
1529                 return ResourceFolderType.VALUES;
1530             default:
1531                 assert false : type;
1532             return ResourceFolderType.VALUES;
1533 
1534         }
1535     }
1536 
1537     /**
1538      * Looks up the {@link ResourceType} defined in a given {@link ResourceFolderType}.
1539      * <p>
1540      * Note that for {@link ResourceFolderType#VALUES} there are many, many
1541      * different types of resources that can be defined, so this method returns
1542      * {@code null} for that scenario.
1543      * <p>
1544      * Note also that {@link ResourceType#ID} is a special case: it can not just
1545      * be defined in {@link ResourceFolderType#VALUES}, but it can also be
1546      * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
1547      * {@link ResourceFolderType#MENU} folders.
1548      *
1549      * @param folderType the resource folder type
1550      * @return the corresponding resource type, or null if {@code folderType} is
1551      *         {@link ResourceFolderType#VALUES}
1552      */
1553     @Nullable
1554     public static ResourceType getResourceTypeFor(@NonNull ResourceFolderType folderType) {
1555         switch (folderType) {
1556             case ANIM:
1557                 return ResourceType.ANIM;
1558             case ANIMATOR:
1559                 return ResourceType.ANIMATOR;
1560             case COLOR:
1561                 return ResourceType.COLOR;
1562             case DRAWABLE:
1563                 return ResourceType.DRAWABLE;
1564             case INTERPOLATOR:
1565                 return ResourceType.INTERPOLATOR;
1566             case LAYOUT:
1567                 return ResourceType.LAYOUT;
1568             case MENU:
1569                 return ResourceType.MENU;
1570             case MIPMAP:
1571                 return ResourceType.MIPMAP;
1572             case RAW:
1573                 return ResourceType.RAW;
1574             case XML:
1575                 return ResourceType.XML;
1576             case VALUES:
1577                 return null;
1578             default:
1579                 assert false : folderType;
1580                 return null;
1581         }
1582     }
1583 }
1584