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 com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter; 23 24 import org.eclipse.core.filesystem.URIUtil; 25 import org.eclipse.core.resources.IFile; 26 import org.eclipse.core.resources.IMarker; 27 import org.eclipse.core.resources.IProject; 28 import org.eclipse.core.resources.IResource; 29 import org.eclipse.core.resources.IWorkspace; 30 import org.eclipse.core.resources.IWorkspaceRoot; 31 import org.eclipse.core.resources.ResourcesPlugin; 32 import org.eclipse.core.runtime.CoreException; 33 import org.eclipse.core.runtime.IPath; 34 import org.eclipse.core.runtime.Path; 35 import org.eclipse.jdt.core.IJavaProject; 36 import org.eclipse.jface.text.BadLocationException; 37 import org.eclipse.jface.text.IDocument; 38 import org.eclipse.jface.text.IRegion; 39 import org.eclipse.ui.IEditorInput; 40 import org.eclipse.ui.IEditorPart; 41 import org.eclipse.ui.IFileEditorInput; 42 import org.eclipse.ui.IURIEditorInput; 43 import org.eclipse.ui.IWorkbenchPage; 44 import org.eclipse.ui.IWorkbenchWindow; 45 import org.eclipse.ui.PlatformUI; 46 import org.eclipse.ui.texteditor.ITextEditor; 47 48 import java.io.File; 49 import java.net.URISyntaxException; 50 import java.net.URL; 51 import java.util.ArrayList; 52 import java.util.List; 53 54 55 /** Utility methods for ADT */ 56 public class AdtUtils { 57 /** 58 * Returns true if the given string ends with the given suffix, using a 59 * case-insensitive comparison. 60 * 61 * @param string the full string to be checked 62 * @param suffix the suffix to be checked for 63 * @return true if the string case-insensitively ends with the given suffix 64 */ 65 public static boolean endsWithIgnoreCase(String string, String suffix) { 66 return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(), 67 suffix, 0, suffix.length()); 68 } 69 70 /** 71 * Returns true if the given sequence ends with the given suffix (case 72 * sensitive). 73 * 74 * @param sequence the character sequence to be checked 75 * @param suffix the suffix to look for 76 * @return true if the given sequence ends with the given suffix 77 */ 78 public static boolean endsWith(CharSequence sequence, CharSequence suffix) { 79 return endsWith(sequence, sequence.length(), suffix); 80 } 81 82 /** 83 * Returns true if the given sequence ends at the given offset with the given suffix (case 84 * sensitive) 85 * 86 * @param sequence the character sequence to be checked 87 * @param endOffset the offset at which the sequence is considered to end 88 * @param suffix the suffix to look for 89 * @return true if the given sequence ends with the given suffix 90 */ 91 public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) { 92 if (endOffset < suffix.length()) { 93 return false; 94 } 95 96 for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) { 97 if (sequence.charAt(i) != suffix.charAt(j)) { 98 return false; 99 } 100 } 101 102 return true; 103 } 104 105 /** 106 * Returns true if the given string starts with the given prefix, using a 107 * case-insensitive comparison. 108 * 109 * @param string the full string to be checked 110 * @param prefix the prefix to be checked for 111 * @return true if the string case-insensitively starts with the given prefix 112 */ 113 public static boolean startsWithIgnoreCase(String string, String prefix) { 114 return string.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length()); 115 } 116 117 /** 118 * Returns true if the given string starts at the given offset with the 119 * given prefix, case insensitively. 120 * 121 * @param string the full string to be checked 122 * @param offset the offset in the string to start looking 123 * @param prefix the prefix to be checked for 124 * @return true if the string case-insensitively starts at the given offset 125 * with the given prefix 126 */ 127 public static boolean startsWith(String string, int offset, String prefix) { 128 return string.regionMatches(true /* ignoreCase */, offset, prefix, 0, prefix.length()); 129 } 130 131 /** 132 * Strips the whitespace from the given string 133 * 134 * @param string the string to be cleaned up 135 * @return the string, without whitespace 136 */ 137 public static String stripWhitespace(String string) { 138 StringBuilder sb = new StringBuilder(string.length()); 139 for (int i = 0, n = string.length(); i < n; i++) { 140 char c = string.charAt(i); 141 if (!Character.isWhitespace(c)) { 142 sb.append(c); 143 } 144 } 145 146 return sb.toString(); 147 } 148 149 /** 150 * Creates a Java class name out of the given string, if possible. For 151 * example, "My Project" becomes "MyProject", "hello" becomes "Hello", 152 * "Java's" becomes "Java", and so on. 153 * 154 * @param string the string to be massaged into a Java class 155 * @return the string as a Java class, or null if a class name could not be 156 * extracted 157 */ 158 public static String extractClassName(String string) { 159 StringBuilder sb = new StringBuilder(string.length()); 160 int n = string.length(); 161 162 int i = 0; 163 for (; i < n; i++) { 164 char c = Character.toUpperCase(string.charAt(i)); 165 if (Character.isJavaIdentifierStart(c)) { 166 sb.append(c); 167 i++; 168 break; 169 } 170 } 171 if (sb.length() > 0) { 172 for (; i < n; i++) { 173 char c = string.charAt(i); 174 if (Character.isJavaIdentifierPart(c)) { 175 sb.append(c); 176 } 177 } 178 179 return sb.toString(); 180 } 181 182 return null; 183 } 184 185 /** 186 * Strips off the last file extension from the given filename, e.g. 187 * "foo.backup.diff" will be turned into "foo.backup". 188 * <p> 189 * Note that dot files (e.g. ".profile") will be left alone. 190 * 191 * @param filename the filename to be stripped 192 * @return the filename without the last file extension. 193 */ 194 public static String stripLastExtension(String filename) { 195 int dotIndex = filename.lastIndexOf('.'); 196 if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently 197 return filename.substring(0, dotIndex); 198 } else { 199 return filename; 200 } 201 } 202 203 /** 204 * Strips off all extensions from the given filename, e.g. "foo.9.png" will 205 * be turned into "foo". 206 * <p> 207 * Note that dot files (e.g. ".profile") will be left alone. 208 * 209 * @param filename the filename to be stripped 210 * @return the filename without any file extensions 211 */ 212 public static String stripAllExtensions(String filename) { 213 int dotIndex = filename.indexOf('.'); 214 if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently 215 return filename.substring(0, dotIndex); 216 } else { 217 return filename; 218 } 219 } 220 221 /** 222 * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z]. 223 * Returns the string unmodified if the first character is not [a-z]. 224 * 225 * @param str The string to capitalize. 226 * @return The capitalized string 227 */ 228 public static String capitalize(String str) { 229 if (str == null || str.length() < 1 || Character.isUpperCase(str.charAt(0))) { 230 return str; 231 } 232 233 StringBuilder sb = new StringBuilder(); 234 sb.append(Character.toUpperCase(str.charAt(0))); 235 sb.append(str.substring(1)); 236 return sb.toString(); 237 } 238 239 /** 240 * Returns the current editor (the currently visible and active editor), or null if 241 * not found 242 * 243 * @return the current editor, or null 244 */ 245 public static IEditorPart getActiveEditor() { 246 IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 247 if (window != null) { 248 IWorkbenchPage page = window.getActivePage(); 249 if (page != null) { 250 return page.getActiveEditor(); 251 } 252 } 253 254 return null; 255 } 256 257 /** 258 * Returns the current text editor (the currently visible and active editor), or null 259 * if not found. 260 * 261 * @return the current text editor, or null 262 */ 263 public static ITextEditor getActiveTextEditor() { 264 IEditorPart editor = getActiveEditor(); 265 if (editor != null) { 266 if (editor instanceof ITextEditor) { 267 return (ITextEditor) editor; 268 } else { 269 return (ITextEditor) editor.getAdapter(ITextEditor.class); 270 } 271 } 272 273 return null; 274 } 275 276 /** 277 * Attempts to convert the given {@link URL} into a {@link File}. 278 * 279 * @param url the {@link URL} to be converted 280 * @return the corresponding {@link File}, which may not exist 281 */ 282 @NonNull 283 public static File getFile(@NonNull URL url) { 284 try { 285 // First try URL.toURI(): this will work for URLs that contain %20 for spaces etc. 286 // Unfortunately, it *doesn't* work for "broken" URLs where the URL contains 287 // spaces, which is often the case. 288 return new File(url.toURI()); 289 } catch (URISyntaxException e) { 290 // ...so as a fallback, go to the old url.getPath() method, which handles space paths. 291 return new File(url.getPath()); 292 } 293 } 294 295 /** 296 * Returns the file for the current editor, if any. 297 * 298 * @return the file for the current editor, or null if none 299 */ 300 public static IFile getActiveFile() { 301 IEditorPart editor = getActiveEditor(); 302 if (editor != null) { 303 IEditorInput input = editor.getEditorInput(); 304 if (input instanceof IFileEditorInput) { 305 IFileEditorInput fileInput = (IFileEditorInput) input; 306 return fileInput.getFile(); 307 } 308 } 309 310 return null; 311 } 312 313 /** 314 * Returns an absolute path to the given resource 315 * 316 * @param resource the resource to look up a path for 317 * @return an absolute file system path to the resource 318 */ 319 public static IPath getAbsolutePath(IResource resource) { 320 IPath location = resource.getRawLocation(); 321 if (location != null) { 322 return location.makeAbsolute(); 323 } else { 324 IWorkspace workspace = ResourcesPlugin.getWorkspace(); 325 IWorkspaceRoot root = workspace.getRoot(); 326 IPath workspacePath = root.getLocation(); 327 return workspacePath.append(resource.getFullPath()); 328 } 329 } 330 331 /** 332 * Converts a {@link File} to an {@link IFile}, if possible. 333 * 334 * @param file a file to be converted 335 * @return the corresponding {@link IFile}, or null 336 */ 337 public static IFile fileToIFile(File file) { 338 if (!file.isAbsolute()) { 339 file = file.getAbsoluteFile(); 340 } 341 342 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 343 IFile[] files = workspace.findFilesForLocationURI(file.toURI()); 344 if (files.length > 0) { 345 return files[0]; 346 } 347 348 IPath filePath = new Path(file.getPath()); 349 return pathToIFile(filePath); 350 } 351 352 /** 353 * Converts a {@link File} to an {@link IResource}, if possible. 354 * 355 * @param file a file to be converted 356 * @return the corresponding {@link IResource}, or null 357 */ 358 public static IResource fileToResource(File file) { 359 if (!file.isAbsolute()) { 360 file = file.getAbsoluteFile(); 361 } 362 363 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 364 IFile[] files = workspace.findFilesForLocationURI(file.toURI()); 365 if (files.length > 0) { 366 return files[0]; 367 } 368 369 IPath filePath = new Path(file.getPath()); 370 return pathToResource(filePath); 371 } 372 373 /** 374 * Converts a {@link IPath} to an {@link IFile}, if possible. 375 * 376 * @param path a path to be converted 377 * @return the corresponding {@link IFile}, or null 378 */ 379 public static IFile pathToIFile(IPath path) { 380 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 381 382 IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute())); 383 if (files.length > 0) { 384 return files[0]; 385 } 386 387 IPath workspacePath = workspace.getLocation(); 388 if (workspacePath.isPrefixOf(path)) { 389 IPath relativePath = path.makeRelativeTo(workspacePath); 390 IResource member = workspace.findMember(relativePath); 391 if (member instanceof IFile) { 392 return (IFile) member; 393 } 394 } else if (path.isAbsolute()) { 395 return workspace.getFileForLocation(path); 396 } 397 398 return null; 399 } 400 401 /** 402 * Converts a {@link IPath} to an {@link IResource}, if possible. 403 * 404 * @param path a path to be converted 405 * @return the corresponding {@link IResource}, or null 406 */ 407 public static IResource pathToResource(IPath path) { 408 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 409 410 IFile[] files = workspace.findFilesForLocationURI(URIUtil.toURI(path.makeAbsolute())); 411 if (files.length > 0) { 412 return files[0]; 413 } 414 415 IPath workspacePath = workspace.getLocation(); 416 if (workspacePath.isPrefixOf(path)) { 417 IPath relativePath = path.makeRelativeTo(workspacePath); 418 return workspace.findMember(relativePath); 419 } else if (path.isAbsolute()) { 420 return workspace.getFileForLocation(path); 421 } 422 423 return null; 424 } 425 426 /** 427 * Returns all markers in a file/document that fit on the same line as the given offset 428 * 429 * @param markerType the marker type 430 * @param file the file containing the markers 431 * @param document the document showing the markers 432 * @param offset the offset to be checked 433 * @return a list (possibly empty but never null) of matching markers 434 */ 435 @NonNull 436 public static List<IMarker> findMarkersOnLine( 437 @NonNull String markerType, 438 @NonNull IResource file, 439 @NonNull IDocument document, 440 int offset) { 441 List<IMarker> matchingMarkers = new ArrayList<IMarker>(2); 442 try { 443 IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO); 444 445 // Look for a match on the same line as the caret. 446 IRegion lineInfo = document.getLineInformationOfOffset(offset); 447 int lineStart = lineInfo.getOffset(); 448 int lineEnd = lineStart + lineInfo.getLength(); 449 int offsetLine = document.getLineOfOffset(offset); 450 451 452 for (IMarker marker : markers) { 453 int start = marker.getAttribute(IMarker.CHAR_START, -1); 454 int end = marker.getAttribute(IMarker.CHAR_END, -1); 455 if (start >= lineStart && start <= lineEnd && end > start) { 456 matchingMarkers.add(marker); 457 } else if (start == -1 && end == -1) { 458 // Some markers don't set character range, they only set the line 459 int line = marker.getAttribute(IMarker.LINE_NUMBER, -1); 460 if (line == offsetLine + 1) { 461 matchingMarkers.add(marker); 462 } 463 } 464 } 465 } catch (CoreException e) { 466 AdtPlugin.log(e, null); 467 } catch (BadLocationException e) { 468 AdtPlugin.log(e, null); 469 } 470 471 return matchingMarkers; 472 } 473 474 /** 475 * Returns the available and open Android projects 476 * 477 * @return the available and open Android projects, never null 478 */ 479 @NonNull 480 public static IJavaProject[] getOpenAndroidProjects() { 481 return BaseProjectHelper.getAndroidProjects(new IProjectFilter() { 482 @Override 483 public boolean accept(IProject project) { 484 return project.isAccessible(); 485 } 486 }); 487 } 488 489 /** 490 * Returns a unique project name, based on the given {@code base} file name 491 * possibly with a {@code conjunction} and a new number behind it to ensure 492 * that the project name is unique. For example, 493 * {@code getUniqueProjectName("project", "_")} will return 494 * {@code "project"} if that name does not already exist, and if it does, it 495 * will return {@code "project_2"}. 496 * 497 * @param base the base name to use, such as "foo" 498 * @param conjunction a string to insert between the base name and the 499 * number. 500 * @return a unique project name based on the given base and conjunction 501 */ 502 public static String getUniqueProjectName(String base, String conjunction) { 503 // We're using all workspace projects here rather than just open Android project 504 // via getOpenAndroidProjects because the name cannot conflict with non-Android 505 // or closed projects either 506 IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); 507 IProject[] projects = workspaceRoot.getProjects(); 508 509 for (int i = 1; i < 1000; i++) { 510 String name = i == 1 ? base : base + conjunction + Integer.toString(i); 511 boolean found = false; 512 for (IProject project : projects) { 513 if (project.getName().equals(name)) { 514 found = true; 515 break; 516 } 517 } 518 if (!found) { 519 return name; 520 } 521 } 522 523 return base; 524 } 525 526 /** 527 * Returns the name of the parent folder for the given editor input 528 * 529 * @param editorInput the editor input to check 530 * @return the parent folder, which is never null but may be "" 531 */ 532 @NonNull 533 public static String getParentFolderName(@Nullable IEditorInput editorInput) { 534 if (editorInput instanceof IFileEditorInput) { 535 IFile file = ((IFileEditorInput) editorInput).getFile(); 536 return file.getParent().getName(); 537 } 538 539 if (editorInput instanceof IURIEditorInput) { 540 IURIEditorInput urlEditorInput = (IURIEditorInput) editorInput; 541 String path = urlEditorInput.getURI().toString(); 542 int lastIndex = path.lastIndexOf('/'); 543 if (lastIndex != -1) { 544 int lastLastIndex = path.lastIndexOf('/', lastIndex - 1); 545 if (lastLastIndex != -1) { 546 return path.substring(lastLastIndex + 1, lastIndex); 547 } 548 } 549 } 550 551 return ""; 552 } 553 } 554