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.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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    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
    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
    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      */
    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
    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
    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      */
    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
    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
    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      */
    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      */
    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      */
    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      */
    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
    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
    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