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 org.eclipse.core.resources.IFile; 20 import org.eclipse.core.resources.IMarker; 21 import org.eclipse.core.resources.IResource; 22 import org.eclipse.core.resources.IWorkspaceRoot; 23 import org.eclipse.core.resources.ResourcesPlugin; 24 import org.eclipse.core.runtime.CoreException; 25 import org.eclipse.core.runtime.IPath; 26 import org.eclipse.core.runtime.Path; 27 import org.eclipse.jface.text.BadLocationException; 28 import org.eclipse.jface.text.IDocument; 29 import org.eclipse.jface.text.IRegion; 30 import org.eclipse.ui.IEditorInput; 31 import org.eclipse.ui.IEditorPart; 32 import org.eclipse.ui.IFileEditorInput; 33 import org.eclipse.ui.IWorkbenchPage; 34 import org.eclipse.ui.IWorkbenchWindow; 35 import org.eclipse.ui.PlatformUI; 36 import org.eclipse.ui.texteditor.ITextEditor; 37 38 import java.io.File; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 43 /** Utility methods for ADT */ 44 public class AdtUtils { 45 /** 46 * Returns true if the given string ends with the given suffix, using a 47 * case-insensitive comparison. 48 * 49 * @param string the full string to be checked 50 * @param suffix the suffix to be checked for 51 * @return true if the string case-insensitively ends with the given suffix 52 */ 53 public static boolean endsWithIgnoreCase(String string, String suffix) { 54 return string.regionMatches(true /* ignoreCase */, string.length() - suffix.length(), 55 suffix, 0, suffix.length()); 56 } 57 58 /** 59 * Returns true if the given sequence ends with the given suffix (case 60 * sensitive). 61 * 62 * @param sequence the character sequence to be checked 63 * @param suffix the suffix to look for 64 * @return true if the given sequence ends with the given suffix 65 */ 66 public static boolean endsWith(CharSequence sequence, CharSequence suffix) { 67 return endsWith(sequence, sequence.length(), suffix); 68 } 69 70 /** 71 * Returns true if the given sequence ends at the given offset with the given suffix (case 72 * sensitive) 73 * 74 * @param sequence the character sequence to be checked 75 * @param endOffset the offset at which the sequence is considered to end 76 * @param suffix the suffix to look for 77 * @return true if the given sequence ends with the given suffix 78 */ 79 public static boolean endsWith(CharSequence sequence, int endOffset, CharSequence suffix) { 80 if (endOffset < suffix.length()) { 81 return false; 82 } 83 84 for (int i = endOffset - 1, j = suffix.length() - 1; j >= 0; i--, j--) { 85 if (sequence.charAt(i) != suffix.charAt(j)) { 86 return false; 87 } 88 } 89 90 return true; 91 } 92 93 /** 94 * Strips the whitespace from the given string 95 * 96 * @param string the string to be cleaned up 97 * @return the string, without whitespace 98 */ 99 public static String stripWhitespace(String string) { 100 StringBuilder sb = new StringBuilder(string.length()); 101 for (int i = 0, n = string.length(); i < n; i++) { 102 char c = string.charAt(i); 103 if (!Character.isWhitespace(c)) { 104 sb.append(c); 105 } 106 } 107 108 return sb.toString(); 109 } 110 111 /** 112 * Creates a Java class name out of the given string, if possible. For 113 * example, "My Project" becomes "MyProject", "hello" becomes "Hello", 114 * "Java's" becomes "Java", and so on. 115 * 116 * @param string the string to be massaged into a Java class 117 * @return the string as a Java class, or null if a class name could not be 118 * extracted 119 */ 120 public static String extractClassName(String string) { 121 StringBuilder sb = new StringBuilder(string.length()); 122 int n = string.length(); 123 124 int i = 0; 125 for (; i < n; i++) { 126 char c = Character.toUpperCase(string.charAt(i)); 127 if (Character.isJavaIdentifierStart(c)) { 128 sb.append(c); 129 i++; 130 break; 131 } 132 } 133 if (sb.length() > 0) { 134 for (; i < n; i++) { 135 char c = string.charAt(i); 136 if (Character.isJavaIdentifierPart(c)) { 137 sb.append(c); 138 } 139 } 140 141 return sb.toString(); 142 } 143 144 return null; 145 } 146 147 /** 148 * Strips off the last file extension from the given filename, e.g. 149 * "foo.backup.diff" will be turned into "foo.backup". 150 * <p> 151 * Note that dot files (e.g. ".profile") will be left alone. 152 * 153 * @param filename the filename to be stripped 154 * @return the filename without the last file extension. 155 */ 156 public static String stripLastExtension(String filename) { 157 int dotIndex = filename.lastIndexOf('.'); 158 if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently 159 return filename.substring(0, dotIndex); 160 } else { 161 return filename; 162 } 163 } 164 165 /** 166 * Strips off all extensions from the given filename, e.g. "foo.9.png" will 167 * be turned into "foo". 168 * <p> 169 * Note that dot files (e.g. ".profile") will be left alone. 170 * 171 * @param filename the filename to be stripped 172 * @return the filename without any file extensions 173 */ 174 public static String stripAllExtensions(String filename) { 175 int dotIndex = filename.indexOf('.'); 176 if (dotIndex > 0) { // > 0 instead of != -1: Treat dot files (e.g. .profile) differently 177 return filename.substring(0, dotIndex); 178 } else { 179 return filename; 180 } 181 } 182 183 /** 184 * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z]. 185 * Returns the string unmodified if the first character is not [a-z]. 186 * 187 * @param str The string to capitalize. 188 * @return The capitalized string 189 */ 190 public static String capitalize(String str) { 191 if (str == null || str.length() < 1 || Character.isUpperCase(str.charAt(0))) { 192 return str; 193 } 194 195 StringBuilder sb = new StringBuilder(); 196 sb.append(Character.toUpperCase(str.charAt(0))); 197 sb.append(str.substring(1)); 198 return sb.toString(); 199 } 200 201 /** 202 * Computes the edit distance (number of insertions, deletions or substitutions 203 * to edit one string into the other) between two strings. In particular, 204 * this will compute the Levenshtein distance. 205 * <p> 206 * See http://en.wikipedia.org/wiki/Levenshtein_distance for details. 207 * 208 * @param s the first string to compare 209 * @param t the second string to compare 210 * @return the edit distance between the two strings 211 */ 212 public static int editDistance(String s, String t) { 213 int m = s.length(); 214 int n = t.length(); 215 int[][] d = new int[m + 1][n + 1]; 216 for (int i = 0; i <= m; i++) { 217 d[i][0] = i; 218 } 219 for (int j = 0; j <= n; j++) { 220 d[0][j] = j; 221 } 222 for (int j = 1; j <= n; j++) { 223 for (int i = 1; i <= m; i++) { 224 if (s.charAt(i - 1) == t.charAt(j - 1)) { 225 d[i][j] = d[i - 1][j - 1]; 226 } else { 227 int deletion = d[i - 1][j] + 1; 228 int insertion = d[i][j - 1] + 1; 229 int substitution = d[i - 1][j - 1] + 1; 230 d[i][j] = Math.min(deletion, Math.min(insertion, substitution)); 231 } 232 } 233 } 234 235 return d[m][n]; 236 } 237 238 /** 239 * Returns the current editor (the currently visible and active editor), or null if 240 * not found 241 * 242 * @return the current editor, or null 243 */ 244 public static IEditorPart getActiveEditor() { 245 IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); 246 if (window != null) { 247 IWorkbenchPage page = window.getActivePage(); 248 if (page != null) { 249 return page.getActiveEditor(); 250 } 251 } 252 253 return null; 254 } 255 256 /** 257 * Returns the current text editor (the currently visible and active editor), or null 258 * if not found. 259 * 260 * @return the current text editor, or null 261 */ 262 public static ITextEditor getActiveTextEditor() { 263 IEditorPart editor = getActiveEditor(); 264 if (editor != null) { 265 if (editor instanceof ITextEditor) { 266 return (ITextEditor) editor; 267 } else { 268 return (ITextEditor) editor.getAdapter(ITextEditor.class); 269 } 270 } 271 272 return null; 273 } 274 275 /** 276 * Returns the file for the current editor, if any. 277 * 278 * @return the file for the current editor, or null if none 279 */ 280 public static IFile getActiveFile() { 281 IEditorPart editor = getActiveEditor(); 282 if (editor != null) { 283 IEditorInput input = editor.getEditorInput(); 284 if (input instanceof IFileEditorInput) { 285 IFileEditorInput fileInput = (IFileEditorInput) input; 286 return fileInput.getFile(); 287 } 288 } 289 290 return null; 291 } 292 293 /** 294 * Returns an absolute path to the given resource 295 * 296 * @param resource the resource to look up a path for 297 * @return an absolute file system path to the resource 298 */ 299 public static IPath getAbsolutePath(IResource resource) { 300 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 301 IPath workspacePath = workspace.getLocation(); 302 return workspacePath.append(resource.getFullPath()); 303 } 304 305 /** 306 * Converts a {@link File} to an {@link IFile}, if possible. 307 * 308 * @param file a file to be converted 309 * @return the corresponding {@link IFile}, or null 310 */ 311 public static IFile fileToIFile(File file) { 312 IPath filePath = new Path(file.getPath()); 313 return pathToIFile(filePath); 314 } 315 316 /** 317 * Converts a {@link IPath} to an {@link IFile}, if possible. 318 * 319 * @param path a path to be converted 320 * @return the corresponding {@link IFile}, or null 321 */ 322 public static IFile pathToIFile(IPath path) { 323 IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); 324 IPath workspacePath = workspace.getLocation(); 325 if (workspacePath.isPrefixOf(path)) { 326 IPath relativePath = path.makeRelativeTo(workspacePath); 327 IResource member = workspace.findMember(relativePath); 328 if (member instanceof IFile) { 329 return (IFile) member; 330 } 331 } else if (path.isAbsolute()) { 332 return workspace.getFileForLocation(path); 333 } 334 335 return null; 336 } 337 338 /** 339 * Returns all markers in a file/document that fit on the same line as the given offset 340 * 341 * @param markerType the marker type 342 * @param file the file containing the markers 343 * @param document the document showing the markers 344 * @param offset the offset to be checked 345 * @return a list (possibly empty but never null) of matching markers 346 */ 347 public static List<IMarker> findMarkersOnLine(String markerType, 348 IFile file, IDocument document, int offset) { 349 List<IMarker> matchingMarkers = new ArrayList<IMarker>(2); 350 try { 351 IMarker[] markers = file.findMarkers(markerType, true, IResource.DEPTH_ZERO); 352 353 // Look for a match on the same line as the caret. 354 IRegion lineInfo = document.getLineInformationOfOffset(offset); 355 int lineStart = lineInfo.getOffset(); 356 int lineEnd = lineStart + lineInfo.getLength(); 357 int offsetLine = document.getLineOfOffset(offset); 358 359 360 for (IMarker marker : markers) { 361 int start = marker.getAttribute(IMarker.CHAR_START, -1); 362 int end = marker.getAttribute(IMarker.CHAR_END, -1); 363 if (start >= lineStart && start <= lineEnd && end > start) { 364 matchingMarkers.add(marker); 365 } else if (start == -1 && end == -1) { 366 // Some markers don't set character range, they only set the line 367 int line = marker.getAttribute(IMarker.LINE_NUMBER, -1); 368 if (line == offsetLine + 1) { 369 matchingMarkers.add(marker); 370 } 371 } 372 } 373 } catch (CoreException e) { 374 AdtPlugin.log(e, null); 375 } catch (BadLocationException e) { 376 AdtPlugin.log(e, null); 377 } 378 379 return matchingMarkers; 380 } 381 } 382