1 /* 2 * Copyright (c) 2008-2019 Emmanuel Dupuy. 3 * This project is distributed under the GPLv3 license. 4 * This is a Copyleft license that gives the user the right to use, 5 * copy and modify the code freely for non-commercial purposes. 6 */ 7 package org.jd.gui.view.component; 8 9 import org.fife.ui.rsyntaxtextarea.DocumentRange; 10 import org.fife.ui.rtextarea.Marker; 11 import org.jd.gui.api.API; 12 import org.jd.gui.api.feature.FocusedTypeGettable; 13 import org.jd.gui.api.feature.IndexesChangeListener; 14 import org.jd.gui.api.feature.UriGettable; 15 import org.jd.gui.api.feature.UriOpenable; 16 import org.jd.gui.api.model.Container; 17 import org.jd.gui.api.model.Indexes; 18 import org.jd.gui.api.model.Type; 19 import org.jd.gui.util.exception.ExceptionUtil; 20 import org.jd.gui.util.index.IndexesUtil; 21 import org.jd.gui.util.matcher.DescriptorMatcher; 22 23 import java.awt.*; 24 import java.net.URI; 25 import java.net.URISyntaxException; 26 import java.util.*; 27 import java.util.List; 28 import java.util.concurrent.Future; 29 import java.util.function.BiFunction; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 public abstract class TypePage extends CustomLineNumbersPage implements UriGettable, UriOpenable, IndexesChangeListener, FocusedTypeGettable { 34 protected API api; 35 protected Container.Entry entry; 36 protected Collection<Future<Indexes>> collectionOfFutureIndexes = Collections.emptyList(); 37 38 protected HashMap<String, DeclarationData> declarations = new HashMap<>(); 39 protected TreeMap<Integer, DeclarationData> typeDeclarations = new TreeMap<>(); 40 protected ArrayList<ReferenceData> references = new ArrayList<>(); 41 protected ArrayList<StringData> strings = new ArrayList<>(); 42 TypePage(API api, Container.Entry entry)43 public TypePage(API api, Container.Entry entry) { 44 // Init attributes 45 this.api = api; 46 this.entry = entry; 47 } 48 49 @Override isHyperlinkEnabled(HyperlinkData hyperlinkData)50 protected boolean isHyperlinkEnabled(HyperlinkData hyperlinkData) { 51 return ((HyperlinkReferenceData)hyperlinkData).reference.enabled; 52 } 53 54 @Override 55 @SuppressWarnings("unchecked") openHyperlink(int x, int y, HyperlinkData hyperlinkData)56 protected void openHyperlink(int x, int y, HyperlinkData hyperlinkData) { 57 HyperlinkReferenceData hyperlinkReferenceData = (HyperlinkReferenceData)hyperlinkData; 58 59 if (hyperlinkReferenceData.reference.enabled) { 60 try { 61 // Save current position in history 62 Point location = textArea.getLocationOnScreen(); 63 int offset = textArea.viewToModel(new Point(x - location.x, y - location.y)); 64 URI uri = entry.getUri(); 65 api.addURI(new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), "position=" + offset, null)); 66 67 // Open link 68 ReferenceData reference = hyperlinkReferenceData.reference; 69 String typeName = reference.typeName; 70 List<Container.Entry> entries = IndexesUtil.findInternalTypeName(collectionOfFutureIndexes, typeName); 71 String fragment = typeName; 72 73 if (reference.name != null) { 74 fragment += '-' + reference.name; 75 } 76 if (reference.descriptor != null) { 77 fragment += '-' + reference.descriptor; 78 } 79 80 if (entries.contains(entry)) { 81 api.openURI(new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), fragment)); 82 } else { 83 String rootUri = entry.getContainer().getRoot().getUri().toString(); 84 ArrayList<Container.Entry> sameContainerEntries = new ArrayList<>(); 85 86 for (Container.Entry entry : entries) { 87 if (entry.getUri().toString().startsWith(rootUri)) { 88 sameContainerEntries.add(entry); 89 } 90 } 91 92 if (sameContainerEntries.size() > 0) { 93 api.openURI(x, y, sameContainerEntries, null, fragment); 94 } else if (entries.size() > 0) { 95 api.openURI(x, y, entries, null, fragment); 96 } 97 } 98 } catch (URISyntaxException e) { 99 assert ExceptionUtil.printStackTrace(e); 100 } 101 } 102 } 103 104 // --- UriGettable --- // getUri()105 @Override public URI getUri() { return entry.getUri(); } 106 107 // --- UriOpenable --- // 108 /** 109 * @param uri for URI format, @see jd.gui.api.feature.UriOpenable 110 */ 111 @Override openUri(URI uri)112 public boolean openUri(URI uri) { 113 ArrayList<DocumentRange> ranges = new ArrayList<>(); 114 String fragment = uri.getFragment(); 115 String query = uri.getQuery(); 116 117 Marker.clearMarkAllHighlights(textArea); 118 119 if (fragment != null) { 120 matchFragmentAndAddDocumentRange(fragment, declarations, ranges); 121 } 122 123 if (query != null) { 124 Map<String, String> parameters = parseQuery(query); 125 126 if (parameters.containsKey("lineNumber")) { 127 String lineNumber = parameters.get("lineNumber"); 128 129 try { 130 goToLineNumber(Integer.parseInt(lineNumber)); 131 return true; 132 } catch (NumberFormatException e) { 133 assert ExceptionUtil.printStackTrace(e); 134 } 135 } else if (parameters.containsKey("position")) { 136 String position = parameters.get("position"); 137 138 try { 139 int pos = Integer.parseInt(position); 140 if (textArea.getDocument().getLength() > pos) { 141 ranges.add(new DocumentRange(pos, pos)); 142 } 143 } catch (NumberFormatException e) { 144 assert ExceptionUtil.printStackTrace(e); 145 } 146 } else { 147 matchQueryAndAddDocumentRange(parameters, declarations, hyperlinks, strings, ranges); 148 } 149 } 150 151 if ((ranges != null) && !ranges.isEmpty()) { 152 textArea.setMarkAllHighlightColor(SELECT_HIGHLIGHT_COLOR); 153 Marker.markAll(textArea, ranges); 154 ranges.sort(null); 155 setCaretPositionAndCenter(ranges.get(0)); 156 } 157 158 return true; 159 } 160 matchFragmentAndAddDocumentRange(String fragment, HashMap<String, DeclarationData> declarations, List<DocumentRange> ranges)161 public static void matchFragmentAndAddDocumentRange(String fragment, HashMap<String, DeclarationData> declarations, List<DocumentRange> ranges) { 162 if ((fragment.indexOf('?') != -1) || (fragment.indexOf('*') != -1)) { 163 // Unknown type and/or descriptor ==> Select all and scroll to the first one 164 int lastDash = fragment.lastIndexOf('-'); 165 166 if (lastDash == -1) { 167 // Search types 168 String slashAndTypeName = fragment.substring(1); 169 String typeName = fragment.substring(2); 170 171 for (Map.Entry<String, DeclarationData> entry : declarations.entrySet()) { 172 if (entry.getKey().endsWith(slashAndTypeName) || entry.getKey().equals(typeName)) { 173 ranges.add(new DocumentRange(entry.getValue().startPosition, entry.getValue().endPosition)); 174 } 175 } 176 } else { 177 String prefix = fragment.substring(0, lastDash+1); 178 String suffix = fragment.substring(lastDash+1); 179 BiFunction<String, String, Boolean> matchDescriptors; 180 181 if (suffix.charAt(0) == '(') { 182 matchDescriptors = DescriptorMatcher::matchMethodDescriptors; 183 } else { 184 matchDescriptors = DescriptorMatcher::matchFieldDescriptors; 185 } 186 187 if (fragment.charAt(0) == '*') { 188 // Unknown type 189 String slashAndTypeNameAndName = prefix.substring(1); 190 String typeNameAndName = prefix.substring(2); 191 192 for (Map.Entry<String, DeclarationData> entry : declarations.entrySet()) { 193 String key = entry.getKey(); 194 if ((key.indexOf(slashAndTypeNameAndName) != -1) || (key.startsWith(typeNameAndName))) { 195 int index = key.lastIndexOf('-') + 1; 196 if (matchDescriptors.apply(suffix, key.substring(index))) { 197 ranges.add(new DocumentRange(entry.getValue().startPosition, entry.getValue().endPosition)); 198 } 199 } 200 } 201 } else { 202 // Known type 203 for (Map.Entry<String, DeclarationData> entry : declarations.entrySet()) { 204 String key = entry.getKey(); 205 if (key.startsWith(prefix)) { 206 int index = key.lastIndexOf('-') + 1; 207 if (matchDescriptors.apply(suffix, key.substring(index))) { 208 ranges.add(new DocumentRange(entry.getValue().startPosition, entry.getValue().endPosition)); 209 } 210 } 211 } 212 } 213 } 214 } else { 215 // Known type and descriptor ==> Search and high light item 216 DeclarationData data = declarations.get(fragment); 217 if (data != null) { 218 ranges.add(new DocumentRange(data.startPosition, data.endPosition)); 219 } else if (fragment.endsWith("-<clinit>-()V")) { 220 // 'static' bloc not found ==> Select type declaration 221 String typeName = fragment.substring(0, fragment.indexOf('-')); 222 data = declarations.get(typeName); 223 ranges.add(new DocumentRange(data.startPosition, data.endPosition)); 224 } 225 } 226 } 227 matchQueryAndAddDocumentRange( Map<String, String> parameters, HashMap<String, DeclarationData> declarations, TreeMap<Integer, HyperlinkData> hyperlinks, ArrayList<StringData> strings, List<DocumentRange> ranges)228 public static void matchQueryAndAddDocumentRange( 229 Map<String, String> parameters, 230 HashMap<String, DeclarationData> declarations, TreeMap<Integer, HyperlinkData> hyperlinks, ArrayList<StringData> strings, 231 List<DocumentRange> ranges) { 232 233 String highlightFlags = parameters.get("highlightFlags"); 234 String highlightPattern = parameters.get("highlightPattern"); 235 236 if ((highlightFlags != null) && (highlightPattern != null)) { 237 String highlightScope = parameters.get("highlightScope"); 238 String regexp = createRegExp(highlightPattern); 239 Pattern pattern = Pattern.compile(regexp + ".*"); 240 241 if (highlightFlags.indexOf('s') != -1) { 242 // Highlight strings 243 Pattern patternForString = Pattern.compile(regexp); 244 245 for (StringData data : strings) { 246 if (matchScope(highlightScope, data.owner)) { 247 Matcher matcher = patternForString.matcher(data.text); 248 int offset = data.startPosition; 249 250 while(matcher.find()) { 251 ranges.add(new DocumentRange(offset + matcher.start(), offset + matcher.end())); 252 } 253 } 254 } 255 } 256 257 boolean t = (highlightFlags.indexOf('t') != -1); // Highlight types 258 boolean f = (highlightFlags.indexOf('f') != -1); // Highlight fields 259 boolean m = (highlightFlags.indexOf('m') != -1); // Highlight methods 260 boolean c = (highlightFlags.indexOf('c') != -1); // Highlight constructors 261 262 if (highlightFlags.indexOf('d') != -1) { 263 // Highlight declarations 264 for (Map.Entry<String, DeclarationData> entry : declarations.entrySet()) { 265 DeclarationData declaration = entry.getValue(); 266 267 if (matchScope(highlightScope, declaration.typeName)) { 268 if ((t && declaration.isAType()) || (c && declaration.isAConstructor())) { 269 matchAndAddDocumentRange(pattern, getMostInnerTypeName(declaration.typeName), declaration.startPosition, declaration.endPosition, ranges); 270 } 271 if ((f && declaration.isAField()) || (m && declaration.isAMethod())) { 272 matchAndAddDocumentRange(pattern, declaration.name, declaration.startPosition, declaration.endPosition, ranges); 273 } 274 } 275 } 276 } 277 278 if (highlightFlags.indexOf('r') != -1) { 279 // Highlight references 280 for (Map.Entry<Integer, HyperlinkData> entry : hyperlinks.entrySet()) { 281 HyperlinkData hyperlink = entry.getValue(); 282 ReferenceData reference = ((HyperlinkReferenceData)hyperlink).reference; 283 284 if (matchScope(highlightScope, reference.owner)) { 285 if ((t && reference.isAType()) || (c && reference.isAConstructor())) { 286 matchAndAddDocumentRange(pattern, getMostInnerTypeName(reference.typeName), hyperlink.startPosition, hyperlink.endPosition, ranges); 287 } 288 if ((f && reference.isAField()) || (m && reference.isAMethod())) { 289 matchAndAddDocumentRange(pattern, reference.name, hyperlink.startPosition, hyperlink.endPosition, ranges); 290 } 291 } 292 } 293 } 294 } 295 } 296 matchScope(String scope, String type)297 public static boolean matchScope(String scope, String type) { 298 if ((scope == null) || scope.isEmpty()) 299 return true; 300 if (scope.charAt(0) == '*') 301 return type.endsWith(scope.substring(1)) || type.equals(scope.substring(2)); 302 return type.equals(scope); 303 } 304 matchAndAddDocumentRange(Pattern pattern, String text, int start, int end, List<DocumentRange> ranges)305 public static void matchAndAddDocumentRange(Pattern pattern, String text, int start, int end, List<DocumentRange> ranges) { 306 if (pattern.matcher(text).matches()) { 307 ranges.add(new DocumentRange(start, end)); 308 } 309 } 310 getMostInnerTypeName(String typeName)311 public static String getMostInnerTypeName(String typeName) { 312 int lastPackageSeparatorIndex = typeName.lastIndexOf('/') + 1; 313 int lastTypeNameSeparatorIndex = typeName.lastIndexOf('$') + 1; 314 int lastIndex = Math.max(lastPackageSeparatorIndex, lastTypeNameSeparatorIndex); 315 return typeName.substring(lastIndex); 316 } 317 318 // --- FocusedTypeGettable --- // getFocusedTypeName()319 @Override public String getFocusedTypeName() { 320 Map.Entry<Integer, DeclarationData> entry = typeDeclarations.floorEntry(textArea.getCaretPosition()); 321 322 if (entry != null) { 323 DeclarationData data = entry.getValue(); 324 if (data != null) { 325 return data.typeName; 326 } 327 } 328 329 return null; 330 } 331 getEntry()332 @Override public Container.Entry getEntry() { return entry; } 333 334 // --- IndexesChangeListener --- // 335 @Override indexesChanged(Collection<Future<Indexes>> collectionOfFutureIndexes)336 public void indexesChanged(Collection<Future<Indexes>> collectionOfFutureIndexes) { 337 // Update the list of containers 338 this.collectionOfFutureIndexes = collectionOfFutureIndexes; 339 // Refresh links 340 boolean refresh = false; 341 342 for (ReferenceData reference : references) { 343 String typeName = reference.typeName; 344 boolean enabled; 345 346 if (reference.name == null) { 347 enabled = false; 348 349 try { 350 for (Future<Indexes> futureIndexes : collectionOfFutureIndexes) { 351 if (futureIndexes.isDone()) { 352 Map<String, Collection> index = futureIndexes.get().getIndex("typeDeclarations"); 353 if ((index != null) && (index.get(typeName) != null)) { 354 enabled = true; 355 break; 356 } 357 } 358 } 359 } catch (Exception e) { 360 assert ExceptionUtil.printStackTrace(e); 361 } 362 } else { 363 try { 364 // Recursive search 365 typeName = searchTypeHavingMember(typeName, reference.name, reference.descriptor, entry); 366 if (typeName != null) { 367 // Replace type with the real type having the referenced member 368 reference.typeName = typeName; 369 enabled = true; 370 } else { 371 enabled = false; 372 } 373 } catch (Error e) { 374 // Catch StackOverflowError or OutOfMemoryError 375 assert ExceptionUtil.printStackTrace(e); 376 enabled = false; 377 } 378 } 379 380 if (reference.enabled != enabled) { 381 reference.enabled = enabled; 382 refresh = true; 383 } 384 } 385 386 if (refresh) { 387 textArea.repaint(); 388 } 389 } 390 391 @SuppressWarnings("unchecked") searchTypeHavingMember(String typeName, String name, String descriptor, Container.Entry entry)392 protected String searchTypeHavingMember(String typeName, String name, String descriptor, Container.Entry entry) { 393 ArrayList<Container.Entry> entries = new ArrayList<>(); 394 395 try { 396 for (Future<Indexes> futureIndexes : collectionOfFutureIndexes) { 397 if (futureIndexes.isDone()) { 398 Map<String, Collection> index = futureIndexes.get().getIndex("typeDeclarations"); 399 if (index != null) { 400 Collection<Container.Entry> collection = index.get(typeName); 401 if (collection != null) { 402 entries.addAll(collection); 403 } 404 } 405 } 406 } 407 } catch (Exception e) { 408 assert ExceptionUtil.printStackTrace(e); 409 } 410 411 String rootUri = entry.getContainer().getRoot().getUri().toString(); 412 ArrayList<Container.Entry> sameContainerEntries = new ArrayList<>(); 413 414 for (Container.Entry e : entries) { 415 if (e.getUri().toString().startsWith(rootUri)) { 416 sameContainerEntries.add(e); 417 } 418 } 419 420 if (sameContainerEntries.size() > 0) { 421 return searchTypeHavingMember(typeName, name, descriptor, sameContainerEntries); 422 } else { 423 return searchTypeHavingMember(typeName, name, descriptor, entries); 424 } 425 } 426 searchTypeHavingMember(String typeName, String name, String descriptor, List<Container.Entry> entries)427 protected String searchTypeHavingMember(String typeName, String name, String descriptor, List<Container.Entry> entries) { 428 for (Container.Entry entry : entries) { 429 Type type = api.getTypeFactory(entry).make(api, entry, typeName); 430 431 if (type != null) { 432 if (descriptor.indexOf('(') == -1) { 433 // Search a field 434 for (Type.Field field : type.getFields()) { 435 if (field.getName().equals(name) && DescriptorMatcher.matchFieldDescriptors(field.getDescriptor(), descriptor)) { 436 // Field found 437 return typeName; 438 } 439 } 440 } else { 441 // Search a method 442 for (Type.Method method : type.getMethods()) { 443 if (method.getName().equals(name) && DescriptorMatcher.matchMethodDescriptors(method.getDescriptor(), descriptor)) { 444 // Method found 445 return typeName; 446 } 447 } 448 } 449 450 // Not found -> Search in super type 451 String typeOwnerName = searchTypeHavingMember(type.getSuperName(), name, descriptor, entry); 452 if (typeOwnerName != null) { 453 return typeOwnerName; 454 } 455 } 456 } 457 458 return null; 459 } 460 461 public static class StringData { 462 int startPosition; 463 int endPosition; 464 String text; 465 String owner; 466 StringData(int startPosition, int length, String text, String owner)467 public StringData(int startPosition, int length, String text, String owner) { 468 this.startPosition = startPosition; 469 this.endPosition = startPosition + length; 470 this.text = text; 471 this.owner = owner; 472 } 473 } 474 475 public static class DeclarationData { 476 int startPosition; 477 int endPosition; 478 String typeName; 479 /** 480 * Field or method name or null for type 481 */ 482 String name; 483 String descriptor; 484 DeclarationData(int startPosition, int length, String typeName, String name, String descriptor)485 public DeclarationData(int startPosition, int length, String typeName, String name, String descriptor) { 486 this.startPosition = startPosition; 487 this.endPosition = startPosition + length; 488 this.typeName = typeName; 489 this.name = name; 490 this.descriptor = descriptor; 491 } 492 isAType()493 public boolean isAType() { return name == null; } isAField()494 public boolean isAField() { return (descriptor != null) && descriptor.charAt(0) != '('; } isAMethod()495 public boolean isAMethod() { return (descriptor != null) && descriptor.charAt(0) == '('; } isAConstructor()496 public boolean isAConstructor() { return "<init>".equals(name); } 497 } 498 499 public static class HyperlinkReferenceData extends HyperlinkData { 500 public ReferenceData reference; 501 HyperlinkReferenceData(int startPosition, int length, ReferenceData reference)502 public HyperlinkReferenceData(int startPosition, int length, ReferenceData reference) { 503 super(startPosition, startPosition+length); 504 this.reference = reference; 505 } 506 } 507 508 protected static class ReferenceData { 509 public String typeName; 510 /** 511 * Field or method name or null for type 512 */ 513 public String name; 514 /** 515 * Field or method descriptor or null for type 516 */ 517 public String descriptor; 518 /** 519 * Internal type name containing reference or null for "import" statement. 520 * Used to high light items matching with URI like "file://dir1/dir2/file?highlightPattern=hello&highlightFlags=drtcmfs&highlightScope=type". 521 */ 522 public String owner; 523 /** 524 * "Enabled" flag for link of reference 525 */ 526 public boolean enabled = false; 527 ReferenceData(String typeName, String name, String descriptor, String owner)528 public ReferenceData(String typeName, String name, String descriptor, String owner) { 529 this.typeName = typeName; 530 this.name = name; 531 this.descriptor = descriptor; 532 this.owner = owner; 533 } 534 isAType()535 boolean isAType() { return name == null; } isAField()536 boolean isAField() { return (descriptor != null) && descriptor.charAt(0) != '('; } isAMethod()537 boolean isAMethod() { return (descriptor != null) && descriptor.charAt(0) == '('; } isAConstructor()538 boolean isAConstructor() { return "<init>".equals(name); } 539 } 540 } 541