1 /******************************************************************************* 2 * Copyright (c) 2000, 2019 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 * daolaf@gmail.com - Contribution for bug 3292227 14 *******************************************************************************/ 15 package org.eclipse.jdt.internal.compiler.util; 16 17 import java.io.BufferedInputStream; 18 import java.io.BufferedOutputStream; 19 import java.io.BufferedReader; 20 import java.io.ByteArrayInputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileOutputStream; 24 import java.io.IOException; 25 import java.io.InputStream; 26 import java.io.InputStreamReader; 27 import java.io.PrintWriter; 28 import java.io.StringWriter; 29 import java.io.UnsupportedEncodingException; 30 import java.util.ArrayList; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 import java.util.StringTokenizer; 35 import java.util.zip.ZipEntry; 36 import java.util.zip.ZipFile; 37 38 import org.eclipse.jdt.core.compiler.CharOperation; 39 import org.eclipse.jdt.internal.compiler.ClassFile; 40 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; 41 import org.eclipse.jdt.internal.compiler.batch.FileSystem; 42 import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; 43 import org.eclipse.jdt.internal.compiler.batch.Main; 44 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; 45 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; 46 import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; 47 import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; 48 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; 49 import org.eclipse.jdt.internal.compiler.lookup.TagBits; 50 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; 51 import org.eclipse.jdt.internal.compiler.lookup.TypeVariableBinding; 52 import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding; 53 54 public class Util implements SuffixConstants { 55 56 /** 57 * Character constant indicating the primitive type boolean in a signature. 58 * Value is <code>'Z'</code>. 59 */ 60 public static final char C_BOOLEAN = 'Z'; 61 62 /** 63 * Character constant indicating the primitive type byte in a signature. 64 * Value is <code>'B'</code>. 65 */ 66 public static final char C_BYTE = 'B'; 67 68 /** 69 * Character constant indicating the primitive type char in a signature. 70 * Value is <code>'C'</code>. 71 */ 72 public static final char C_CHAR = 'C'; 73 74 /** 75 * Character constant indicating the primitive type double in a signature. 76 * Value is <code>'D'</code>. 77 */ 78 public static final char C_DOUBLE = 'D'; 79 80 /** 81 * Character constant indicating the primitive type float in a signature. 82 * Value is <code>'F'</code>. 83 */ 84 public static final char C_FLOAT = 'F'; 85 86 /** 87 * Character constant indicating the primitive type int in a signature. 88 * Value is <code>'I'</code>. 89 */ 90 public static final char C_INT = 'I'; 91 92 /** 93 * Character constant indicating the semicolon in a signature. 94 * Value is <code>';'</code>. 95 */ 96 public static final char C_SEMICOLON = ';'; 97 98 /** 99 * Character constant indicating the colon in a signature. 100 * Value is <code>':'</code>. 101 * @since 3.0 102 */ 103 public static final char C_COLON = ':'; 104 105 /** 106 * Character constant indicating the primitive type long in a signature. 107 * Value is <code>'J'</code>. 108 */ 109 public static final char C_LONG = 'J'; 110 111 /** 112 * Character constant indicating the primitive type short in a signature. 113 * Value is <code>'S'</code>. 114 */ 115 public static final char C_SHORT = 'S'; 116 117 /** 118 * Character constant indicating result type void in a signature. 119 * Value is <code>'V'</code>. 120 */ 121 public static final char C_VOID = 'V'; 122 123 /** 124 * Character constant indicating the start of a resolved type variable in a 125 * signature. Value is <code>'T'</code>. 126 * @since 3.0 127 */ 128 public static final char C_TYPE_VARIABLE = 'T'; 129 130 /** 131 * Character constant indicating an unbound wildcard type argument 132 * in a signature. 133 * Value is <code>'*'</code>. 134 * @since 3.0 135 */ 136 public static final char C_STAR = '*'; 137 138 /** 139 * Character constant indicating an exception in a signature. 140 * Value is <code>'^'</code>. 141 * @since 3.1 142 */ 143 public static final char C_EXCEPTION_START = '^'; 144 145 /** 146 * Character constant indicating a bound wildcard type argument 147 * in a signature with extends clause. 148 * Value is <code>'+'</code>. 149 * @since 3.1 150 */ 151 public static final char C_EXTENDS = '+'; 152 153 /** 154 * Character constant indicating a bound wildcard type argument 155 * in a signature with super clause. 156 * Value is <code>'-'</code>. 157 * @since 3.1 158 */ 159 public static final char C_SUPER = '-'; 160 161 /** 162 * Character constant indicating the dot in a signature. 163 * Value is <code>'.'</code>. 164 */ 165 public static final char C_DOT = '.'; 166 167 /** 168 * Character constant indicating the dollar in a signature. 169 * Value is <code>'$'</code>. 170 */ 171 public static final char C_DOLLAR = '$'; 172 173 /** 174 * Character constant indicating an array type in a signature. 175 * Value is <code>'['</code>. 176 */ 177 public static final char C_ARRAY = '['; 178 179 /** 180 * Character constant indicating the start of a resolved, named type in a 181 * signature. Value is <code>'L'</code>. 182 */ 183 public static final char C_RESOLVED = 'L'; 184 185 /** 186 * Character constant indicating the start of an unresolved, named type in a 187 * signature. Value is <code>'Q'</code>. 188 */ 189 public static final char C_UNRESOLVED = 'Q'; 190 191 /** 192 * Character constant indicating the end of a named type in a signature. 193 * Value is <code>';'</code>. 194 */ 195 public static final char C_NAME_END = ';'; 196 197 /** 198 * Character constant indicating the start of a parameter type list in a 199 * signature. Value is <code>'('</code>. 200 */ 201 public static final char C_PARAM_START = '('; 202 203 /** 204 * Character constant indicating the end of a parameter type list in a 205 * signature. Value is <code>')'</code>. 206 */ 207 public static final char C_PARAM_END = ')'; 208 209 /** 210 * Character constant indicating the start of a formal type parameter 211 * (or type argument) list in a signature. Value is <code>'<'</code>. 212 * @since 3.0 213 */ 214 public static final char C_GENERIC_START = '<'; 215 216 /** 217 * Character constant indicating the end of a generic type list in a 218 * signature. Value is <code>'>'</code>. 219 * @since 3.0 220 */ 221 public static final char C_GENERIC_END = '>'; 222 223 /** 224 * Character constant indicating a capture of a wildcard type in a 225 * signature. Value is <code>'!'</code>. 226 * @since 3.1 227 */ 228 public static final char C_CAPTURE = '!'; 229 230 public interface Displayable { displayString(Object o)231 String displayString(Object o); 232 } 233 234 private static final int DEFAULT_READING_SIZE = 8192; 235 private static final int DEFAULT_WRITING_SIZE = 1024; 236 public final static String UTF_8 = "UTF-8"; //$NON-NLS-1$ 237 public static final String LINE_SEPARATOR = System.getProperty("line.separator"); //$NON-NLS-1$ 238 239 public static final String EMPTY_STRING = new String(CharOperation.NO_CHAR); 240 /** 241 * @since 3.14 242 */ 243 public static final String COMMA_SEPARATOR = new String(CharOperation.COMMA_SEPARATOR); 244 public static final int[] EMPTY_INT_ARRAY= new int[0]; 245 246 /** 247 * Build all the directories and subdirectories corresponding to the packages names 248 * into the directory specified in parameters. 249 * 250 * outputPath is formed like: 251 * c:\temp\ the last character is a file separator 252 * relativeFileName is formed like: 253 * java\lang\String.class * 254 * 255 * @param outputPath java.lang.String 256 * @param relativeFileName java.lang.String 257 * @return java.lang.String 258 */ buildAllDirectoriesInto(String outputPath, String relativeFileName)259 public static String buildAllDirectoriesInto(String outputPath, String relativeFileName) throws IOException { 260 char fileSeparatorChar = File.separatorChar; 261 String fileSeparator = File.separator; 262 File f; 263 outputPath = outputPath.replace('/', fileSeparatorChar); 264 // these could be optimized out if we normalized paths once and for 265 // all 266 relativeFileName = relativeFileName.replace('/', fileSeparatorChar); 267 String outputDirPath, fileName; 268 int separatorIndex = relativeFileName.lastIndexOf(fileSeparatorChar); 269 if (separatorIndex == -1) { 270 if (outputPath.endsWith(fileSeparator)) { 271 outputDirPath = outputPath.substring(0, outputPath.length() - 1); 272 fileName = outputPath + relativeFileName; 273 } else { 274 outputDirPath = outputPath; 275 fileName = outputPath + fileSeparator + relativeFileName; 276 } 277 } else { 278 if (outputPath.endsWith(fileSeparator)) { 279 outputDirPath = outputPath + 280 relativeFileName.substring(0, separatorIndex); 281 fileName = outputPath + relativeFileName; 282 } else { 283 outputDirPath = outputPath + fileSeparator + 284 relativeFileName.substring(0, separatorIndex); 285 fileName = outputPath + fileSeparator + relativeFileName; 286 } 287 } 288 f = new File(outputDirPath); 289 f.mkdirs(); 290 if (f.isDirectory()) { 291 return fileName; 292 } else { 293 // the directory creation failed for some reason - retry using 294 // a slower algorithm so as to refine the diagnostic 295 if (outputPath.endsWith(fileSeparator)) { 296 outputPath = outputPath.substring(0, outputPath.length() - 1); 297 } 298 f = new File(outputPath); 299 boolean checkFileType = false; 300 if (f.exists()) { 301 checkFileType = true; // pre-existed 302 } else { 303 // we have to create that directory 304 if (!f.mkdirs()) { 305 if (f.exists()) { 306 // someone else created f -- need to check its type 307 checkFileType = true; 308 } else { 309 // no one could create f -- complain 310 throw new IOException(Messages.bind( 311 Messages.output_notValidAll, f.getAbsolutePath())); 312 } 313 } 314 } 315 if (checkFileType) { 316 if (!f.isDirectory()) { 317 throw new IOException(Messages.bind( 318 Messages.output_isFile, f.getAbsolutePath())); 319 } 320 } 321 StringBuffer outDir = new StringBuffer(outputPath); 322 outDir.append(fileSeparator); 323 StringTokenizer tokenizer = 324 new StringTokenizer(relativeFileName, fileSeparator); 325 String token = tokenizer.nextToken(); 326 while (tokenizer.hasMoreTokens()) { 327 f = new File(outDir.append(token).append(fileSeparator).toString()); 328 checkFileType = false; // reset 329 if (f.exists()) { 330 checkFileType = true; // this is suboptimal, but it catches corner cases 331 // in which a regular file pre-exists 332 } else { 333 // we have to create that directory 334 if (!f.mkdir()) { 335 if (f.exists()) { 336 // someone else created f -- need to check its type 337 checkFileType = true; 338 } else { 339 // no one could create f -- complain 340 throw new IOException(Messages.bind( 341 Messages.output_notValid, 342 outDir.substring(outputPath.length() + 1, 343 outDir.length() - 1), 344 outputPath)); 345 } 346 } 347 } 348 if (checkFileType) { 349 if (!f.isDirectory()) { 350 throw new IOException(Messages.bind( 351 Messages.output_isFile, f.getAbsolutePath())); 352 } 353 } 354 token = tokenizer.nextToken(); 355 } 356 // token contains the last one 357 return outDir.append(token).toString(); 358 } 359 } 360 361 /** 362 * Returns the given bytes as a char array using a given encoding (null means platform default). 363 */ bytesToChar(byte[] bytes, String encoding)364 public static char[] bytesToChar(byte[] bytes, String encoding) throws IOException { 365 366 return getInputStreamAsCharArray(new ByteArrayInputStream(bytes), bytes.length, encoding); 367 368 } 369 370 /** 371 * Returns the outer most enclosing type's visibility for the given TypeDeclaration 372 * and visibility based on compiler options. 373 */ computeOuterMostVisibility(TypeDeclaration typeDeclaration, int visibility)374 public static int computeOuterMostVisibility(TypeDeclaration typeDeclaration, int visibility) { 375 while (typeDeclaration != null) { 376 switch (typeDeclaration.modifiers & ExtraCompilerModifiers.AccVisibilityMASK) { 377 case ClassFileConstants.AccPrivate: 378 visibility = ClassFileConstants.AccPrivate; 379 break; 380 case ClassFileConstants.AccDefault: 381 if (visibility != ClassFileConstants.AccPrivate) { 382 visibility = ClassFileConstants.AccDefault; 383 } 384 break; 385 case ClassFileConstants.AccProtected: 386 if (visibility == ClassFileConstants.AccPublic) { 387 visibility = ClassFileConstants.AccProtected; 388 } 389 break; 390 } 391 typeDeclaration = typeDeclaration.enclosingType; 392 } 393 return visibility; 394 } 395 /** 396 * Returns the contents of the given file as a byte array. 397 * @throws IOException if a problem occured reading the file. 398 */ getFileByteContent(File file)399 public static byte[] getFileByteContent(File file) throws IOException { 400 InputStream stream = null; 401 try { 402 stream = new BufferedInputStream(new FileInputStream(file)); 403 return getInputStreamAsByteArray(stream, (int) file.length()); 404 } finally { 405 if (stream != null) { 406 try { 407 stream.close(); 408 } catch (IOException e) { 409 // ignore 410 } 411 } 412 } 413 } 414 /** 415 * Returns the contents of the given file as a char array. 416 * When encoding is null, then the platform default one is used 417 * @throws IOException if a problem occured reading the file. 418 */ getFileCharContent(File file, String encoding)419 public static char[] getFileCharContent(File file, String encoding) throws IOException { 420 InputStream stream = null; 421 try { 422 stream = new FileInputStream(file); 423 return getInputStreamAsCharArray(stream, (int) file.length(), encoding); 424 } finally { 425 if (stream != null) { 426 try { 427 stream.close(); 428 } catch (IOException e) { 429 // ignore 430 } 431 } 432 } 433 } getFileOutputStream(boolean generatePackagesStructure, String outputPath, String relativeFileName)434 private static FileOutputStream getFileOutputStream(boolean generatePackagesStructure, String outputPath, String relativeFileName) throws IOException { 435 if (generatePackagesStructure) { 436 return new FileOutputStream(new File(buildAllDirectoriesInto(outputPath, relativeFileName))); 437 } else { 438 String fileName = null; 439 char fileSeparatorChar = File.separatorChar; 440 String fileSeparator = File.separator; 441 // First we ensure that the outputPath exists 442 outputPath = outputPath.replace('/', fileSeparatorChar); 443 // To be able to pass the mkdirs() method we need to remove the extra file separator at the end of the outDir name 444 int indexOfPackageSeparator = relativeFileName.lastIndexOf(fileSeparatorChar); 445 if (indexOfPackageSeparator == -1) { 446 if (outputPath.endsWith(fileSeparator)) { 447 fileName = outputPath + relativeFileName; 448 } else { 449 fileName = outputPath + fileSeparator + relativeFileName; 450 } 451 } else { 452 int length = relativeFileName.length(); 453 if (outputPath.endsWith(fileSeparator)) { 454 fileName = outputPath + relativeFileName.substring(indexOfPackageSeparator + 1, length); 455 } else { 456 fileName = outputPath + fileSeparator + relativeFileName.substring(indexOfPackageSeparator + 1, length); 457 } 458 } 459 return new FileOutputStream(new File(fileName)); 460 } 461 } 462 463 /* 464 * NIO support to get input stream as byte array. 465 * Not used as with JDK 1.4.2 this support is slower than standard IO one... 466 * Keep it as comment for future in case of next JDK versions improve performance 467 * in this area... 468 * 469 public static byte[] getInputStreamAsByteArray(FileInputStream stream, int length) 470 throws IOException { 471 472 FileChannel channel = stream.getChannel(); 473 int size = (int)channel.size(); 474 if (length >= 0 && length < size) size = length; 475 byte[] contents = new byte[size]; 476 ByteBuffer buffer = ByteBuffer.wrap(contents); 477 channel.read(buffer); 478 return contents; 479 } 480 */ 481 /** 482 * Returns the given input stream's contents as a byte array. 483 * If a length is specified (i.e. if length != -1), only length bytes 484 * are returned. Otherwise all bytes in the stream are returned. 485 * Note this doesn't close the stream. 486 * @throws IOException if a problem occured reading the stream. 487 */ getInputStreamAsByteArray(InputStream stream, int length)488 public static byte[] getInputStreamAsByteArray(InputStream stream, int length) 489 throws IOException { 490 byte[] contents; 491 if (length == -1) { 492 contents = new byte[0]; 493 int contentsLength = 0; 494 int amountRead = -1; 495 do { 496 int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); // read at least 8K 497 498 // resize contents if needed 499 if (contentsLength + amountRequested > contents.length) { 500 System.arraycopy( 501 contents, 502 0, 503 contents = new byte[contentsLength + amountRequested], 504 0, 505 contentsLength); 506 } 507 508 // read as many bytes as possible 509 amountRead = stream.read(contents, contentsLength, amountRequested); 510 511 if (amountRead > 0) { 512 // remember length of contents 513 contentsLength += amountRead; 514 } 515 } while (amountRead != -1); 516 517 // resize contents if necessary 518 if (contentsLength < contents.length) { 519 System.arraycopy( 520 contents, 521 0, 522 contents = new byte[contentsLength], 523 0, 524 contentsLength); 525 } 526 } else { 527 contents = new byte[length]; 528 int len = 0; 529 int readSize = 0; 530 while ((readSize != -1) && (len != length)) { 531 // See PR 1FMS89U 532 // We record first the read size. In this case len is the actual read size. 533 len += readSize; 534 readSize = stream.read(contents, len, length - len); 535 } 536 } 537 538 return contents; 539 } 540 541 /* 542 * NIO support to get input stream as char array. 543 * Not used as with JDK 1.4.2 this support is slower than standard IO one... 544 * Keep it as comment for future in case of next JDK versions improve performance 545 * in this area... 546 public static char[] getInputStreamAsCharArray(FileInputStream stream, int length, String encoding) 547 throws IOException { 548 549 FileChannel channel = stream.getChannel(); 550 int size = (int)channel.size(); 551 if (length >= 0 && length < size) size = length; 552 Charset charset = encoding==null?systemCharset:Charset.forName(encoding); 553 if (charset != null) { 554 MappedByteBuffer bbuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size); 555 CharsetDecoder decoder = charset.newDecoder(); 556 CharBuffer buffer = decoder.decode(bbuffer); 557 char[] contents = new char[buffer.limit()]; 558 buffer.get(contents); 559 return contents; 560 } 561 throw new UnsupportedCharsetException(SYSTEM_FILE_ENCODING); 562 } 563 */ 564 /** 565 * Returns the given input stream's contents as a character array. 566 * If a length is specified (i.e. if length != -1), this represents the number of bytes in the stream. 567 * Note this doesn't close the stream. 568 * @throws IOException if a problem occured reading the stream. 569 */ getInputStreamAsCharArray(InputStream stream, int length, String encoding)570 public static char[] getInputStreamAsCharArray(InputStream stream, int length, String encoding) 571 throws IOException { 572 BufferedReader reader = null; 573 try { 574 reader = encoding == null 575 ? new BufferedReader(new InputStreamReader(stream)) 576 : new BufferedReader(new InputStreamReader(stream, encoding)); 577 } catch (UnsupportedEncodingException e) { 578 // encoding is not supported 579 reader = new BufferedReader(new InputStreamReader(stream)); 580 } 581 char[] contents; 582 int totalRead = 0; 583 if (length == -1) { 584 contents = CharOperation.NO_CHAR; 585 } else { 586 // length is a good guess when the encoding produces less or the same amount of characters than the file length 587 contents = new char[length]; // best guess 588 } 589 590 while (true) { 591 int amountRequested; 592 if (totalRead < length) { 593 // until known length is met, reuse same array sized eagerly 594 amountRequested = length - totalRead; 595 } else { 596 // reading beyond known length 597 int current = reader.read(); 598 if (current < 0) break; 599 600 amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); // read at least 8K 601 602 // resize contents if needed 603 if (totalRead + 1 + amountRequested > contents.length) 604 System.arraycopy(contents, 0, contents = new char[totalRead + 1 + amountRequested], 0, totalRead); 605 606 // add current character 607 contents[totalRead++] = (char) current; // coming from totalRead==length 608 } 609 // read as many chars as possible 610 int amountRead = reader.read(contents, totalRead, amountRequested); 611 if (amountRead < 0) break; 612 totalRead += amountRead; 613 } 614 615 // Do not keep first character for UTF-8 BOM encoding 616 int start = 0; 617 if (totalRead > 0 && UTF_8.equals(encoding)) { 618 if (contents[0] == 0xFEFF) { // if BOM char then skip 619 totalRead--; 620 start = 1; 621 } 622 } 623 624 // resize contents if necessary 625 if (totalRead < contents.length) 626 System.arraycopy(contents, start, contents = new char[totalRead], 0, totalRead); 627 628 return contents; 629 } 630 631 /** 632 * Returns a one line summary for an exception (extracted from its stacktrace: name + first frame) 633 * @param exception 634 * @return one line summary for an exception 635 */ getExceptionSummary(Throwable exception)636 public static String getExceptionSummary(Throwable exception) { 637 StringWriter stringWriter = new StringWriter(); 638 exception.printStackTrace(new PrintWriter(stringWriter)); 639 StringBuffer buffer = stringWriter.getBuffer(); 640 StringBuffer exceptionBuffer = new StringBuffer(50); 641 exceptionBuffer.append(exception.toString()); 642 // only keep leading frame portion of the trace (i.e. line no. 2 from the stacktrace) 643 lookupLine2: for (int i = 0, lineSep = 0, max = buffer.length(), line2Start = 0; i < max; i++) { 644 switch (buffer.charAt(i)) { 645 case '\n': 646 case '\r' : 647 if (line2Start > 0) { 648 exceptionBuffer.append(' ').append(buffer.substring(line2Start, i)); 649 break lookupLine2; 650 } 651 lineSep++; 652 break; 653 case ' ' : 654 case '\t' : 655 break; 656 default : 657 if (lineSep > 0) { 658 line2Start = i; 659 lineSep = 0; 660 } 661 break; 662 } 663 } 664 return exceptionBuffer.toString(); 665 } 666 getLineNumber(int position, int[] lineEnds, int g, int d)667 public static int getLineNumber(int position, int[] lineEnds, int g, int d) { 668 if (lineEnds == null) 669 return 1; 670 if (d == -1) 671 return 1; 672 int m = g, start; 673 while (g <= d) { 674 m = g + (d - g) /2; 675 if (position < (start = lineEnds[m])) { 676 d = m-1; 677 } else if (position > start) { 678 g = m+1; 679 } else { 680 return m + 1; 681 } 682 } 683 if (position < lineEnds[m]) { 684 return m+1; 685 } 686 return m+2; 687 } 688 /** 689 * Returns the contents of the given zip entry as a byte array. 690 * @throws IOException if a problem occurred reading the zip entry. 691 */ getZipEntryByteContent(ZipEntry ze, ZipFile zip)692 public static byte[] getZipEntryByteContent(ZipEntry ze, ZipFile zip) 693 throws IOException { 694 695 InputStream stream = null; 696 try { 697 InputStream inputStream = zip.getInputStream(ze); 698 if (inputStream == null) throw new IOException("Invalid zip entry name : " + ze.getName()); //$NON-NLS-1$ 699 stream = new BufferedInputStream(inputStream); 700 return getInputStreamAsByteArray(stream, (int) ze.getSize()); 701 } finally { 702 if (stream != null) { 703 try { 704 stream.close(); 705 } catch (IOException e) { 706 // ignore 707 } 708 } 709 } 710 } hashCode(Object[] array)711 public static int hashCode(Object[] array) { 712 int prime = 31; 713 if (array == null) { 714 return 0; 715 } 716 int result = 1; 717 for (int index = 0; index < array.length; index++) { 718 result = prime * result + (array[index] == null ? 0 : array[index].hashCode()); 719 } 720 return result; 721 } 722 /** 723 * Returns whether the given name is potentially a zip archive file name 724 * (it has a file extension and it is not ".java" nor ".class") 725 */ isPotentialZipArchive(String name)726 public final static boolean isPotentialZipArchive(String name) { 727 int lastDot = name.lastIndexOf('.'); 728 if (lastDot == -1) 729 return false; // no file extension, it cannot be a zip archive name 730 if (name.lastIndexOf(File.separatorChar) > lastDot) 731 return false; // dot was before the last file separator, it cannot be a zip archive name 732 int length = name.length(); 733 int extensionLength = length - lastDot - 1; 734 if (extensionLength == EXTENSION_java.length()) { 735 for (int i = extensionLength-1; i >=0; i--) { 736 if (Character.toLowerCase(name.charAt(length - extensionLength + i)) != EXTENSION_java.charAt(i)) { 737 break; // not a ".java" file, check ".class" file case below 738 } 739 if (i == 0) { 740 return false; // it is a ".java" file, it cannot be a zip archive name 741 } 742 } 743 } 744 if (extensionLength == EXTENSION_class.length()) { 745 for (int i = extensionLength-1; i >=0; i--) { 746 if (Character.toLowerCase(name.charAt(length - extensionLength + i)) != EXTENSION_class.charAt(i)) { 747 return true; // not a ".class" file, so this is a potential archive name 748 } 749 } 750 return false; // it is a ".class" file, it cannot be a zip archive name 751 } 752 return true; // it is neither a ".java" file nor a ".class" file, so this is a potential archive name 753 } 754 755 public static final int ZIP_FILE = 0; 756 public static final int JMOD_FILE = 1; 757 758 /** 759 * Returns the kind of archive this file is. The format is one of 760 * #ZIP_FILE or {@link #JMOD_FILE} 761 */ archiveFormat(String name)762 public final static int archiveFormat(String name) { 763 int lastDot = name.lastIndexOf('.'); 764 if (lastDot == -1) 765 return -1; // no file extension, it cannot be a zip archive name 766 if (name.lastIndexOf(File.separatorChar) > lastDot) 767 return -1; // dot was before the last file separator, it cannot be a zip archive name 768 int length = name.length(); 769 int extensionLength = length - lastDot - 1; 770 771 if (extensionLength == EXTENSION_java.length()) { 772 for (int i = extensionLength-1; i >=0; i--) { 773 if (Character.toLowerCase(name.charAt(length - extensionLength + i)) != EXTENSION_java.charAt(i)) { 774 break; // not a ".java" file, check ".class" file case below 775 } 776 if (i == 0) { 777 return -1; // it is a ".java" file, it cannot be a zip archive name 778 } 779 } 780 } 781 if (extensionLength == EXTENSION_class.length()) { 782 for (int i = extensionLength-1; i >=0; i--) { 783 if (Character.toLowerCase(name.charAt(length - extensionLength + i)) != EXTENSION_class.charAt(i)) { 784 return ZIP_FILE; // not a ".class" file, so this is a potential archive name 785 } 786 } 787 return -1; // it is a ".class" file, it cannot be a zip archive name 788 } 789 if (extensionLength == EXTENSION_jmod.length()) { 790 for (int i = extensionLength-1; i >=0; i--) { 791 if (Character.toLowerCase(name.charAt(length - extensionLength + i)) != EXTENSION_jmod.charAt(i)) { 792 return ZIP_FILE; // not a ".jmod" file, so this is a potential archive name 793 } 794 } 795 return JMOD_FILE; 796 } 797 return ZIP_FILE; // it is neither a ".java" file nor a ".class" file, so this is a potential archive name 798 } 799 800 /** 801 * Returns true iff str.toLowerCase().endsWith(".class") 802 * implementation is not creating extra strings. 803 */ isClassFileName(char[] name)804 public final static boolean isClassFileName(char[] name) { 805 int nameLength = name == null ? 0 : name.length; 806 int suffixLength = SUFFIX_CLASS.length; 807 if (nameLength < suffixLength) return false; 808 809 for (int i = 0, offset = nameLength - suffixLength; i < suffixLength; i++) { 810 char c = name[offset + i]; 811 if (c != SUFFIX_class[i] && c != SUFFIX_CLASS[i]) return false; 812 } 813 return true; 814 } 815 /** 816 * Returns true iff str.toLowerCase().endsWith(".class") 817 * implementation is not creating extra strings. 818 */ isClassFileName(String name)819 public final static boolean isClassFileName(String name) { 820 int nameLength = name == null ? 0 : name.length(); 821 int suffixLength = SUFFIX_CLASS.length; 822 if (nameLength < suffixLength) return false; 823 824 for (int i = 0; i < suffixLength; i++) { 825 char c = name.charAt(nameLength - i - 1); 826 int suffixIndex = suffixLength - i - 1; 827 if (c != SUFFIX_class[suffixIndex] && c != SUFFIX_CLASS[suffixIndex]) return false; 828 } 829 return true; 830 } 831 /* TODO (philippe) should consider promoting it to CharOperation 832 * Returns whether the given resource path matches one of the inclusion/exclusion 833 * patterns. 834 * NOTE: should not be asked directly using pkg root pathes 835 * @see IClasspathEntry#getInclusionPatterns 836 * @see IClasspathEntry#getExclusionPatterns 837 */ isExcluded(char[] path, char[][] inclusionPatterns, char[][] exclusionPatterns, boolean isFolderPath)838 public final static boolean isExcluded(char[] path, char[][] inclusionPatterns, char[][] exclusionPatterns, boolean isFolderPath) { 839 if (inclusionPatterns == null && exclusionPatterns == null) return false; 840 841 inclusionCheck: if (inclusionPatterns != null) { 842 for (int i = 0, length = inclusionPatterns.length; i < length; i++) { 843 char[] pattern = inclusionPatterns[i]; 844 char[] folderPattern = pattern; 845 if (isFolderPath) { 846 int lastSlash = CharOperation.lastIndexOf('/', pattern); 847 if (lastSlash != -1 && lastSlash != pattern.length-1){ // trailing slash -> adds '**' for free (see http://ant.apache.org/manual/dirtasks.html) 848 int star = CharOperation.indexOf('*', pattern, lastSlash); 849 if ((star == -1 850 || star >= pattern.length-1 851 || pattern[star+1] != '*')) { 852 folderPattern = CharOperation.subarray(pattern, 0, lastSlash); 853 } 854 } 855 } 856 if (CharOperation.pathMatch(folderPattern, path, true, '/')) { 857 break inclusionCheck; 858 } 859 } 860 return true; // never included 861 } 862 if (isFolderPath) { 863 path = CharOperation.concat(path, new char[] {'*'}, '/'); 864 } 865 if (exclusionPatterns != null) { 866 for (int i = 0, length = exclusionPatterns.length; i < length; i++) { 867 if (CharOperation.pathMatch(exclusionPatterns[i], path, true, '/')) { 868 return true; 869 } 870 } 871 } 872 return false; 873 } 874 875 /** 876 * Returns true iff str.toLowerCase().endsWith(".java") 877 * implementation is not creating extra strings. 878 */ isJavaFileName(char[] name)879 public final static boolean isJavaFileName(char[] name) { 880 int nameLength = name == null ? 0 : name.length; 881 int suffixLength = SUFFIX_JAVA.length; 882 if (nameLength < suffixLength) return false; 883 884 for (int i = 0, offset = nameLength - suffixLength; i < suffixLength; i++) { 885 char c = name[offset + i]; 886 if (c != SUFFIX_java[i] && c != SUFFIX_JAVA[i]) return false; 887 } 888 return true; 889 } 890 891 /** 892 * Returns true iff str.toLowerCase().endsWith(".java") 893 * implementation is not creating extra strings. 894 */ isJavaFileName(String name)895 public final static boolean isJavaFileName(String name) { 896 int nameLength = name == null ? 0 : name.length(); 897 int suffixLength = SUFFIX_JAVA.length; 898 if (nameLength < suffixLength) return false; 899 900 for (int i = 0; i < suffixLength; i++) { 901 char c = name.charAt(nameLength - i - 1); 902 int suffixIndex = suffixLength - i - 1; 903 if (c != SUFFIX_java[suffixIndex] && c != SUFFIX_JAVA[suffixIndex]) return false; 904 } 905 return true; 906 } 907 908 /** 909 * Returns true iff str.toLowerCase().endsWith("jrt-fs.jar") 910 * implementation is not creating extra strings. 911 */ isJrt(String name)912 public final static boolean isJrt(String name) { 913 return name.endsWith(JRTUtil.JRT_FS_JAR); 914 } 915 reverseQuickSort(char[][] list, int left, int right)916 public static void reverseQuickSort(char[][] list, int left, int right) { 917 int original_left= left; 918 int original_right= right; 919 char[] mid= list[left + ((right-left)/2)]; 920 do { 921 while (CharOperation.compareTo(list[left], mid) > 0) { 922 left++; 923 } 924 while (CharOperation.compareTo(mid, list[right]) > 0) { 925 right--; 926 } 927 if (left <= right) { 928 char[] tmp= list[left]; 929 list[left]= list[right]; 930 list[right]= tmp; 931 left++; 932 right--; 933 } 934 } while (left <= right); 935 if (original_left < right) { 936 reverseQuickSort(list, original_left, right); 937 } 938 if (left < original_right) { 939 reverseQuickSort(list, left, original_right); 940 } 941 } reverseQuickSort(char[][] list, int left, int right, int[] result)942 public static void reverseQuickSort(char[][] list, int left, int right, int[] result) { 943 int original_left= left; 944 int original_right= right; 945 char[] mid= list[left + ((right-left)/2)]; 946 do { 947 while (CharOperation.compareTo(list[left], mid) > 0) { 948 left++; 949 } 950 while (CharOperation.compareTo(mid, list[right]) > 0) { 951 right--; 952 } 953 if (left <= right) { 954 char[] tmp= list[left]; 955 list[left]= list[right]; 956 list[right]= tmp; 957 int temp = result[left]; 958 result[left] = result[right]; 959 result[right] = temp; 960 left++; 961 right--; 962 } 963 } while (left <= right); 964 if (original_left < right) { 965 reverseQuickSort(list, original_left, right, result); 966 } 967 if (left < original_right) { 968 reverseQuickSort(list, left, original_right, result); 969 } 970 } 971 /** 972 * INTERNAL USE-ONLY 973 * Search the column number corresponding to a specific position 974 */ searchColumnNumber(int[] startLineIndexes, int lineNumber, int position)975 public static final int searchColumnNumber(int[] startLineIndexes, int lineNumber, int position) { 976 switch(lineNumber) { 977 case 1 : 978 return position + 1; 979 case 2: 980 return position - startLineIndexes[0]; 981 default: 982 int line = lineNumber - 2; 983 int length = startLineIndexes.length; 984 if (line >= length) { 985 return position - startLineIndexes[length - 1]; 986 } 987 return position - startLineIndexes[line]; 988 } 989 } 990 991 /** 992 * Converts a boolean value into Boolean. 993 * @param bool The boolean to convert 994 * @return The corresponding Boolean object (TRUE or FALSE). 995 */ toBoolean(boolean bool)996 public static Boolean toBoolean(boolean bool) { 997 if (bool) { 998 return Boolean.TRUE; 999 } else { 1000 return Boolean.FALSE; 1001 } 1002 } 1003 /** 1004 * Converts an array of Objects into String. 1005 */ toString(Object[] objects)1006 public static String toString(Object[] objects) { 1007 return toString(objects, 1008 new Displayable(){ 1009 @Override 1010 public String displayString(Object o) { 1011 if (o == null) return "null"; //$NON-NLS-1$ 1012 return o.toString(); 1013 } 1014 }); 1015 } 1016 1017 /** 1018 * Converts an array of Objects into String. 1019 */ 1020 public static String toString(Object[] objects, Displayable renderer) { 1021 if (objects == null) return ""; //$NON-NLS-1$ 1022 StringBuffer buffer = new StringBuffer(10); 1023 for (int i = 0; i < objects.length; i++){ 1024 if (i > 0) buffer.append(", "); //$NON-NLS-1$ 1025 buffer.append(renderer.displayString(objects[i])); 1026 } 1027 return buffer.toString(); 1028 } 1029 1030 /** 1031 * outputPath is formed like: 1032 * c:\temp\ the last character is a file separator 1033 * relativeFileName is formed like: 1034 * java\lang\String.class 1035 * @param generatePackagesStructure a flag to know if the packages structure has to be generated. 1036 * @param outputPath the given output directory 1037 * @param relativeFileName the given relative file name 1038 * @param classFile the given classFile to write 1039 * 1040 */ 1041 public static void writeToDisk(boolean generatePackagesStructure, String outputPath, String relativeFileName, ClassFile classFile) throws IOException { 1042 FileOutputStream file = getFileOutputStream(generatePackagesStructure, outputPath, relativeFileName); 1043 /* use java.nio to write 1044 if (true) { 1045 FileChannel ch = file.getChannel(); 1046 try { 1047 ByteBuffer buffer = ByteBuffer.allocate(classFile.headerOffset + classFile.contentsOffset); 1048 buffer.put(classFile.header, 0, classFile.headerOffset); 1049 buffer.put(classFile.contents, 0, classFile.contentsOffset); 1050 buffer.flip(); 1051 while (true) { 1052 if (ch.write(buffer) == 0) break; 1053 } 1054 } finally { 1055 ch.close(); 1056 } 1057 return; 1058 } 1059 */ 1060 BufferedOutputStream output = new BufferedOutputStream(file, DEFAULT_WRITING_SIZE); 1061 // BufferedOutputStream output = new BufferedOutputStream(file); 1062 try { 1063 // if no IOException occured, output cannot be null 1064 output.write(classFile.header, 0, classFile.headerOffset); 1065 output.write(classFile.contents, 0, classFile.contentsOffset); 1066 output.flush(); 1067 } catch(IOException e) { 1068 throw e; 1069 } finally { 1070 output.close(); 1071 } 1072 } 1073 @SuppressWarnings({ "rawtypes", "unchecked" }) 1074 public static void recordNestedType(ClassFile classFile, TypeBinding typeBinding) { 1075 if (classFile.visitedTypes == null) { 1076 classFile.visitedTypes = new HashSet(3); 1077 } else if (classFile.visitedTypes.contains(typeBinding)) { 1078 // type is already visited 1079 return; 1080 } 1081 classFile.visitedTypes.add(typeBinding); 1082 if (typeBinding.isParameterizedType() 1083 && ((typeBinding.tagBits & TagBits.ContainsNestedTypeReferences) != 0)) { 1084 ParameterizedTypeBinding parameterizedTypeBinding = (ParameterizedTypeBinding) typeBinding; 1085 ReferenceBinding genericType = parameterizedTypeBinding.genericType(); 1086 if ((genericType.tagBits & TagBits.ContainsNestedTypeReferences) != 0) { 1087 recordNestedType(classFile, genericType); 1088 } 1089 TypeBinding[] arguments = parameterizedTypeBinding.arguments; 1090 if (arguments != null) { 1091 for (int j = 0, max2 = arguments.length; j < max2; j++) { 1092 TypeBinding argument = arguments[j]; 1093 if (argument.isWildcard()) { 1094 WildcardBinding wildcardBinding = (WildcardBinding) argument; 1095 TypeBinding bound = wildcardBinding.bound; 1096 if (bound != null 1097 && ((bound.tagBits & TagBits.ContainsNestedTypeReferences) != 0)) { 1098 recordNestedType(classFile, bound); 1099 } 1100 ReferenceBinding superclass = wildcardBinding.superclass(); 1101 if (superclass != null 1102 && ((superclass.tagBits & TagBits.ContainsNestedTypeReferences) != 0)) { 1103 recordNestedType(classFile, superclass); 1104 } 1105 ReferenceBinding[] superInterfaces = wildcardBinding.superInterfaces(); 1106 if (superInterfaces != null) { 1107 for (int k = 0, max3 = superInterfaces.length; k < max3; k++) { 1108 ReferenceBinding superInterface = superInterfaces[k]; 1109 if ((superInterface.tagBits & TagBits.ContainsNestedTypeReferences) != 0) { 1110 recordNestedType(classFile, superInterface); 1111 } 1112 } 1113 } 1114 } else if ((argument.tagBits & TagBits.ContainsNestedTypeReferences) != 0) { 1115 recordNestedType(classFile, argument); 1116 } 1117 } 1118 } 1119 } else if (typeBinding.isTypeVariable() 1120 && ((typeBinding.tagBits & TagBits.ContainsNestedTypeReferences) != 0)) { 1121 TypeVariableBinding typeVariableBinding = (TypeVariableBinding) typeBinding; 1122 TypeBinding upperBound = typeVariableBinding.upperBound(); 1123 if (upperBound != null && ((upperBound.tagBits & TagBits.ContainsNestedTypeReferences) != 0)) { 1124 recordNestedType(classFile, upperBound); 1125 } 1126 TypeBinding[] upperBounds = typeVariableBinding.otherUpperBounds(); 1127 if (upperBounds != null) { 1128 for (int k = 0, max3 = upperBounds.length; k < max3; k++) { 1129 TypeBinding otherUpperBound = upperBounds[k]; 1130 if ((otherUpperBound.tagBits & TagBits.ContainsNestedTypeReferences) != 0) { 1131 recordNestedType(classFile, otherUpperBound); 1132 } 1133 } 1134 } 1135 } else if (typeBinding.isNestedType()) { 1136 TypeBinding enclosingType = typeBinding; 1137 do { 1138 if (!enclosingType.canBeSeenBy(classFile.referenceBinding.scope)) 1139 break; 1140 enclosingType = enclosingType.enclosingType(); 1141 } while (enclosingType != null); 1142 boolean onBottomForBug445231 = enclosingType != null; 1143 classFile.recordInnerClasses(typeBinding, onBottomForBug445231); 1144 } 1145 } 1146 /* 1147 * External API 1148 */ 1149 public static File getJavaHome() { 1150 String javaHome = System.getProperty("java.home");//$NON-NLS-1$ 1151 if (javaHome != null) { 1152 File javaHomeFile = new File(javaHome); 1153 if (javaHomeFile.exists()) { 1154 return javaHomeFile; 1155 } 1156 } 1157 return null; 1158 } 1159 1160 public static void collectVMBootclasspath(List<Classpath> bootclasspaths, File javaHome) { 1161 List<Classpath> classpaths = collectPlatformLibraries(javaHome); 1162 bootclasspaths.addAll(classpaths); 1163 } 1164 public static void collectRunningVMBootclasspath(List<Classpath> bootclasspaths) { 1165 collectVMBootclasspath(bootclasspaths, null); 1166 } 1167 public static long getJDKLevel(File javaHome) { 1168 String version = System.getProperty("java.version"); //$NON-NLS-1$ 1169 return CompilerOptions.versionToJdkLevel(version); 1170 } 1171 public static List<FileSystem.Classpath> collectFilesNames() { 1172 return collectPlatformLibraries(null); 1173 } 1174 public static List<FileSystem.Classpath> collectPlatformLibraries(File javaHome) { 1175 /* no bootclasspath specified 1176 * we can try to retrieve the default librairies of the VM used to run 1177 * the batch compiler 1178 */ 1179 String javaversion = null; 1180 javaversion = System.getProperty("java.version"); //$NON-NLS-1$ 1181 // Surely, this ain't required anymore? 1182 if (javaversion != null && javaversion.equalsIgnoreCase("1.1.8")) { //$NON-NLS-1$ 1183 throw new IllegalStateException(); 1184 } 1185 long jdkLevel = CompilerOptions.versionToJdkLevel(javaversion); 1186 if (jdkLevel >= ClassFileConstants.JDK9) { 1187 List<FileSystem.Classpath> filePaths = new ArrayList<>(); 1188 if (javaHome == null) { 1189 javaHome = getJavaHome(); 1190 } 1191 if (javaHome != null) { 1192 filePaths.add(FileSystem.getJrtClasspath(javaHome.getAbsolutePath(), null, null, null)); 1193 return filePaths; 1194 } 1195 } 1196 1197 /* 1198 * Handle >= JDK 1.2.2 settings: retrieve the bootclasspath 1199 */ 1200 // check bootclasspath properties for Sun, JRockit and Harmony VMs 1201 String bootclasspathProperty = System.getProperty("sun.boot.class.path"); //$NON-NLS-1$ 1202 if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) { 1203 // IBM J9 VMs 1204 bootclasspathProperty = System.getProperty("vm.boot.class.path"); //$NON-NLS-1$ 1205 if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) { 1206 // Harmony using IBM VME 1207 bootclasspathProperty = System.getProperty("org.apache.harmony.boot.class.path"); //$NON-NLS-1$ 1208 } 1209 } 1210 Set<String> filePaths = new HashSet<>(); 1211 if ((bootclasspathProperty != null) && (bootclasspathProperty.length() != 0)) { 1212 StringTokenizer tokenizer = new StringTokenizer(bootclasspathProperty, File.pathSeparator); 1213 while (tokenizer.hasMoreTokens()) { 1214 filePaths.add(tokenizer.nextToken()); 1215 } 1216 } else { 1217 // try to get all jars inside the lib folder of the java home 1218 if (javaHome == null) { 1219 javaHome = getJavaHome(); 1220 } 1221 if (javaHome != null) { 1222 File[] directoriesToCheck = null; 1223 if (System.getProperty("os.name").startsWith("Mac")) {//$NON-NLS-1$//$NON-NLS-2$ 1224 directoriesToCheck = new File[] { 1225 new File(javaHome, "../Classes"), //$NON-NLS-1$ 1226 }; 1227 } else { 1228 // fall back to try to retrieve them out of the lib directory 1229 directoriesToCheck = new File[] { 1230 new File(javaHome, "lib") //$NON-NLS-1$ 1231 }; 1232 } 1233 File[][] systemLibrariesJars = Main.getLibrariesFiles(directoriesToCheck); 1234 if (systemLibrariesJars != null) { 1235 for (int i = 0, max = systemLibrariesJars.length; i < max; i++) { 1236 File[] current = systemLibrariesJars[i]; 1237 if (current != null) { 1238 for (int j = 0, max2 = current.length; j < max2; j++) { 1239 filePaths.add(current[j].getAbsolutePath()); 1240 } 1241 } 1242 } 1243 } 1244 } 1245 } 1246 List<FileSystem.Classpath> classpaths = new ArrayList<>(); 1247 for (String filePath : filePaths) { 1248 FileSystem.Classpath currentClasspath = FileSystem.getClasspath(filePath, null, null, null, null); 1249 if (currentClasspath != null) { 1250 classpaths.add(currentClasspath); 1251 } 1252 } 1253 return classpaths; 1254 } 1255 public static int getParameterCount(char[] methodSignature) { 1256 try { 1257 int count = 0; 1258 int i = CharOperation.indexOf(C_PARAM_START, methodSignature); 1259 if (i < 0) { 1260 throw new IllegalArgumentException(String.valueOf(methodSignature)); 1261 } else { 1262 i++; 1263 } 1264 for (;;) { 1265 if (methodSignature[i] == C_PARAM_END) { 1266 return count; 1267 } 1268 int e= Util.scanTypeSignature(methodSignature, i); 1269 if (e < 0) { 1270 throw new IllegalArgumentException(String.valueOf(methodSignature)); 1271 } else { 1272 i = e + 1; 1273 } 1274 count++; 1275 } 1276 } catch (ArrayIndexOutOfBoundsException e) { 1277 throw new IllegalArgumentException(String.valueOf(methodSignature), e); 1278 } 1279 } 1280 1281 /** 1282 * Scans the given string for a type signature starting at the given index 1283 * and returns the index of the last character. 1284 * <pre> 1285 * TypeSignature: 1286 * | BaseTypeSignature 1287 * | ArrayTypeSignature 1288 * | ClassTypeSignature 1289 * | TypeVariableSignature 1290 * </pre> 1291 * 1292 * @param string the signature string 1293 * @param start the 0-based character index of the first character 1294 * @return the 0-based character index of the last character 1295 * @exception IllegalArgumentException if this is not a type signature 1296 */ 1297 public static int scanTypeSignature(char[] string, int start) { 1298 // need a minimum 1 char 1299 if (start >= string.length) { 1300 throw newIllegalArgumentException(string, start); 1301 } 1302 char c = string[start]; 1303 switch (c) { 1304 case C_ARRAY : 1305 return scanArrayTypeSignature(string, start); 1306 case C_RESOLVED : 1307 case C_UNRESOLVED : 1308 return scanClassTypeSignature(string, start); 1309 case C_TYPE_VARIABLE : 1310 return scanTypeVariableSignature(string, start); 1311 case C_BOOLEAN : 1312 case C_BYTE : 1313 case C_CHAR : 1314 case C_DOUBLE : 1315 case C_FLOAT : 1316 case C_INT : 1317 case C_LONG : 1318 case C_SHORT : 1319 case C_VOID : 1320 return scanBaseTypeSignature(string, start); 1321 case C_CAPTURE : 1322 return scanCaptureTypeSignature(string, start); 1323 case C_EXTENDS: 1324 case C_SUPER: 1325 case C_STAR: 1326 return scanTypeBoundSignature(string, start); 1327 default : 1328 throw newIllegalArgumentException(string, start); 1329 } 1330 } 1331 1332 /** 1333 * Scans the given string for a base type signature starting at the given index 1334 * and returns the index of the last character. 1335 * <pre> 1336 * BaseTypeSignature: 1337 * <b>B</b> | <b>C</b> | <b>D</b> | <b>F</b> | <b>I</b> 1338 * | <b>J</b> | <b>S</b> | <b>V</b> | <b>Z</b> 1339 * </pre> 1340 * Note that although the base type "V" is only allowed in method return types, 1341 * there is no syntactic ambiguity. This method will accept them anywhere 1342 * without complaint. 1343 * 1344 * @param string the signature string 1345 * @param start the 0-based character index of the first character 1346 * @return the 0-based character index of the last character 1347 * @exception IllegalArgumentException if this is not a base type signature 1348 */ 1349 public static int scanBaseTypeSignature(char[] string, int start) { 1350 // need a minimum 1 char 1351 if (start >= string.length) { 1352 throw newIllegalArgumentException(string, start); 1353 } 1354 char c = string[start]; 1355 if ("BCDFIJSVZ".indexOf(c) >= 0) { //$NON-NLS-1$ 1356 return start; 1357 } else { 1358 throw newIllegalArgumentException(string, start); 1359 } 1360 } 1361 1362 /** 1363 * Scans the given string for an array type signature starting at the given 1364 * index and returns the index of the last character. 1365 * <pre> 1366 * ArrayTypeSignature: 1367 * <b>[</b> TypeSignature 1368 * </pre> 1369 * 1370 * @param string the signature string 1371 * @param start the 0-based character index of the first character 1372 * @return the 0-based character index of the last character 1373 * @exception IllegalArgumentException if this is not an array type signature 1374 */ 1375 public static int scanArrayTypeSignature(char[] string, int start) { 1376 int length = string.length; 1377 // need a minimum 2 char 1378 if (start >= length - 1) { 1379 throw newIllegalArgumentException(string, start); 1380 } 1381 char c = string[start]; 1382 if (c != C_ARRAY) { 1383 throw newIllegalArgumentException(string, start); 1384 } 1385 1386 c = string[++start]; 1387 while(c == C_ARRAY) { 1388 // need a minimum 2 char 1389 if (start >= length - 1) { 1390 throw newIllegalArgumentException(string, start); 1391 } 1392 c = string[++start]; 1393 } 1394 return scanTypeSignature(string, start); 1395 } 1396 1397 /** 1398 * Scans the given string for a capture of a wildcard type signature starting at the given 1399 * index and returns the index of the last character. 1400 * <pre> 1401 * CaptureTypeSignature: 1402 * <b>!</b> TypeBoundSignature 1403 * </pre> 1404 * 1405 * @param string the signature string 1406 * @param start the 0-based character index of the first character 1407 * @return the 0-based character index of the last character 1408 * @exception IllegalArgumentException if this is not a capture type signature 1409 */ 1410 public static int scanCaptureTypeSignature(char[] string, int start) { 1411 // need a minimum 2 char 1412 if (start >= string.length - 1) { 1413 throw newIllegalArgumentException(string, start); 1414 } 1415 char c = string[start]; 1416 if (c != C_CAPTURE) { 1417 throw newIllegalArgumentException(string, start); 1418 } 1419 return scanTypeBoundSignature(string, start + 1); 1420 } 1421 1422 /** 1423 * Scans the given string for a type variable signature starting at the given 1424 * index and returns the index of the last character. 1425 * <pre> 1426 * TypeVariableSignature: 1427 * <b>T</b> Identifier <b>;</b> 1428 * </pre> 1429 * 1430 * @param string the signature string 1431 * @param start the 0-based character index of the first character 1432 * @return the 0-based character index of the last character 1433 * @exception IllegalArgumentException if this is not a type variable signature 1434 */ 1435 public static int scanTypeVariableSignature(char[] string, int start) { 1436 // need a minimum 3 chars "Tx;" 1437 if (start >= string.length - 2) { 1438 throw newIllegalArgumentException(string, start); 1439 } 1440 // must start in "T" 1441 char c = string[start]; 1442 if (c != C_TYPE_VARIABLE) { 1443 throw newIllegalArgumentException(string, start); 1444 } 1445 int id = scanIdentifier(string, start + 1); 1446 c = string[id + 1]; 1447 if (c == C_SEMICOLON) { 1448 return id + 1; 1449 } else { 1450 throw newIllegalArgumentException(string, start); 1451 } 1452 } 1453 1454 /** 1455 * Scans the given string for an identifier starting at the given 1456 * index and returns the index of the last character. 1457 * Stop characters are: ";", ":", "<", ">", "/", ".". 1458 * 1459 * @param string the signature string 1460 * @param start the 0-based character index of the first character 1461 * @return the 0-based character index of the last character 1462 * @exception IllegalArgumentException if this is not an identifier 1463 */ 1464 public static int scanIdentifier(char[] string, int start) { 1465 // need a minimum 1 char 1466 if (start >= string.length) { 1467 throw newIllegalArgumentException(string, start); 1468 } 1469 int p = start; 1470 while (true) { 1471 char c = string[p]; 1472 if (c == '<' || c == '>' || c == ':' || c == ';' || c == '.' || c == '/') { 1473 return p - 1; 1474 } 1475 p++; 1476 if (p == string.length) { 1477 return p - 1; 1478 } 1479 } 1480 } 1481 1482 /** 1483 * Scans the given string for a class type signature starting at the given 1484 * index and returns the index of the last character. 1485 * <pre> 1486 * ClassTypeSignature: 1487 * { <b>L</b> | <b>Q</b> } Identifier 1488 * { { <b>/</b> | <b>.</b> Identifier [ <b><</b> TypeArgumentSignature* <b>></b> ] } 1489 * <b>;</b> 1490 * </pre> 1491 * Note that although all "/"-identifiers most come before "."-identifiers, 1492 * there is no syntactic ambiguity. This method will accept them without 1493 * complaint. 1494 * 1495 * @param string the signature string 1496 * @param start the 0-based character index of the first character 1497 * @return the 0-based character index of the last character 1498 * @exception IllegalArgumentException if this is not a class type signature 1499 */ 1500 public static int scanClassTypeSignature(char[] string, int start) { 1501 // need a minimum 3 chars "Lx;" 1502 if (start >= string.length - 2) { 1503 throw newIllegalArgumentException(string, start); 1504 } 1505 // must start in "L" or "Q" 1506 char c = string[start]; 1507 if (c != C_RESOLVED && c != C_UNRESOLVED) { 1508 return -1; 1509 } 1510 int p = start + 1; 1511 while (true) { 1512 if (p >= string.length) { 1513 throw newIllegalArgumentException(string, start); 1514 } 1515 c = string[p]; 1516 if (c == C_SEMICOLON) { 1517 // all done 1518 return p; 1519 } else if (c == C_GENERIC_START) { 1520 int e = scanTypeArgumentSignatures(string, p); 1521 p = e; 1522 } else if (c == C_DOT || c == '/') { 1523 int id = scanIdentifier(string, p + 1); 1524 p = id; 1525 } 1526 p++; 1527 } 1528 } 1529 1530 /** 1531 * Scans the given string for a type bound signature starting at the given 1532 * index and returns the index of the last character. 1533 * <pre> 1534 * TypeBoundSignature: 1535 * <b>[-+]</b> TypeSignature <b>;</b> 1536 * <b>*</b></b> 1537 * </pre> 1538 * 1539 * @param string the signature string 1540 * @param start the 0-based character index of the first character 1541 * @return the 0-based character index of the last character 1542 * @exception IllegalArgumentException if this is not a type variable signature 1543 */ 1544 public static int scanTypeBoundSignature(char[] string, int start) { 1545 // need a minimum 1 char for wildcard 1546 if (start >= string.length) { 1547 throw newIllegalArgumentException(string, start); 1548 } 1549 char c = string[start]; 1550 switch (c) { 1551 case C_STAR : 1552 return start; 1553 case C_SUPER : 1554 case C_EXTENDS : 1555 break; 1556 default : 1557 // must start in "+/-" 1558 throw newIllegalArgumentException(string, start); 1559 } 1560 c = string[++start]; 1561 if (c != C_STAR && start >= string.length -1) { // unless "-*" we need at least one more char, e.g. after "+[", other variants are even longer 1562 throw new IllegalArgumentException(); 1563 } 1564 switch (c) { 1565 case C_CAPTURE : 1566 return scanCaptureTypeSignature(string, start); 1567 case C_SUPER : 1568 case C_EXTENDS : 1569 return scanTypeBoundSignature(string, start); 1570 case C_RESOLVED : 1571 case C_UNRESOLVED : 1572 return scanClassTypeSignature(string, start); 1573 case C_TYPE_VARIABLE : 1574 return scanTypeVariableSignature(string, start); 1575 case C_ARRAY : 1576 return scanArrayTypeSignature(string, start); 1577 case C_STAR: 1578 return start; 1579 default: 1580 throw newIllegalArgumentException(string, start); 1581 } 1582 } 1583 1584 /** 1585 * Scans the given string for a list of type argument signatures starting at 1586 * the given index and returns the index of the last character. 1587 * <pre> 1588 * TypeArgumentSignatures: 1589 * <b><</b> TypeArgumentSignature* <b>></b> 1590 * </pre> 1591 * Note that although there is supposed to be at least one type argument, there 1592 * is no syntactic ambiguity if there are none. This method will accept zero 1593 * type argument signatures without complaint. 1594 * 1595 * @param string the signature string 1596 * @param start the 0-based character index of the first character 1597 * @return the 0-based character index of the last character 1598 * @exception IllegalArgumentException if this is not a list of type arguments 1599 * signatures 1600 */ 1601 public static int scanTypeArgumentSignatures(char[] string, int start) { 1602 // need a minimum 2 char "<>" 1603 if (start >= string.length - 1) { 1604 throw newIllegalArgumentException(string, start); 1605 } 1606 char c = string[start]; 1607 if (c != C_GENERIC_START) { 1608 throw newIllegalArgumentException(string, start); 1609 } 1610 int p = start + 1; 1611 while (true) { 1612 if (p >= string.length) { 1613 throw newIllegalArgumentException(string, start); 1614 } 1615 c = string[p]; 1616 if (c == C_GENERIC_END) { 1617 return p; 1618 } 1619 int e = scanTypeArgumentSignature(string, p); 1620 p = e + 1; 1621 } 1622 } 1623 1624 /** 1625 * Scans the given string for a type argument signature starting at the given 1626 * index and returns the index of the last character. 1627 * <pre> 1628 * TypeArgumentSignature: 1629 * <b>*</b> 1630 * | <b>+</b> TypeSignature 1631 * | <b>-</b> TypeSignature 1632 * | TypeSignature 1633 * </pre> 1634 * Note that although base types are not allowed in type arguments, there is 1635 * no syntactic ambiguity. This method will accept them without complaint. 1636 * 1637 * @param string the signature string 1638 * @param start the 0-based character index of the first character 1639 * @return the 0-based character index of the last character 1640 * @exception IllegalArgumentException if this is not a type argument signature 1641 */ 1642 public static int scanTypeArgumentSignature(char[] string, int start) { 1643 // need a minimum 1 char 1644 if (start >= string.length) { 1645 throw newIllegalArgumentException(string, start); 1646 } 1647 char c = string[start]; 1648 switch (c) { 1649 case C_STAR : 1650 return start; 1651 case C_EXTENDS : 1652 case C_SUPER : 1653 return scanTypeBoundSignature(string, start); 1654 default : 1655 return scanTypeSignature(string, start); 1656 } 1657 } 1658 1659 public static boolean effectivelyEqual(Object [] one, Object [] two) { 1660 if (one == two) 1661 return true; 1662 int oneLength = one == null ? 0 : one.length; 1663 int twoLength = two == null ? 0 : two.length; 1664 if (oneLength != twoLength) 1665 return false; 1666 if (oneLength == 0) 1667 return true; 1668 for (int i = 0; i < one.length; i++) { 1669 if (one[i] != two[i]) 1670 return false; 1671 } 1672 return true; 1673 } 1674 1675 public static void appendEscapedChar(StringBuffer buffer, char c, boolean stringLiteral) { 1676 switch (c) { 1677 case '\b' : 1678 buffer.append("\\b"); //$NON-NLS-1$ 1679 break; 1680 case '\t' : 1681 buffer.append("\\t"); //$NON-NLS-1$ 1682 break; 1683 case '\n' : 1684 buffer.append("\\n"); //$NON-NLS-1$ 1685 break; 1686 case '\f' : 1687 buffer.append("\\f"); //$NON-NLS-1$ 1688 break; 1689 case '\r' : 1690 buffer.append("\\r"); //$NON-NLS-1$ 1691 break; 1692 case '\"': 1693 if (stringLiteral) { 1694 buffer.append("\\\""); //$NON-NLS-1$ 1695 } else { 1696 buffer.append(c); 1697 } 1698 break; 1699 case '\'': 1700 if (stringLiteral) { 1701 buffer.append(c); 1702 } else { 1703 buffer.append("\\\'"); //$NON-NLS-1$ 1704 } 1705 break; 1706 case '\\': 1707 buffer.append("\\\\"); //$NON-NLS-1$ 1708 break; 1709 default: 1710 if (c >= 0x20) { 1711 buffer.append(c); 1712 } else if (c >= 0x10) { 1713 buffer.append("\\u00").append(Integer.toHexString(c)); //$NON-NLS-1$ 1714 } else if (c >= 0) { 1715 buffer.append("\\u000").append(Integer.toHexString(c)); //$NON-NLS-1$ 1716 } else { 1717 buffer.append(c); 1718 } 1719 } 1720 } 1721 1722 private static IllegalArgumentException newIllegalArgumentException(char[] string, int start) { 1723 return new IllegalArgumentException("\"" + String.valueOf(string) + "\" at " + start); //$NON-NLS-1$ //$NON-NLS-2$ 1724 } 1725 }