aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java1561
1 files changed, 1561 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
new file mode 100644
index 000000000..93cd2da1e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -0,0 +1,1561 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt;
+
+import static com.android.SdkConstants.TOOLS_PREFIX;
+import static com.android.SdkConstants.TOOLS_URI;
+import static org.eclipse.ui.IWorkbenchPage.MATCH_INPUT;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.sdklib.SdkVersionInfo;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.repository.PkgProps;
+import com.android.utils.XmlUtils;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+
+import org.eclipse.core.filebuffers.FileBuffers;
+import org.eclipse.core.filebuffers.ITextFileBuffer;
+import org.eclipse.core.filebuffers.ITextFileBufferManager;
+import org.eclipse.core.filebuffers.LocationKind;
+import org.eclipse.core.filesystem.URIUtil;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IURIEditorInput;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.part.FileEditorInput;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+
+
+/** Utility methods for ADT */
+@SuppressWarnings("restriction") // WST API
+public class AdtUtils {
+ /**
+ * Creates a Java class name out of the given string, if possible. For
+ * example, "My Project" becomes "MyProject", "hello" becomes "Hello",
+ * "Java's" becomes "Java", and so on.
+ *
+ * @param string the string to be massaged into a Java class
+ * @return the string as a Java class, or null if a class name could not be
+ * extracted
+ */
+ @Nullable
+ public static String extractClassName(@NonNull String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ int n = string.length();
+
+ int i = 0;
+ for (; i < n; i++) {
+ char c = Character.toUpperCase(string.charAt(i));
+ if (Character.isJavaIdentifierStart(c)) {
+ sb.append(c);
+ i++;
+ break;
+ }
+ }
+ if (sb.length() > 0) {
+ for (; i < n; i++) {
+ char c = string.charAt(i);
+ if (Character.isJavaIdentifierPart(c)) {
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ return null;
+ }
+
+ /**
+ * Strips off the last file extension from the given filename, e.g.
+ * "foo.backup.diff" will be turned into "foo.backup".
+ * <p>
+ * Note that dot files (e.g. ".profile") will be left alone.
+ *
+ * @param filename the filename to be stripped
+ * @return the filename without the last file extension.
+ */
+ public static String stripLastExtension(String filename) {
+ int dotIndex = filename.lastIndexOf('.');
+ if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently
+ return filename.substring(0, dotIndex);
+ } else {
+ return filename;
+ }
+ }
+
+ /**
+ * Strips off all extensions from the given filename, e.g. "foo.9.png" will
+ * be turned into "foo".
+ * <p>
+ * Note that dot files (e.g. ".profile") will be left alone.
+ *
+ * @param filename the filename to be stripped
+ * @return the filename without any file extensions
+ */
+ public static String stripAllExtensions(String filename) {
+ int dotIndex = filename.indexOf('.');
+ if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently
+ return filename.substring(0, dotIndex);
+ } else {
+ return filename;
+ }
+ }
+
+ /**
+ * Strips the given suffix from the given string, provided that the string ends with
+ * the suffix.
+ *
+ * @param string the full string to strip from
+ * @param suffix the suffix to strip out
+ * @return the string without the suffix at the end
+ */
+ public static String stripSuffix(@NonNull String string, @NonNull String suffix) {
+ if (string.endsWith(suffix)) {
+ return string.substring(0, string.length() - suffix.length());
+ }
+
+ return string;
+ }
+
+ /**
+ * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z].
+ * Returns the string unmodified if the first character is not [a-z].
+ *
+ * @param str The string to capitalize.
+ * @return The capitalized string
+ */
+ public static String capitalize(String str) {
+ if (str == null || str.length() < 1 || Character.isUpperCase(str.charAt(0))) {
+ return str;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(Character.toUpperCase(str.charAt(0)));
+ sb.append(str.substring(1));
+ return sb.toString();
+ }
+
+ /**
+ * Converts a CamelCase word into an underlined_word
+ *
+ * @param string the CamelCase version of the word
+ * @return the underlined version of the word
+ */
+ public static String camelCaseToUnderlines(String string) {
+ if (string.isEmpty()) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder(2 * string.length());
+ int n = string.length();
+ boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
+ for (int i = 0; i < n; i++) {
+ char c = string.charAt(i);
+ boolean isUpperCase = Character.isUpperCase(c);
+ if (isUpperCase && !lastWasUpperCase) {
+ sb.append('_');
+ }
+ lastWasUpperCase = isUpperCase;
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Converts an underlined_word into a CamelCase word
+ *
+ * @param string the underlined word to convert
+ * @return the CamelCase version of the word
+ */
+ public static String underlinesToCamelCase(String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ int n = string.length();
+
+ int i = 0;
+ boolean upcaseNext = true;
+ for (; i < n; i++) {
+ char c = string.charAt(i);
+ if (c == '_') {
+ upcaseNext = true;
+ } else {
+ if (upcaseNext) {
+ c = Character.toUpperCase(c);
+ }
+ upcaseNext = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the current editor (the currently visible and active editor), or null if
+ * not found
+ *
+ * @return the current editor, or null
+ */
+ public static IEditorPart getActiveEditor() {
+ IWorkbenchWindow window = getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ return page.getActiveEditor();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the current active workbench, or null if not found
+ *
+ * @return the current window, or null
+ */
+ @Nullable
+ public static IWorkbenchWindow getActiveWorkbenchWindow() {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+ if (window == null) {
+ IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
+ if (windows.length > 0) {
+ window = windows[0];
+ }
+ }
+
+ return window;
+ }
+
+ /**
+ * Returns the current active workbench page, or null if not found
+ *
+ * @return the current page, or null
+ */
+ @Nullable
+ public static IWorkbenchPage getActiveWorkbenchPage() {
+ IWorkbenchWindow window = getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page == null) {
+ IWorkbenchPage[] pages = window.getPages();
+ if (pages.length > 0) {
+ page = pages[0];
+ }
+ }
+
+ return page;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the current active workbench part, or null if not found
+ *
+ * @return the current active workbench part, or null
+ */
+ @Nullable
+ public static IWorkbenchPart getActivePart() {
+ IWorkbenchWindow window = getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage activePage = window.getActivePage();
+ if (activePage != null) {
+ return activePage.getActivePart();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current text editor (the currently visible and active editor), or null
+ * if not found.
+ *
+ * @return the current text editor, or null
+ */
+ public static ITextEditor getActiveTextEditor() {
+ IEditorPart editor = getActiveEditor();
+ if (editor != null) {
+ if (editor instanceof ITextEditor) {
+ return (ITextEditor) editor;
+ } else {
+ return (ITextEditor) editor.getAdapter(ITextEditor.class);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Looks through the open editors and returns the editors that have the
+ * given file as input.
+ *
+ * @param file the file to search for
+ * @param restore whether editors should be restored (if they have an open
+ * tab, but the editor hasn't been restored since the most recent
+ * IDE start yet
+ * @return a collection of editors
+ */
+ @NonNull
+ public static Collection<IEditorPart> findEditorsFor(@NonNull IFile file, boolean restore) {
+ FileEditorInput input = new FileEditorInput(file);
+ List<IEditorPart> result = null;
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
+ for (IWorkbenchWindow window : windows) {
+ IWorkbenchPage[] pages = window.getPages();
+ for (IWorkbenchPage page : pages) {
+ IEditorReference[] editors = page.findEditors(input, null, MATCH_INPUT);
+ if (editors != null) {
+ for (IEditorReference reference : editors) {
+ IEditorPart editor = reference.getEditor(restore);
+ if (editor != null) {
+ if (result == null) {
+ result = new ArrayList<IEditorPart>();
+ }
+ result.add(editor);
+ }
+ }
+ }
+ }
+ }
+
+ if (result == null) {
+ return Collections.emptyList();
+ }
+
+ return result;
+ }
+
+ /**
+ * Attempts to convert the given {@link URL} into a {@link File}.
+ *
+ * @param url the {@link URL} to be converted
+ * @return the corresponding {@link File}, which may not exist
+ */
+ @NonNull
+ public static File getFile(@NonNull URL url) {
+ try {
+ // First try URL.toURI(): this will work for URLs that contain %20 for spaces etc.
+ // Unfortunately, it *doesn't* work for "broken" URLs where the URL contains
+ // spaces, which is often the case.
+ return new File(url.toURI());
+ } catch (URISyntaxException e) {
+ // ...so as a fallback, go to the old url.getPath() method, which handles space paths.
+ return new File(url.getPath());
+ }
+ }
+
+ /**
+ * Returns the file for the current editor, if any.
+ *
+ * @return the file for the current editor, or null if none
+ */
+ public static IFile getActiveFile() {
+ IEditorPart editor = getActiveEditor();
+ if (editor != null) {
+ IEditorInput input = editor.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ IFileEditorInput fileInput = (IFileEditorInput) input;
+ return fileInput.getFile();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns an absolute path to the given resource
+ *
+ * @param resource the resource to look up a path for
+ * @return an absolute file system path to the resource
+ */
+ @NonNull
+ public static IPath getAbsolutePath(@NonNull IResource resource) {
+ IPath location = resource.getRawLocation();
+ if (location != null) {
+ return location.makeAbsolute();
+ } else {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot root = workspace.getRoot();
+ IPath workspacePath = root.getLocation();
+ return workspacePath.append(resource.getFullPath());
+ }
+ }
+
+ /**
+ * Converts a workspace-relative path to an absolute file path
+ *
+ * @param path the workspace-relative path to convert
+ * @return the corresponding absolute file in the file system
+ */
+ @NonNull
+ public static File workspacePathToFile(@NonNull IPath path) {
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IResource res = root.findMember(path);
+ if (res != null) {
+ IPath location = res.getLocation();
+ if (location != null) {
+ return location.toFile();
+ }
+ return root.getLocation().append(path).toFile();
+ }
+
+ return path.toFile();
+ }
+
+ /**
+ * Converts a {@link File} to an {@link IFile}, if possible.
+ *
+ * @param file a file to be converted
+ * @return the corresponding {@link IFile}, or null
+ */
+ public static IFile fileToIFile(File file) {
+ if (!file.isAbsolute()) {
+ file = file.getAbsoluteFile();
+ }
+
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IFile[] files = workspace.findFilesForLocationURI(file.toURI());
+ if (files.length > 0) {
+ return files[0];
+ }
+
+ IPath filePath = new Path(file.getPath());
+ return pathToIFile(filePath);
+ }
+
+ /**
+ * Converts a {@link File} to an {@link IResource}, if possible.
+ *
+ * @param file a file to be converted
+ * @return the corresponding {@link IResource}, or null
+ */
+ public static IResource fileToResource(File file) {
+ if (!file.isAbsolute()) {
+ file = file.getAbsoluteFile();
+ }
+
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+ IFile[] files = workspace.findFilesForLocationURI(file.toURI());
+ if (files.length > 0) {
+ return files[0];
+ }
+
+ IPath filePath = new Path(file.getPath());
+ return pathToResource(filePath);
+ }
+
+ /**
+ * Converts a {@link IPath} to an {@link IFile}, if possible.
+ *
+ * @param path a path to be converted
+ * @return the corresponding {@link IFile}, or null
+ */
+ public static IFile pathToIFile(IPath path) {
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+
+ IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute()));
+ if (files.length > 0) {
+ return files[0];
+ }
+
+ IPath workspacePath = workspace.getLocation();
+ if (workspacePath.isPrefixOf(path)) {
+ IPath relativePath = path.makeRelativeTo(workspacePath);
+ IResource member = workspace.findMember(relativePath);
+ if (member instanceof IFile) {
+ return (IFile) member;
+ }
+ } else if (path.isAbsolute()) {
+ return workspace.getFileForLocation(path);
+ }
+
+ return null;
+ }
+
+ /**
+ * Converts a {@link IPath} to an {@link IResource}, if possible.
+ *
+ * @param path a path to be converted
+ * @return the corresponding {@link IResource}, or null
+ */
+ public static IResource pathToResource(IPath path) {
+ IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot();
+
+ IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute()));
+ if (files.length > 0) {
+ return files[0];
+ }
+
+ IPath workspacePath = workspace.getLocation();
+ if (workspacePath.isPrefixOf(path)) {
+ IPath relativePath = path.makeRelativeTo(workspacePath);
+ return workspace.findMember(relativePath);
+ } else if (path.isAbsolute()) {
+ return workspace.getFileForLocation(path);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all markers in a file/document that fit on the same line as the given offset
+ *
+ * @param markerType the marker type
+ * @param file the file containing the markers
+ * @param document the document showing the markers
+ * @param offset the offset to be checked
+ * @return a list (possibly empty but never null) of matching markers
+ */
+ @NonNull
+ public static List<IMarker> findMarkersOnLine(
+ @NonNull String markerType,
+ @NonNull IResource file,
+ @NonNull IDocument document,
+ int offset) {
+ List<IMarker> matchingMarkers = new ArrayList<IMarker>(2);
+ try {
+ IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO);
+
+ // Look for a match on the same line as the caret.
+ IRegion lineInfo = document.getLineInformationOfOffset(offset);
+ int lineStart = lineInfo.getOffset();
+ int lineEnd = lineStart + lineInfo.getLength();
+ int offsetLine = document.getLineOfOffset(offset);
+
+
+ for (IMarker marker : markers) {
+ int start = marker.getAttribute(IMarker.CHAR_START, -1);
+ int end = marker.getAttribute(IMarker.CHAR_END, -1);
+ if (start >= lineStart && start <= lineEnd && end > start) {
+ matchingMarkers.add(marker);
+ } else if (start == -1 && end == -1) {
+ // Some markers don't set character range, they only set the line
+ int line = marker.getAttribute(IMarker.LINE_NUMBER, -1);
+ if (line == offsetLine + 1) {
+ matchingMarkers.add(marker);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return matchingMarkers;
+ }
+
+ /**
+ * Returns the available and open Android projects
+ *
+ * @return the available and open Android projects, never null
+ */
+ @NonNull
+ public static IJavaProject[] getOpenAndroidProjects() {
+ return BaseProjectHelper.getAndroidProjects(new IProjectFilter() {
+ @Override
+ public boolean accept(IProject project) {
+ return project.isAccessible();
+ }
+ });
+ }
+
+ /**
+ * Returns a unique project name, based on the given {@code base} file name
+ * possibly with a {@code conjunction} and a new number behind it to ensure
+ * that the project name is unique. For example,
+ * {@code getUniqueProjectName("project", "_")} will return
+ * {@code "project"} if that name does not already exist, and if it does, it
+ * will return {@code "project_2"}.
+ *
+ * @param base the base name to use, such as "foo"
+ * @param conjunction a string to insert between the base name and the
+ * number.
+ * @return a unique project name based on the given base and conjunction
+ */
+ public static String getUniqueProjectName(String base, String conjunction) {
+ // We're using all workspace projects here rather than just open Android project
+ // via getOpenAndroidProjects because the name cannot conflict with non-Android
+ // or closed projects either
+ IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
+ IProject[] projects = workspaceRoot.getProjects();
+
+ for (int i = 1; i < 1000; i++) {
+ String name = i == 1 ? base : base + conjunction + Integer.toString(i);
+ boolean found = false;
+ for (IProject project : projects) {
+ // Need to make case insensitive comparison, since otherwise we can hit
+ // org.eclipse.core.internal.resources.ResourceException:
+ // A resource exists with a different case: '/test'.
+ if (project.getName().equalsIgnoreCase(name)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return name;
+ }
+ }
+
+ return base;
+ }
+
+ /**
+ * Returns the name of the parent folder for the given editor input
+ *
+ * @param editorInput the editor input to check
+ * @return the parent folder, which is never null but may be ""
+ */
+ @NonNull
+ public static String getParentFolderName(@Nullable IEditorInput editorInput) {
+ if (editorInput instanceof IFileEditorInput) {
+ IFile file = ((IFileEditorInput) editorInput).getFile();
+ return file.getParent().getName();
+ }
+
+ if (editorInput instanceof IURIEditorInput) {
+ IURIEditorInput urlEditorInput = (IURIEditorInput) editorInput;
+ String path = urlEditorInput.getURI().toString();
+ int lastIndex = path.lastIndexOf('/');
+ if (lastIndex != -1) {
+ int lastLastIndex = path.lastIndexOf('/', lastIndex - 1);
+ if (lastLastIndex != -1) {
+ return path.substring(lastLastIndex + 1, lastIndex);
+ }
+ }
+ }
+
+ return "";
+ }
+
+ /**
+ * Returns the XML editor for the given editor part
+ *
+ * @param part the editor part to look up the editor for
+ * @return the editor or null if this part is not an XML editor
+ */
+ @Nullable
+ public static AndroidXmlEditor getXmlEditor(@NonNull IEditorPart part) {
+ if (part instanceof AndroidXmlEditor) {
+ return (AndroidXmlEditor) part;
+ } else if (part instanceof GraphicalEditorPart) {
+ ((GraphicalEditorPart) part).getEditorDelegate().getEditor();
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the given tools: attribute in the given XML editor document, adding
+ * the tools name space declaration if necessary, formatting the affected
+ * document region, and optionally comma-appending to an existing value and
+ * optionally opening and revealing the attribute.
+ *
+ * @param editor the associated editor
+ * @param element the associated element
+ * @param description the description of the attribute (shown in the undo
+ * event)
+ * @param name the name of the attribute
+ * @param value the attribute value
+ * @param reveal if true, open the editor and select the given attribute
+ * node
+ * @param appendValue if true, add this value as a comma separated value to
+ * the existing attribute value, if any
+ */
+ public static void setToolsAttribute(
+ @NonNull final AndroidXmlEditor editor,
+ @NonNull final Element element,
+ @NonNull final String description,
+ @NonNull final String name,
+ @Nullable final String value,
+ final boolean reveal,
+ final boolean appendValue) {
+ editor.wrapUndoEditXmlModel(description, new Runnable() {
+ @Override
+ public void run() {
+ String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI, null, true);
+ if (prefix == null) {
+ // Add in new prefix...
+ prefix = XmlUtils.lookupNamespacePrefix(element,
+ TOOLS_URI, TOOLS_PREFIX, true /*create*/);
+ if (value != null) {
+ // ...and ensure that the header is formatted such that
+ // the XML namespace declaration is placed in the right
+ // position and wrapping is applied etc.
+ editor.scheduleNodeReformat(editor.getUiRootNode(),
+ true /*attributesOnly*/);
+ }
+ }
+
+ String v = value;
+ if (appendValue && v != null) {
+ String prev = element.getAttributeNS(TOOLS_URI, name);
+ if (prev.length() > 0) {
+ v = prev + ',' + value;
+ }
+ }
+
+ // Use the non-namespace form of set attribute since we can't
+ // reference the namespace until the model has been reloaded
+ if (v != null) {
+ element.setAttribute(prefix + ':' + name, v);
+ } else {
+ element.removeAttribute(prefix + ':' + name);
+ }
+
+ UiElementNode rootUiNode = editor.getUiRootNode();
+ if (rootUiNode != null && v != null) {
+ final UiElementNode uiNode = rootUiNode.findXmlNode(element);
+ if (uiNode != null) {
+ editor.scheduleNodeReformat(uiNode, true /*attributesOnly*/);
+
+ if (reveal) {
+ // Update editor selection after format
+ Display display = AdtPlugin.getDisplay();
+ if (display != null) {
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ Node xmlNode = uiNode.getXmlNode();
+ Attr attribute = ((Element) xmlNode).getAttributeNodeNS(
+ TOOLS_URI, name);
+ if (attribute instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) attribute;
+ editor.getStructuredTextEditor().selectAndReveal(
+ region.getStartOffset(), region.getLength());
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns a string label for the given target, of the form
+ * "API 16: Android 4.1 (Jelly Bean)".
+ *
+ * @param target the target to generate a string from
+ * @return a suitable display string
+ */
+ @NonNull
+ public static String getTargetLabel(@NonNull IAndroidTarget target) {
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ String codename = target.getProperty(PkgProps.PLATFORM_CODENAME);
+ String release = target.getProperty("ro.build.version.release"); //$NON-NLS-1$
+ if (codename != null) {
+ return String.format("API %1$d: Android %2$s (%3$s)",
+ version.getApiLevel(),
+ release,
+ codename);
+ }
+ return String.format("API %1$d: Android %2$s", version.getApiLevel(),
+ release);
+ }
+
+ return String.format("%1$s (API %2$s)", target.getFullName(),
+ target.getVersion().getApiString());
+ }
+
+ /**
+ * Sets the given tools: attribute in the given XML editor document, adding
+ * the tools name space declaration if necessary, and optionally
+ * comma-appending to an existing value.
+ *
+ * @param file the file associated with the element
+ * @param element the associated element
+ * @param description the description of the attribute (shown in the undo
+ * event)
+ * @param name the name of the attribute
+ * @param value the attribute value
+ * @param appendValue if true, add this value as a comma separated value to
+ * the existing attribute value, if any
+ */
+ public static void setToolsAttribute(
+ @NonNull final IFile file,
+ @NonNull final Element element,
+ @NonNull final String description,
+ @NonNull final String name,
+ @Nullable final String value,
+ final boolean appendValue) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ if (modelManager == null) {
+ return;
+ }
+
+ try {
+ IStructuredModel model = null;
+ if (model == null) {
+ model = modelManager.getModelForEdit(file);
+ }
+ if (model != null) {
+ try {
+ model.aboutToChangeModel();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Document doc = domModel.getDocument();
+ if (doc != null && element.getOwnerDocument() == doc) {
+ String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI,
+ null, true);
+ if (prefix == null) {
+ // Add in new prefix...
+ prefix = XmlUtils.lookupNamespacePrefix(element,
+ TOOLS_URI, TOOLS_PREFIX, true);
+ }
+
+ String v = value;
+ if (appendValue && v != null) {
+ String prev = element.getAttributeNS(TOOLS_URI, name);
+ if (prev.length() > 0) {
+ v = prev + ',' + value;
+ }
+ }
+
+ // Use the non-namespace form of set attribute since we can't
+ // reference the namespace until the model has been reloaded
+ if (v != null) {
+ element.setAttribute(prefix + ':' + name, v);
+ } else {
+ element.removeAttribute(prefix + ':' + name);
+ }
+ }
+ }
+ } finally {
+ model.changedModel();
+ String updated = model.getStructuredDocument().get();
+ model.releaseFromEdit();
+ model.save(file);
+
+ // Must also force a save on disk since the above model.save(file) often
+ // (always?) has no effect.
+ ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
+ NullProgressMonitor monitor = new NullProgressMonitor();
+ IPath path = file.getFullPath();
+ manager.connect(path, LocationKind.IFILE, monitor);
+ try {
+ ITextFileBuffer buffer = manager.getTextFileBuffer(path,
+ LocationKind.IFILE);
+ IDocument currentDocument = buffer.getDocument();
+ currentDocument.set(updated);
+ buffer.commit(monitor, true);
+ } finally {
+ manager.disconnect(path, LocationKind.IFILE, monitor);
+ }
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
+ * Returns the Android version and code name of the given API level
+ *
+ * @param api the api level
+ * @return a suitable version display name
+ */
+ public static String getAndroidName(int api) {
+ if (api <= SdkVersionInfo.HIGHEST_KNOWN_API) {
+ return SdkVersionInfo.getAndroidName(api);
+ }
+
+ // Consult SDK manager to see if we know any more (later) names,
+ // installed by user
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ for (IAndroidTarget target : sdk.getTargets()) {
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (version.getApiLevel() == api) {
+ return getTargetLabel(target);
+ }
+ }
+ }
+ }
+
+ return "API " + api;
+ }
+
+ /**
+ * Returns the highest known API level to this version of ADT. The
+ * {@link #getAndroidName(int)} method will return real names up to and
+ * including this number.
+ *
+ * @return the highest known API number
+ */
+ public static int getHighestKnownApiLevel() {
+ return SdkVersionInfo.HIGHEST_KNOWN_API;
+ }
+
+ /**
+ * Returns a list of known API names
+ *
+ * @return a list of string API names, starting from 1 and up through the
+ * maximum known versions (with no gaps)
+ */
+ public static String[] getKnownVersions() {
+ int max = getHighestKnownApiLevel();
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ for (IAndroidTarget target : sdk.getTargets()) {
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (!version.isPreview()) {
+ max = Math.max(max, version.getApiLevel());
+ }
+ }
+ }
+ }
+
+ String[] versions = new String[max];
+ for (int api = 1; api <= max; api++) {
+ versions[api-1] = getAndroidName(api);
+ }
+
+ return versions;
+ }
+
+ /**
+ * Returns the Android project(s) that are selected or active, if any. This
+ * considers the selection, the active editor, etc.
+ *
+ * @param selection the current selection
+ * @return a list of projects, possibly empty (but never null)
+ */
+ @NonNull
+ public static List<IProject> getSelectedProjects(@Nullable ISelection selection) {
+ List<IProject> projects = new ArrayList<IProject>();
+
+ if (selection instanceof IStructuredSelection) {
+ IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+ // get the unique selected item.
+ Iterator<?> iterator = structuredSelection.iterator();
+ while (iterator.hasNext()) {
+ Object element = iterator.next();
+
+ // First look up the resource (since some adaptables
+ // provide an IResource but not an IProject, and we can
+ // always go from IResource to IProject)
+ IResource resource = null;
+ if (element instanceof IResource) { // may include IProject
+ resource = (IResource) element;
+ } else if (element instanceof IAdaptable) {
+ IAdaptable adaptable = (IAdaptable)element;
+ Object adapter = adaptable.getAdapter(IResource.class);
+ resource = (IResource) adapter;
+ }
+
+ // get the project object from it.
+ IProject project = null;
+ if (resource != null) {
+ project = resource.getProject();
+ } else if (element instanceof IAdaptable) {
+ project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
+ }
+
+ if (project != null && !projects.contains(project)) {
+ projects.add(project);
+ }
+ }
+ }
+
+ if (projects.isEmpty()) {
+ // Try to look at the active editor instead
+ IFile file = AdtUtils.getActiveFile();
+ if (file != null) {
+ projects.add(file.getProject());
+ }
+ }
+
+ if (projects.isEmpty()) {
+ // If we didn't find a default project based on the selection, check how many
+ // open Android projects we can find in the current workspace. If there's only
+ // one, we'll just select it by default.
+ IJavaProject[] open = AdtUtils.getOpenAndroidProjects();
+ for (IJavaProject project : open) {
+ projects.add(project.getProject());
+ }
+ return projects;
+ } else {
+ // Make sure all the projects are Android projects
+ List<IProject> androidProjects = new ArrayList<IProject>(projects.size());
+ for (IProject project : projects) {
+ if (BaseProjectHelper.isAndroidProject(project)) {
+ androidProjects.add(project);
+ }
+ }
+ return androidProjects;
+ }
+ }
+
+ private static Boolean sEclipse4;
+
+ /**
+ * Returns true if the running Eclipse is version 4.x or later
+ *
+ * @return true if the current Eclipse version is 4.x or later, false
+ * otherwise
+ */
+ public static boolean isEclipse4() {
+ if (sEclipse4 == null) {
+ sEclipse4 = Platform.getBundle("org.eclipse.e4.ui.model.workbench") != null; //$NON-NLS-1$
+ }
+
+ return sEclipse4;
+ }
+
+ /**
+ * Reads the contents of an {@link IFile} and return it as a byte array
+ *
+ * @param file the file to be read
+ * @return the String read from the file, or null if there was an error
+ */
+ @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet
+ @Nullable
+ public static byte[] readData(@NonNull IFile file) {
+ InputStream contents = null;
+ try {
+ contents = file.getContents();
+ return ByteStreams.toByteArray(contents);
+ } catch (Exception e) {
+ // Pass -- just return null
+ } finally {
+ Closeables.closeQuietly(contents);
+ }
+
+ return null;
+ }
+
+ /**
+ * Ensure that a given folder (and all its parents) are created. This implements
+ * the equivalent of {@link File#mkdirs()} for {@link IContainer} folders.
+ *
+ * @param container the container to ensure exists
+ * @throws CoreException if an error occurs
+ */
+ public static void ensureExists(@Nullable IContainer container) throws CoreException {
+ if (container == null || container.exists()) {
+ return;
+ }
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IFolder folder = root.getFolder(container.getFullPath());
+ ensureExists(folder);
+ }
+
+ private static void ensureExists(IFolder folder) throws CoreException {
+ if (folder != null && !folder.exists()) {
+ IContainer parent = folder.getParent();
+ if (parent instanceof IFolder) {
+ ensureExists((IFolder) parent);
+ }
+ folder.create(false, false, null);
+ }
+ }
+
+ /**
+ * Format the given floating value into an XML string, omitting decimals if
+ * 0
+ *
+ * @param value the value to be formatted
+ * @return the corresponding XML string for the value
+ */
+ public static String formatFloatAttribute(float value) {
+ if (value != (int) value) {
+ // Run String.format without a locale, because we don't want locale-specific
+ // conversions here like separating the decimal part with a comma instead of a dot!
+ return String.format((Locale) null, "%.2f", value); //$NON-NLS-1$
+ } else {
+ return Integer.toString((int) value);
+ }
+ }
+
+ /**
+ * Creates all the directories required for the given path.
+ *
+ * @param wsPath the path to create all the parent directories for
+ * @return true if all the parent directories were created
+ */
+ public static boolean createWsParentDirectory(IContainer wsPath) {
+ if (wsPath.getType() == IResource.FOLDER) {
+ if (wsPath.exists()) {
+ return true;
+ }
+
+ IFolder folder = (IFolder) wsPath;
+ try {
+ if (createWsParentDirectory(wsPath.getParent())) {
+ folder.create(true /* force */, true /* local */, null /* monitor */);
+ return true;
+ }
+ } catch (CoreException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Lists the files of the given directory and returns them as an array which
+ * is never null. This simplifies processing file listings from for each
+ * loops since {@link File#listFiles} can return null. This method simply
+ * wraps it and makes sure it returns an empty array instead if necessary.
+ *
+ * @param dir the directory to list
+ * @return the children, or empty if it has no children, is not a directory,
+ * etc.
+ */
+ @NonNull
+ public static File[] listFiles(File dir) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ return files;
+ } else {
+ return new File[0];
+ }
+ }
+
+ /**
+ * Closes all open editors that are showing a file for the given project. This method
+ * should be called when a project is closed or deleted.
+ * <p>
+ * This method can be called from any thread, but if it is not called on the GUI thread
+ * the editor will be closed asynchronously.
+ *
+ * @param project the project to close all editors for
+ * @param save whether unsaved editors should be saved first
+ */
+ public static void closeEditors(@NonNull final IProject project, final boolean save) {
+ final Display display = AdtPlugin.getDisplay();
+ if (display == null || display.isDisposed()) {
+ return;
+ }
+ if (display.getThread() != Thread.currentThread()) {
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ closeEditors(project, save);
+ }
+ });
+ return;
+ }
+
+ // Close editors for removed files
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
+ for (IWorkbenchPage page : window.getPages()) {
+ List<IEditorReference> matching = null;
+ for (IEditorReference ref : page.getEditorReferences()) {
+ boolean close = false;
+ try {
+ IEditorInput input = ref.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ IFileEditorInput fileInput = (IFileEditorInput) input;
+ if (project.equals(fileInput.getFile().getProject())) {
+ close = true;
+ }
+ }
+ } catch (PartInitException ex) {
+ close = true;
+ }
+ if (close) {
+ if (matching == null) {
+ matching = new ArrayList<IEditorReference>(2);
+ }
+ matching.add(ref);
+ }
+ }
+ if (matching != null) {
+ IEditorReference[] refs = new IEditorReference[matching.size()];
+ page.closeEditors(matching.toArray(refs), save);
+ }
+ }
+ }
+ }
+
+ /**
+ * Closes all open editors for the given file. Note that a file can be open in
+ * more than one editor, for example by using Open With on the file to choose different
+ * editors.
+ * <p>
+ * This method can be called from any thread, but if it is not called on the GUI thread
+ * the editor will be closed asynchronously.
+ *
+ * @param file the file whose editors should be closed.
+ * @param save whether unsaved editors should be saved first
+ */
+ public static void closeEditors(@NonNull final IFile file, final boolean save) {
+ final Display display = AdtPlugin.getDisplay();
+ if (display == null || display.isDisposed()) {
+ return;
+ }
+ if (display.getThread() != Thread.currentThread()) {
+ display.asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ closeEditors(file, save);
+ }
+ });
+ return;
+ }
+
+ // Close editors for removed files
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
+ for (IWorkbenchPage page : window.getPages()) {
+ List<IEditorReference> matching = null;
+ for (IEditorReference ref : page.getEditorReferences()) {
+ boolean close = false;
+ try {
+ IEditorInput input = ref.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ IFileEditorInput fileInput = (IFileEditorInput) input;
+ if (file.equals(fileInput.getFile())) {
+ close = true;
+ }
+ }
+ } catch (PartInitException ex) {
+ close = true;
+ }
+ if (close) {
+ // Found
+ if (matching == null) {
+ matching = new ArrayList<IEditorReference>(2);
+ }
+ matching.add(ref);
+ // We don't break here in case the file is
+ // opened multiple times with different editors.
+ }
+ }
+ if (matching != null) {
+ IEditorReference[] refs = new IEditorReference[matching.size()];
+ page.closeEditors(matching.toArray(refs), save);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the offset region of the given 0-based line number in the given
+ * file
+ *
+ * @param file the file to look up the line number in
+ * @param line the line number (0-based, meaning that the first line is line
+ * 0)
+ * @return the corresponding offset range, or null
+ */
+ @Nullable
+ public static IRegion getRegionOfLine(@NonNull IFile file, int line) {
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ try {
+ provider.connect(file);
+ IDocument document = provider.getDocument(file);
+ if (document != null) {
+ return document.getLineInformation(line);
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, "Can't find range information for %1$s", file.getName());
+ } finally {
+ provider.disconnect(file);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns all resource variations for the given file
+ *
+ * @param file resource file, which should be an XML file in one of the
+ * various resource folders, e.g. res/layout, res/values-xlarge, etc.
+ * @param includeSelf if true, include the file itself in the list,
+ * otherwise exclude it
+ * @return a list of all the resource variations
+ */
+ public static List<IFile> getResourceVariations(@Nullable IFile file, boolean includeSelf) {
+ if (file == null) {
+ return Collections.emptyList();
+ }
+
+ // Compute the set of layout files defining this layout resource
+ List<IFile> variations = new ArrayList<IFile>();
+ String name = file.getName();
+ IContainer parent = file.getParent();
+ if (parent != null) {
+ IContainer resFolder = parent.getParent();
+ if (resFolder != null) {
+ String parentName = parent.getName();
+ String prefix = parentName;
+ int qualifiers = prefix.indexOf('-');
+
+ if (qualifiers != -1) {
+ parentName = prefix.substring(0, qualifiers);
+ prefix = prefix.substring(0, qualifiers + 1);
+ } else {
+ prefix = prefix + '-';
+ }
+ try {
+ for (IResource resource : resFolder.members()) {
+ String n = resource.getName();
+ if ((n.startsWith(prefix) || n.equals(parentName))
+ && resource instanceof IContainer) {
+ IContainer layoutFolder = (IContainer) resource;
+ IResource r = layoutFolder.findMember(name);
+ if (r instanceof IFile) {
+ IFile variation = (IFile) r;
+ if (!includeSelf && file.equals(variation)) {
+ continue;
+ }
+ variations.add(variation);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ return variations;
+ }
+
+ /**
+ * Returns whether the current thread is the UI thread
+ *
+ * @return true if the current thread is the UI thread
+ */
+ public static boolean isUiThread() {
+ return AdtPlugin.getDisplay() != null
+ && AdtPlugin.getDisplay().getThread() == Thread.currentThread();
+ }
+
+ /**
+ * Replaces any {@code \\uNNNN} references in the given string with the corresponding
+ * unicode characters.
+ *
+ * @param s the string to perform replacements in
+ * @return the string with unicode escapes replaced with actual characters
+ */
+ @NonNull
+ public static String replaceUnicodeEscapes(@NonNull String s) {
+ // Handle unicode escapes
+ if (s.indexOf("\\u") != -1) { //$NON-NLS-1$
+ StringBuilder sb = new StringBuilder(s.length());
+ for (int i = 0, n = s.length(); i < n; i++) {
+ char c = s.charAt(i);
+ if (c == '\\' && i < n - 1) {
+ char next = s.charAt(i + 1);
+ if (next == 'u' && i < n - 5) { // case sensitive
+ String hex = s.substring(i + 2, i + 6);
+ try {
+ int unicodeValue = Integer.parseInt(hex, 16);
+ sb.append((char) unicodeValue);
+ i += 5;
+ continue;
+ } catch (NumberFormatException nufe) {
+ // Invalid escape: Just proceed to literally transcribe it
+ sb.append(c);
+ }
+ } else {
+ sb.append(c);
+ sb.append(next);
+ i++;
+ continue;
+ }
+ } else {
+ sb.append(c);
+ }
+ }
+ s = sb.toString();
+ }
+
+ return s;
+ }
+
+ /**
+ * Looks up the {@link ResourceFolderType} corresponding to a given
+ * {@link ResourceType}: the folder where those resources can be found.
+ * <p>
+ * Note that {@link ResourceType#ID} is a special case: it can not just
+ * be defined in {@link ResourceFolderType#VALUES}, but it can also be
+ * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
+ * {@link ResourceFolderType#MENU} folders.
+ *
+ * @param type the resource type
+ * @return the corresponding resource folder type
+ */
+ @NonNull
+ public static ResourceFolderType getFolderTypeFor(@NonNull ResourceType type) {
+ switch (type) {
+ case ANIM:
+ return ResourceFolderType.ANIM;
+ case ANIMATOR:
+ return ResourceFolderType.ANIMATOR;
+ case ARRAY:
+ return ResourceFolderType.VALUES;
+ case COLOR:
+ return ResourceFolderType.COLOR;
+ case DRAWABLE:
+ return ResourceFolderType.DRAWABLE;
+ case INTERPOLATOR:
+ return ResourceFolderType.INTERPOLATOR;
+ case LAYOUT:
+ return ResourceFolderType.LAYOUT;
+ case MENU:
+ return ResourceFolderType.MENU;
+ case MIPMAP:
+ return ResourceFolderType.MIPMAP;
+ case RAW:
+ return ResourceFolderType.RAW;
+ case XML:
+ return ResourceFolderType.XML;
+ case ATTR:
+ case BOOL:
+ case DECLARE_STYLEABLE:
+ case DIMEN:
+ case FRACTION:
+ case ID:
+ case INTEGER:
+ case PLURALS:
+ case PUBLIC:
+ case STRING:
+ case STYLE:
+ case STYLEABLE:
+ return ResourceFolderType.VALUES;
+ default:
+ assert false : type;
+ return ResourceFolderType.VALUES;
+
+ }
+ }
+
+ /**
+ * Looks up the {@link ResourceType} defined in a given {@link ResourceFolderType}.
+ * <p>
+ * Note that for {@link ResourceFolderType#VALUES} there are many, many
+ * different types of resources that can be defined, so this method returns
+ * {@code null} for that scenario.
+ * <p>
+ * Note also that {@link ResourceType#ID} is a special case: it can not just
+ * be defined in {@link ResourceFolderType#VALUES}, but it can also be
+ * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
+ * {@link ResourceFolderType#MENU} folders.
+ *
+ * @param folderType the resource folder type
+ * @return the corresponding resource type, or null if {@code folderType} is
+ * {@link ResourceFolderType#VALUES}
+ */
+ @Nullable
+ public static ResourceType getResourceTypeFor(@NonNull ResourceFolderType folderType) {
+ switch (folderType) {
+ case ANIM:
+ return ResourceType.ANIM;
+ case ANIMATOR:
+ return ResourceType.ANIMATOR;
+ case COLOR:
+ return ResourceType.COLOR;
+ case DRAWABLE:
+ return ResourceType.DRAWABLE;
+ case INTERPOLATOR:
+ return ResourceType.INTERPOLATOR;
+ case LAYOUT:
+ return ResourceType.LAYOUT;
+ case MENU:
+ return ResourceType.MENU;
+ case MIPMAP:
+ return ResourceType.MIPMAP;
+ case RAW:
+ return ResourceType.RAW;
+ case XML:
+ return ResourceType.XML;
+ case VALUES:
+ return null;
+ default:
+ assert false : folderType;
+ return null;
+ }
+ }
+}