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