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