1 /******************************************************************************* 2 * Copyright (c) 2007, 2020 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 *******************************************************************************/ 14 package org.eclipse.pde.api.tools.internal.util; 15 16 import java.io.BufferedInputStream; 17 import java.io.BufferedOutputStream; 18 import java.io.BufferedWriter; 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.File; 22 import java.io.FileFilter; 23 import java.io.FileInputStream; 24 import java.io.FileNotFoundException; 25 import java.io.FileOutputStream; 26 import java.io.FileWriter; 27 import java.io.FilenameFilter; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.LineNumberReader; 31 import java.io.PrintWriter; 32 import java.io.StringReader; 33 import java.lang.reflect.Field; 34 import java.nio.ByteBuffer; 35 import java.nio.CharBuffer; 36 import java.nio.charset.Charset; 37 import java.nio.charset.CharsetDecoder; 38 import java.nio.charset.CodingErrorAction; 39 import java.nio.charset.IllegalCharsetNameException; 40 import java.nio.charset.StandardCharsets; 41 import java.nio.charset.UnsupportedCharsetException; 42 import java.text.MessageFormat; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.Comparator; 47 import java.util.Enumeration; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Properties; 53 import java.util.Set; 54 import java.util.StringTokenizer; 55 import java.util.jar.JarFile; 56 import java.util.regex.Pattern; 57 import java.util.regex.PatternSyntaxException; 58 import java.util.stream.Stream; 59 import java.util.zip.ZipEntry; 60 import java.util.zip.ZipFile; 61 import java.util.zip.ZipInputStream; 62 63 import javax.xml.parsers.DocumentBuilder; 64 import javax.xml.parsers.DocumentBuilderFactory; 65 import javax.xml.parsers.FactoryConfigurationError; 66 import javax.xml.parsers.ParserConfigurationException; 67 import javax.xml.transform.OutputKeys; 68 import javax.xml.transform.Transformer; 69 import javax.xml.transform.TransformerException; 70 import javax.xml.transform.TransformerFactory; 71 import javax.xml.transform.dom.DOMSource; 72 import javax.xml.transform.stream.StreamResult; 73 74 import org.eclipse.core.filebuffers.FileBuffers; 75 import org.eclipse.core.filebuffers.ITextFileBufferManager; 76 import org.eclipse.core.filebuffers.LocationKind; 77 import org.eclipse.core.resources.IFile; 78 import org.eclipse.core.resources.IMarker; 79 import org.eclipse.core.resources.IProject; 80 import org.eclipse.core.resources.IResource; 81 import org.eclipse.core.resources.IncrementalProjectBuilder; 82 import org.eclipse.core.resources.ResourcesPlugin; 83 import org.eclipse.core.runtime.Assert; 84 import org.eclipse.core.runtime.AssertionFailedException; 85 import org.eclipse.core.runtime.CoreException; 86 import org.eclipse.core.runtime.IPath; 87 import org.eclipse.core.runtime.IProgressMonitor; 88 import org.eclipse.core.runtime.IStatus; 89 import org.eclipse.core.runtime.NullProgressMonitor; 90 import org.eclipse.core.runtime.OperationCanceledException; 91 import org.eclipse.core.runtime.Path; 92 import org.eclipse.core.runtime.Status; 93 import org.eclipse.core.runtime.SubMonitor; 94 import org.eclipse.core.runtime.jobs.Job; 95 import org.eclipse.jdt.core.Flags; 96 import org.eclipse.jdt.core.ICompilationUnit; 97 import org.eclipse.jdt.core.IField; 98 import org.eclipse.jdt.core.IJavaElement; 99 import org.eclipse.jdt.core.IJavaProject; 100 import org.eclipse.jdt.core.IMember; 101 import org.eclipse.jdt.core.IMethod; 102 import org.eclipse.jdt.core.IPackageFragment; 103 import org.eclipse.jdt.core.IType; 104 import org.eclipse.jdt.core.ITypeParameter; 105 import org.eclipse.jdt.core.ITypeRoot; 106 import org.eclipse.jdt.core.JavaCore; 107 import org.eclipse.jdt.core.JavaModelException; 108 import org.eclipse.jdt.core.Signature; 109 import org.eclipse.jdt.internal.compiler.codegen.ConstantPool; 110 import org.eclipse.jdt.internal.core.BinaryType; 111 import org.eclipse.jdt.internal.core.ClassFile; 112 import org.eclipse.jdt.internal.core.CompilationUnit; 113 import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner; 114 import org.eclipse.jdt.internal.core.JavaProject; 115 import org.eclipse.jdt.internal.core.NameLookup; 116 import org.eclipse.jdt.internal.core.PackageFragment; 117 import org.eclipse.jdt.internal.core.SourceType; 118 import org.eclipse.jdt.launching.IVMInstall; 119 import org.eclipse.jdt.launching.JavaRuntime; 120 import org.eclipse.jdt.launching.LibraryLocation; 121 import org.eclipse.jdt.launching.environments.ExecutionEnvironmentDescription; 122 import org.eclipse.jface.text.IDocument; 123 import org.eclipse.osgi.util.NLS; 124 import org.eclipse.pde.api.tools.internal.FilterStore; 125 import org.eclipse.pde.api.tools.internal.IApiCoreConstants; 126 import org.eclipse.pde.api.tools.internal.builder.BuildState; 127 import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; 128 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; 129 import org.eclipse.pde.api.tools.internal.provisional.Factory; 130 import org.eclipse.pde.api.tools.internal.provisional.IApiMarkerConstants; 131 import org.eclipse.pde.api.tools.internal.provisional.IRequiredComponentDescription; 132 import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers; 133 import org.eclipse.pde.api.tools.internal.provisional.comparator.DeltaVisitor; 134 import org.eclipse.pde.api.tools.internal.provisional.comparator.IDelta; 135 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; 136 import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; 137 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; 138 import org.eclipse.pde.api.tools.internal.provisional.model.IApiElement; 139 import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; 140 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; 141 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; 142 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemTypes; 143 import org.eclipse.pde.api.tools.internal.search.SkippedComponent; 144 import org.objectweb.asm.Opcodes; 145 import org.osgi.framework.Version; 146 import org.w3c.dom.Document; 147 import org.w3c.dom.Element; 148 import org.xml.sax.SAXException; 149 import org.xml.sax.helpers.DefaultHandler; 150 151 /** 152 * A Utility class to use for API tools 153 * 154 * @since 1.0.0 155 */ 156 public final class Util { 157 158 public static final String DOT_TGZ = ".tgz"; //$NON-NLS-1$ 159 public static final String DOT_TAR_GZ = ".tar.gz"; //$NON-NLS-1$ 160 public static final String DOT_JAR = ".jar"; //$NON-NLS-1$ 161 public static final String DOT_ZIP = ".zip"; //$NON-NLS-1$ 162 163 public static final char VERSION_SEPARATOR = '('; 164 165 /** 166 * Class that runs a build in the workspace or the given project 167 */ 168 private static final class BuildJob extends Job { 169 private final IProject[] fProjects; 170 private int fBuildType; 171 172 /** 173 * Constructor 174 * 175 * @param name 176 * @param project 177 */ BuildJob(String name, IProject[] projects)178 BuildJob(String name, IProject[] projects) { 179 this(name, projects, IncrementalProjectBuilder.FULL_BUILD); 180 } 181 BuildJob(String name, IProject[] projects, int buildType)182 BuildJob(String name, IProject[] projects, int buildType) { 183 super(name); 184 fProjects = projects; 185 this.fBuildType = buildType; 186 } 187 188 @Override belongsTo(Object family)189 public boolean belongsTo(Object family) { 190 return ResourcesPlugin.FAMILY_MANUAL_BUILD == family; 191 } 192 193 /** 194 * Returns if this build job is covered by another build job 195 * 196 * @param other 197 * @return true if covered by another build job, false otherwise 198 */ isCoveredBy(BuildJob other)199 public boolean isCoveredBy(BuildJob other) { 200 if (other.fProjects == null) { 201 return true; 202 } 203 if (this.fProjects != null) { 204 for (int i = 0, max = this.fProjects.length; i < max; i++) { 205 if (!other.contains(this.fProjects[i])) { 206 return false; 207 } 208 } 209 return true; 210 } 211 return false; 212 } 213 contains(IProject project)214 public boolean contains(IProject project) { 215 if (project == null) { 216 return false; 217 } 218 for (IProject fProject : this.fProjects) { 219 if (project.equals(fProject)) { 220 return true; 221 } 222 } 223 return false; 224 } 225 226 @Override run(IProgressMonitor monitor)227 protected IStatus run(IProgressMonitor monitor) { 228 synchronized (getClass()) { 229 if (monitor.isCanceled()) { 230 return Status.CANCEL_STATUS; 231 } 232 // cancelBuild(ResourcesPlugin.FAMILY_AUTO_BUILD); 233 cancelBuild(ResourcesPlugin.FAMILY_MANUAL_BUILD); 234 } 235 try { 236 if (fProjects != null) { 237 SubMonitor localmonitor = SubMonitor.convert(monitor, UtilMessages.Util_0, fProjects.length); 238 for (IProject currentProject : fProjects) { 239 if (this.fBuildType == IncrementalProjectBuilder.FULL_BUILD) { 240 BuildState.setLastBuiltState(currentProject, null); 241 } 242 localmonitor.subTask(NLS.bind(UtilMessages.Util_5, currentProject.getName())); 243 if (ResourcesPlugin.getWorkspace().isAutoBuilding()) { 244 currentProject.touch(null); 245 } else { 246 currentProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, localmonitor.split(1)); 247 } 248 } 249 } 250 } catch (CoreException e) { 251 return new Status(e.getStatus().getSeverity(), ApiPlugin.PLUGIN_ID, ApiPlugin.INTERNAL_ERROR, UtilMessages.Util_builder_errorMessage, e); 252 } catch (OperationCanceledException e) { 253 return Status.CANCEL_STATUS; 254 } finally { 255 monitor.done(); 256 } 257 return Status.OK_STATUS; 258 } 259 cancelBuild(Object jobfamily)260 private void cancelBuild(Object jobfamily) { 261 Job[] buildJobs = Job.getJobManager().find(jobfamily); 262 for (Job curr : buildJobs) { 263 if (curr != this && curr instanceof BuildJob) { 264 BuildJob job = (BuildJob) curr; 265 if (job.isCoveredBy(this)) { 266 curr.cancel(); // cancel all other build jobs of our 267 // kind 268 } 269 } 270 } 271 } 272 } 273 274 public static final String EMPTY_STRING = "";//$NON-NLS-1$ 275 public static final String DEFAULT_PACKAGE_NAME = EMPTY_STRING; 276 public static final String MANIFEST_NAME = "MANIFEST.MF"; //$NON-NLS-1$ 277 278 public static final String DOT_CLASS_SUFFIX = ".class"; //$NON-NLS-1$ 279 public static final String DOT_JAVA_SUFFIX = ".java"; //$NON-NLS-1$ 280 281 /** 282 * Constant representing the default size to read from an input stream 283 */ 284 private static final int DEFAULT_READING_SIZE = 8192; 285 286 private static final String JAVA_LANG_OBJECT = "java.lang.Object"; //$NON-NLS-1$ 287 private static final String JAVA_LANG_RUNTIMEEXCEPTION = "java.lang.RuntimeException"; //$NON-NLS-1$ 288 public static final String LINE_DELIMITER = System.lineSeparator(); 289 290 public static final String UNKNOWN_ELEMENT_KIND = "UNKNOWN_ELEMENT_KIND"; //$NON-NLS-1$ 291 292 public static final String UNKNOWN_FLAGS = "UNKNOWN_FLAGS"; //$NON-NLS-1$ 293 public static final String UNKNOWN_KIND = "UNKNOWN_KIND"; //$NON-NLS-1$ 294 public static final String UNKNOWN_VISIBILITY = "UNKNOWN_VISIBILITY"; //$NON-NLS-1$ 295 public static final String ISO_8859_1 = "ISO-8859-1"; //$NON-NLS-1$ 296 public static final String REGULAR_EXPRESSION_START = "R:"; //$NON-NLS-1$ 297 298 // Trace for delete operation 299 /* 300 * Maximum time wasted repeating delete operations while running JDT/Core 301 * tests. 302 */ 303 private static int DELETE_MAX_TIME = 0; 304 /** 305 * Trace deletion operations while running JDT/Core tests. 306 */ 307 private static boolean DELETE_DEBUG = false; 308 /** 309 * Maximum of time in milliseconds to wait in deletion operation while 310 * running JDT/Core tests. Default is 10 seconds. This number cannot exceed 311 * 1 minute (i.e. 60000). <br> 312 * To avoid too many loops while waiting, the ten first ones are done 313 * waiting 10ms before repeating, the ten loops after are done waiting 100ms 314 * and the other loops are done waiting 1s... 315 */ 316 private static int DELETE_MAX_WAIT = 10000; 317 318 public static final IPath MANIFEST_PROJECT_RELATIVE_PATH = new Path(JarFile.MANIFEST_NAME); 319 320 public static final String ORG_ECLIPSE_SWT = "org.eclipse.swt"; //$NON-NLS-1$ 321 322 public static final int LATEST_OPCODES_ASM = Opcodes.ASM8; 323 324 /** 325 * Throws an exception with the given message and underlying exception. 326 * 327 * @param message error message 328 * @param exception underlying exception, or <code>null</code> 329 * @throws CoreException 330 */ abort(String message, Throwable exception)331 private static void abort(String message, Throwable exception) throws CoreException { 332 IStatus status = new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, message, exception); 333 throw new CoreException(status); 334 } 335 336 /** 337 * Appends a property to the given string buffer with the given key and 338 * value in the format "key=value\n". 339 * 340 * @param buffer buffer to append to 341 * @param key key 342 * @param value value 343 */ appendProperty(StringBuilder buffer, String key, String value)344 private static void appendProperty(StringBuilder buffer, String key, String value) { 345 buffer.append(key); 346 buffer.append('='); 347 buffer.append(value); 348 buffer.append('\n'); 349 } 350 351 /** 352 * Collects all of the deltas from the given parent delta 353 * 354 * @param delta 355 * @return 356 */ collectAllDeltas(IDelta delta)357 public static List<IDelta> collectAllDeltas(IDelta delta) { 358 final List<IDelta> list = new ArrayList<>(); 359 delta.accept(new DeltaVisitor() { 360 @Override 361 public void endVisit(IDelta localDelta) { 362 if (localDelta.getChildren().length == 0) { 363 list.add(localDelta); 364 } 365 super.endVisit(localDelta); 366 } 367 }); 368 return list; 369 } 370 371 /** 372 * Collects files into the collector array list 373 * 374 * @param root the root to collect the files from 375 * @param collector the collector to place matches into 376 * @param fileFilter the filter for files or <code>null</code> to accept all 377 * files 378 */ collectAllFiles(File root, ArrayList<File> collector, FileFilter fileFilter)379 private static void collectAllFiles(File root, ArrayList<File> collector, FileFilter fileFilter) { 380 File[] files = root.listFiles(fileFilter); 381 for (final File currentFile : files) { 382 if (currentFile.isDirectory()) { 383 collectAllFiles(currentFile, collector, fileFilter); 384 } else { 385 collector.add(currentFile); 386 } 387 } 388 } 389 390 /** 391 * Returns all of the API projects in the workspace 392 * 393 * @return all of the API projects in the workspace or <code>null</code> if 394 * there are none. 395 */ getApiProjects()396 public static IProject[] getApiProjects() { 397 IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); 398 ArrayList<IProject> temp = new ArrayList<>(); 399 IProject project = null; 400 for (IProject allProject : allProjects) { 401 project = allProject; 402 if (project.isAccessible()) { 403 try { 404 if (project.hasNature(org.eclipse.pde.api.tools.internal.provisional.ApiPlugin.NATURE_ID)) { 405 temp.add(project); 406 } 407 } catch (CoreException e) { 408 // should not happen 409 } 410 } 411 } 412 IProject[] projects = null; 413 if (temp.size() != 0) { 414 projects = new IProject[temp.size()]; 415 temp.toArray(projects); 416 } 417 return projects; 418 } 419 420 /** 421 * Returns all of the API projects in the workspace 422 * 423 * @param sourcelevel 424 * @return all of the API projects in the workspace or <code>null</code> if 425 * there are none. 426 */ getApiProjectsMinSourceLevel(String sourcelevel)427 public static IProject[] getApiProjectsMinSourceLevel(String sourcelevel) { 428 IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); 429 ArrayList<IProject> temp = new ArrayList<>(); 430 IProject project = null; 431 for (IProject allProject : allProjects) { 432 project = allProject; 433 if (project.isAccessible()) { 434 try { 435 if (project.hasNature(org.eclipse.pde.api.tools.internal.provisional.ApiPlugin.NATURE_ID)) { 436 IJavaProject jp = JavaCore.create(project); 437 String src = jp.getOption(JavaCore.COMPILER_SOURCE, true); 438 if (src != null && src.compareTo(sourcelevel) >= 0) { 439 temp.add(project); 440 } 441 } 442 } catch (CoreException e) { 443 // should not happen 444 } 445 } 446 } 447 IProject[] projects = null; 448 if (temp.size() != 0) { 449 projects = new IProject[temp.size()]; 450 temp.toArray(projects); 451 } 452 return projects; 453 } 454 455 /** 456 * Copies the given file to the new file 457 * 458 * @param file 459 * @param newFile 460 * @return if the copy succeeded 461 */ copy(File file, File newFile)462 public static boolean copy(File file, File newFile) { 463 byte[] bytes = null; 464 BufferedInputStream inputStream = null; 465 try { 466 inputStream = new BufferedInputStream(new FileInputStream(file)); 467 bytes = Util.getInputStreamAsByteArray(inputStream, -1); 468 } catch (IOException e) { 469 ApiPlugin.log(e); 470 } finally { 471 if (inputStream != null) { 472 try { 473 inputStream.close(); 474 } catch (IOException e) { 475 ApiPlugin.log(e); 476 } 477 } 478 } 479 if (bytes != null) { 480 BufferedOutputStream outputStream = null; 481 try { 482 outputStream = new BufferedOutputStream(new FileOutputStream(newFile)); 483 outputStream.write(bytes); 484 outputStream.flush(); 485 } catch (IOException e) { 486 ApiPlugin.log(e); 487 } finally { 488 if (outputStream != null) { 489 try { 490 outputStream.close(); 491 } catch (IOException e) { 492 // ignore 493 } 494 } 495 } 496 return true; 497 } 498 return false; 499 } 500 501 /** 502 * Creates an EE file for the given JRE and specified EE id 503 * 504 * @param jre 505 * @param eeid 506 * @return 507 * @throws IOException 508 */ createEEFile(IVMInstall jre, String eeid)509 public static File createEEFile(IVMInstall jre, String eeid) throws IOException { 510 String string = Util.generateEEContents(jre, eeid); 511 File eeFile = createTempFile("eed", ".ee"); //$NON-NLS-1$ //$NON-NLS-2$ 512 FileOutputStream outputStream = null; 513 try { 514 outputStream = new FileOutputStream(eeFile); 515 outputStream.write(string.getBytes(StandardCharsets.UTF_8)); 516 } finally { 517 if (outputStream != null) { 518 outputStream.close(); 519 } 520 } 521 return eeFile; 522 } 523 524 /** 525 * Returns whether the objects are equal, accounting for either one being 526 * <code>null</code>. 527 * 528 * @param o1 529 * @param o2 530 * @return whether the objects are equal, or both are <code>null</code> 531 */ equalsOrNull(Object o1, Object o2)532 public static boolean equalsOrNull(Object o1, Object o2) { 533 if (o1 == null) { 534 return o2 == null; 535 } 536 return o1.equals(o2); 537 } 538 539 /** 540 * Returns an execution environment description for the given VM. 541 * 542 * @param vm JRE to create an definition for 543 * @return an execution environment description for the given VM 544 * @throws IOException if unable to generate description 545 */ generateEEContents(IVMInstall vm, String eeId)546 public static String generateEEContents(IVMInstall vm, String eeId) throws IOException { 547 StringBuilder buffer = new StringBuilder(); 548 appendProperty(buffer, ExecutionEnvironmentDescription.JAVA_HOME, vm.getInstallLocation().getCanonicalPath()); 549 StringBuilder paths = new StringBuilder(); 550 LibraryLocation[] libraryLocations = JavaRuntime.getLibraryLocations(vm); 551 for (int i = 0; i < libraryLocations.length; i++) { 552 LibraryLocation lib = libraryLocations[i]; 553 paths.append(lib.getSystemLibraryPath().toOSString()); 554 if (i < (libraryLocations.length - 1)) { 555 paths.append(File.pathSeparatorChar); 556 } 557 } 558 appendProperty(buffer, ExecutionEnvironmentDescription.BOOT_CLASS_PATH, paths.toString()); 559 appendProperty(buffer, ExecutionEnvironmentDescription.CLASS_LIB_LEVEL, eeId); 560 return buffer.toString(); 561 } 562 563 /** 564 * Returns an array of all of the files from the given root that are 565 * accepted by the given file filter. If the file filter is null all files 566 * within the given root are returned. 567 * 568 * @param root 569 * @param fileFilter 570 * @return the list of files from within the given root 571 */ getAllFiles(File root, FileFilter fileFilter)572 public static File[] getAllFiles(File root, FileFilter fileFilter) { 573 ArrayList<File> files = new ArrayList<>(); 574 if (root.isDirectory()) { 575 collectAllFiles(root, files, fileFilter); 576 File[] result = new File[files.size()]; 577 files.toArray(result); 578 return result; 579 } 580 return null; 581 } 582 583 /** 584 * Returns a build job that will perform a full build on the given projects. 585 * 586 * If <code>projects</code> are null, then an AssertionFailedException is 587 * thrown 588 * 589 * @param projects the projects to build 590 * @return the build job 591 * @throws AssertionFailedException if the given projects are null 592 */ getBuildJob(final IProject[] projects)593 public static Job getBuildJob(final IProject[] projects) { 594 Assert.isNotNull(projects); 595 Job buildJob = new BuildJob(UtilMessages.Util_4, projects); 596 buildJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); 597 buildJob.setUser(true); 598 return buildJob; 599 } 600 601 /** 602 * Returns a build job that will return the build that corresponds to the 603 * given build kind on the given projects. 604 * 605 * If <code>projects</code> are null, then an AssertionFailedException is 606 * thrown 607 * 608 * @param projects the projects to build 609 * @param buildKind the given build kind 610 * @return the build job 611 * @throws AssertionFailedException if the given projects are null 612 */ getBuildJob(final IProject[] projects, int buildKind)613 public static Job getBuildJob(final IProject[] projects, int buildKind) { 614 Assert.isNotNull(projects); 615 Job buildJob = new BuildJob(UtilMessages.Util_4, projects, buildKind); 616 buildJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule()); 617 buildJob.setUser(true); 618 return buildJob; 619 } 620 621 /** 622 * Returns a result of searching the given components for class file with 623 * the given type name. 624 * 625 * @param components API components to search or <code>null</code> if none 626 * @param typeName type to search for 627 * @return class file or <code>null</code> if none found 628 */ getClassFile(IApiComponent[] components, String typeName)629 public static IApiTypeRoot getClassFile(IApiComponent[] components, String typeName) { 630 if (components == null) { 631 return null; 632 } 633 CoreException ex = null; 634 IApiComponent component = null; 635 for (IApiComponent apiComponent : components) { 636 if (apiComponent != null) { 637 try { 638 IApiTypeRoot classFile = apiComponent.findTypeRoot(typeName); 639 if (classFile != null) { 640 return classFile; 641 } 642 } catch (CoreException e) { 643 if (ex == null) { 644 ex = e; 645 component = apiComponent; 646 } 647 } 648 } 649 } 650 if (ex != null) { 651 ApiPlugin.log("Error while resolving class file for: " + typeName + " via " + component.getName(), ex); //$NON-NLS-1$ //$NON-NLS-2$ 652 } 653 return null; 654 } 655 656 /** 657 * Return a string that represents the element type of the given delta. 658 * Returns {@link #UNKNOWN_ELEMENT_KIND} if the element type cannot be 659 * determined. 660 * 661 * @param delta the given delta 662 * @return a string that represents the element type of the given delta. 663 */ getDeltaElementType(IDelta delta)664 public static String getDeltaElementType(IDelta delta) { 665 return getDeltaElementType(delta.getElementType()); 666 } 667 668 /** 669 * Returns a text representation of a marker severity level 670 * 671 * @param severity 672 * @return text of a marker severity level 673 */ getSeverity(int severity)674 public static String getSeverity(int severity) { 675 switch (severity) { 676 case IMarker.SEVERITY_ERROR: { 677 return "ERROR"; //$NON-NLS-1$ 678 } 679 case IMarker.SEVERITY_INFO: { 680 return "INFO"; //$NON-NLS-1$ 681 } 682 case IMarker.SEVERITY_WARNING: { 683 return "WARNING"; //$NON-NLS-1$ 684 } 685 default: { 686 return "UNKNOWN_SEVERITY"; //$NON-NLS-1$ 687 } 688 } 689 } 690 691 /** 692 * Return an int value that represents the given element type Returns -1 if 693 * the element type cannot be determined. 694 * 695 * @param elementType the given element type 696 * @return an int that represents the given element type constant. 697 */ getDeltaElementTypeValue(String elementType)698 public static int getDeltaElementTypeValue(String elementType) { 699 Class<IDelta> IDeltaClass = IDelta.class; 700 try { 701 Field field = IDeltaClass.getField(elementType); 702 return field.getInt(null); 703 } catch (SecurityException | IllegalArgumentException | NoSuchFieldException | IllegalAccessException e) { 704 // ignore 705 } 706 return -1; 707 } 708 709 /** 710 * Return a string that represents the given element type Returns 711 * {@link #UNKNOWN_ELEMENT_KIND} if the element type cannot be determined. 712 * 713 * @param elementType the given element type 714 * @return a string that represents the given element type. 715 */ getDeltaElementType(int elementType)716 public static String getDeltaElementType(int elementType) { 717 switch (elementType) { 718 case IDelta.ANNOTATION_ELEMENT_TYPE: 719 return "ANNOTATION_ELEMENT_TYPE"; //$NON-NLS-1$ 720 case IDelta.INTERFACE_ELEMENT_TYPE: 721 return "INTERFACE_ELEMENT_TYPE"; //$NON-NLS-1$ 722 case IDelta.ENUM_ELEMENT_TYPE: 723 return "ENUM_ELEMENT_TYPE"; //$NON-NLS-1$ 724 case IDelta.API_COMPONENT_ELEMENT_TYPE: 725 return "API_COMPONENT_ELEMENT_TYPE"; //$NON-NLS-1$ 726 case IDelta.API_BASELINE_ELEMENT_TYPE: 727 return "API_BASELINE_ELEMENT_TYPE"; //$NON-NLS-1$ 728 case IDelta.CONSTRUCTOR_ELEMENT_TYPE: 729 return "CONSTRUCTOR_ELEMENT_TYPE"; //$NON-NLS-1$ 730 case IDelta.METHOD_ELEMENT_TYPE: 731 return "METHOD_ELEMENT_TYPE"; //$NON-NLS-1$ 732 case IDelta.FIELD_ELEMENT_TYPE: 733 return "FIELD_ELEMENT_TYPE"; //$NON-NLS-1$ 734 case IDelta.CLASS_ELEMENT_TYPE: 735 return "CLASS_ELEMENT_TYPE"; //$NON-NLS-1$ 736 case IDelta.TYPE_PARAMETER_ELEMENT_TYPE: 737 return "TYPE_PARAMETER_ELEMENT_TYPE"; //$NON-NLS-1$ 738 default: 739 break; 740 } 741 return UNKNOWN_ELEMENT_KIND; 742 } 743 744 /** 745 * Return a string that represents the given flags Returns 746 * {@link #UNKNOWN_FLAGS} if the flags cannot be determined. 747 * 748 * @param flags the given delta's flags 749 * @return a string that represents the given flags. 750 */ getDeltaFlagsName(int flags)751 public static String getDeltaFlagsName(int flags) { 752 switch (flags) { 753 case IDelta.ABSTRACT_TO_NON_ABSTRACT: 754 return "ABSTRACT_TO_NON_ABSTRACT"; //$NON-NLS-1$ 755 case IDelta.ANNOTATION_DEFAULT_VALUE: 756 return "ANNOTATION_DEFAULT_VALUE"; //$NON-NLS-1$ 757 case IDelta.API_COMPONENT: 758 return "API_COMPONENT"; //$NON-NLS-1$ 759 case IDelta.ARRAY_TO_VARARGS: 760 return "ARRAY_TO_VARARGS"; //$NON-NLS-1$ 761 case IDelta.CHECKED_EXCEPTION: 762 return "CHECKED_EXCEPTION"; //$NON-NLS-1$ 763 case IDelta.CLASS_BOUND: 764 return "CLASS_BOUND"; //$NON-NLS-1$ 765 case IDelta.CLINIT: 766 return "CLINIT"; //$NON-NLS-1$ 767 case IDelta.CONSTRUCTOR: 768 return "CONSTRUCTOR"; //$NON-NLS-1$ 769 case IDelta.CONTRACTED_SUPERINTERFACES_SET: 770 return "CONTRACTED_SUPERINTERFACES_SET"; //$NON-NLS-1$ 771 case IDelta.DECREASE_ACCESS: 772 return "DECREASE_ACCESS"; //$NON-NLS-1$ 773 case IDelta.ENUM_CONSTANT: 774 return "ENUM_CONSTANT"; //$NON-NLS-1$ 775 case IDelta.EXECUTION_ENVIRONMENT: 776 return "EXECUTION_ENVIRONMENT"; //$NON-NLS-1$ 777 case IDelta.EXPANDED_SUPERINTERFACES_SET: 778 return "EXPANDED_SUPERINTERFACES_SET"; //$NON-NLS-1$ 779 case IDelta.EXPANDED_SUPERINTERFACES_SET_BREAKING: 780 return "EXPANDED_SUPERINTERFACES_SET_BREAKING"; //$NON-NLS-1$ 781 case IDelta.FIELD: 782 return "FIELD"; //$NON-NLS-1$ 783 case IDelta.FIELD_MOVED_UP: 784 return "FIELD_MOVED_UP"; //$NON-NLS-1$ 785 case IDelta.FINAL_TO_NON_FINAL: 786 return "FINAL_TO_NON_FINAL"; //$NON-NLS-1$ 787 case IDelta.FINAL_TO_NON_FINAL_NON_STATIC: 788 return "FINAL_TO_NON_FINAL_NON_STATIC"; //$NON-NLS-1$ 789 case IDelta.FINAL_TO_NON_FINAL_STATIC_CONSTANT: 790 return "FINAL_TO_NON_FINAL_STATIC_CONSTANT"; //$NON-NLS-1$ 791 case IDelta.FINAL_TO_NON_FINAL_STATIC_NON_CONSTANT: 792 return "FINAL_TO_NON_FINAL_STATIC_NON_CONSTANT"; //$NON-NLS-1$ 793 case IDelta.INCREASE_ACCESS: 794 return "INCREASE_ACCESS"; //$NON-NLS-1$ 795 case IDelta.INTERFACE_BOUND: 796 return "INTERFACE_BOUND"; //$NON-NLS-1$ 797 case IDelta.METHOD: 798 return "METHOD"; //$NON-NLS-1$ 799 case IDelta.DEFAULT_METHOD: 800 case IDelta.SUPER_INTERFACE_DEFAULT_METHOD: 801 return "DEFAULT_METHOD"; //$NON-NLS-1$ 802 case IDelta.METHOD_MOVED_UP: 803 return "METHOD_MOVED_UP"; //$NON-NLS-1$ 804 case IDelta.METHOD_WITH_DEFAULT_VALUE: 805 return "METHOD_WITH_DEFAULT_VALUE"; //$NON-NLS-1$ 806 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 807 return "METHOD_WITHOUT_DEFAULT_VALUE"; //$NON-NLS-1$ 808 case IDelta.NATIVE_TO_NON_NATIVE: 809 return "NATIVE_TO_NON_NATIVE"; //$NON-NLS-1$ 810 case IDelta.NON_ABSTRACT_TO_ABSTRACT: 811 return "NON_ABSTRACT_TO_ABSTRACT"; //$NON-NLS-1$ 812 case IDelta.NON_FINAL_TO_FINAL: 813 return "NON_FINAL_TO_FINAL"; //$NON-NLS-1$ 814 case IDelta.NON_NATIVE_TO_NATIVE: 815 return "NON_NATIVE_TO_NATIVE"; //$NON-NLS-1$ 816 case IDelta.NON_STATIC_TO_STATIC: 817 return "NON_STATIC_TO_STATIC"; //$NON-NLS-1$ 818 case IDelta.NON_SYNCHRONIZED_TO_SYNCHRONIZED: 819 return "NON_SYNCHRONIZED_TO_SYNCHRONIZED"; //$NON-NLS-1$ 820 case IDelta.NON_TRANSIENT_TO_TRANSIENT: 821 return "NON_TRANSIENT_TO_TRANSIENT"; //$NON-NLS-1$ 822 case IDelta.OVERRIDEN_METHOD: 823 return "OVERRIDEN_METHOD"; //$NON-NLS-1$ 824 case IDelta.STATIC_TO_NON_STATIC: 825 return "STATIC_TO_NON_STATIC"; //$NON-NLS-1$ 826 case IDelta.SUPERCLASS: 827 return "SUPERCLASS"; //$NON-NLS-1$ 828 case IDelta.SYNCHRONIZED_TO_NON_SYNCHRONIZED: 829 return "SYNCHRONIZED_TO_NON_SYNCHRONIZED"; //$NON-NLS-1$ 830 case IDelta.TYPE_CONVERSION: 831 return "TYPE_CONVERSION"; //$NON-NLS-1$ 832 case IDelta.TRANSIENT_TO_NON_TRANSIENT: 833 return "TRANSIENT_TO_NON_TRANSIENT"; //$NON-NLS-1$ 834 case IDelta.TYPE: 835 return "TYPE"; //$NON-NLS-1$ 836 case IDelta.TYPE_ARGUMENTS: 837 return "TYPE_ARGUMENTS"; //$NON-NLS-1$ 838 case IDelta.TYPE_MEMBER: 839 return "TYPE_MEMBER"; //$NON-NLS-1$ 840 case IDelta.TYPE_PARAMETER: 841 return "TYPE_PARAMETER"; //$NON-NLS-1$ 842 case IDelta.TYPE_PARAMETER_NAME: 843 return "TYPE_PARAMETER_NAME"; //$NON-NLS-1$ 844 case IDelta.TYPE_PARAMETERS: 845 return "TYPE_PARAMETERS"; //$NON-NLS-1$ 846 case IDelta.TYPE_VISIBILITY: 847 return "TYPE_VISIBILITY"; //$NON-NLS-1$ 848 case IDelta.UNCHECKED_EXCEPTION: 849 return "UNCHECKED_EXCEPTION"; //$NON-NLS-1$ 850 case IDelta.VALUE: 851 return "VALUE"; //$NON-NLS-1$ 852 case IDelta.VARARGS_TO_ARRAY: 853 return "VARARGS_TO_ARRAY"; //$NON-NLS-1$ 854 case IDelta.RESTRICTIONS: 855 return "RESTRICTIONS"; //$NON-NLS-1$ 856 case IDelta.API_TYPE: 857 return "API_TYPE"; //$NON-NLS-1$ 858 case IDelta.NON_VOLATILE_TO_VOLATILE: 859 return "NON_VOLATILE_TO_VOLATILE"; //$NON-NLS-1$ 860 case IDelta.VOLATILE_TO_NON_VOLATILE: 861 return "VOLATILE_TO_NON_VOLATILE"; //$NON-NLS-1$ 862 case IDelta.MINOR_VERSION: 863 return "MINOR_VERSION"; //$NON-NLS-1$ 864 case IDelta.MAJOR_VERSION: 865 return "MAJOR_VERSION"; //$NON-NLS-1$ 866 case IDelta.API_FIELD: 867 return "API_FIELD"; //$NON-NLS-1$ 868 case IDelta.API_METHOD: 869 return "API_METHOD"; //$NON-NLS-1$ 870 case IDelta.API_CONSTRUCTOR: 871 return "API_CONSTRUCTOR"; //$NON-NLS-1$ 872 case IDelta.API_ENUM_CONSTANT: 873 return "API_ENUM_CONSTANT"; //$NON-NLS-1$ 874 case IDelta.API_METHOD_WITH_DEFAULT_VALUE: 875 return "API_METHOD_WITH_DEFAULT_VALUE"; //$NON-NLS-1$ 876 case IDelta.API_METHOD_WITHOUT_DEFAULT_VALUE: 877 return "API_METHOD_WITHOUT_DEFAULT_VALUE"; //$NON-NLS-1$ 878 case IDelta.TYPE_ARGUMENT: 879 return "TYPE_ARGUMENT"; //$NON-NLS-1$ 880 case IDelta.SUPER_INTERFACE_WITH_METHODS: 881 return "SUPER_INTERFACE_WITH_METHODS"; //$NON-NLS-1$ 882 case IDelta.REEXPORTED_API_TYPE: 883 return "REEXPORTED_API_TYPE"; //$NON-NLS-1$ 884 case IDelta.REEXPORTED_TYPE: 885 return "REEXPORTED_TYPE"; //$NON-NLS-1$ 886 case IDelta.METHOD_MOVED_DOWN: 887 return "METHOD_MOVED_DOWN"; //$NON-NLS-1$ 888 case IDelta.DEPRECATION: 889 return "DEPRECATION"; //$NON-NLS-1$ 890 default: 891 break; 892 } 893 return UNKNOWN_FLAGS; 894 } 895 896 /** 897 * Return a string that represents the kind of the given delta. Returns 898 * {@link #UNKNOWN_KIND} if the kind cannot be determined. 899 * 900 * @param delta the given delta 901 * @return a string that represents the kind of the given delta. 902 */ getDeltaKindName(IDelta delta)903 public static String getDeltaKindName(IDelta delta) { 904 return getDeltaKindName(delta.getKind()); 905 } 906 907 /** 908 * Return a string that represents the given kind. Returns 909 * {@link #UNKNOWN_KIND} if the kind cannot be determined. 910 * 911 * @param delta the given kind 912 * @return a string that represents the given kind. 913 */ getDeltaKindName(int kind)914 public static String getDeltaKindName(int kind) { 915 switch (kind) { 916 case IDelta.ADDED: 917 return "ADDED"; //$NON-NLS-1$ 918 case IDelta.CHANGED: 919 return "CHANGED"; //$NON-NLS-1$ 920 case IDelta.REMOVED: 921 return "REMOVED"; //$NON-NLS-1$ 922 default: 923 break; 924 } 925 return UNKNOWN_KIND; 926 } 927 928 /** 929 * Returns the preference key for the given element type, the given kind and 930 * the given flags. 931 * 932 * @param elementType the given element type (retrieved using 933 * {@link IDelta#getElementType()} 934 * @param kind the given kind (retrieved using {@link IDelta#getKind()} 935 * @param flags the given flags (retrieved using {@link IDelta#getFlags()} 936 * @return the preference key for the given element type, the given kind and 937 * the given flags. 938 */ getDeltaPrefererenceKey(int elementType, int kind, int flags)939 public static String getDeltaPrefererenceKey(int elementType, int kind, int flags) { 940 StringBuilder buffer = new StringBuilder(Util.getDeltaElementType(elementType)); 941 buffer.append('_').append(Util.getDeltaKindName(kind)); 942 if (flags != -1) { 943 buffer.append('_'); 944 switch (flags) { 945 case IDelta.API_FIELD: 946 buffer.append(Util.getDeltaFlagsName(IDelta.FIELD)); 947 break; 948 case IDelta.API_ENUM_CONSTANT: 949 buffer.append(Util.getDeltaFlagsName(IDelta.ENUM_CONSTANT)); 950 break; 951 case IDelta.API_CONSTRUCTOR: 952 buffer.append(Util.getDeltaFlagsName(IDelta.CONSTRUCTOR)); 953 break; 954 case IDelta.API_METHOD: 955 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD)); 956 break; 957 case IDelta.API_METHOD_WITH_DEFAULT_VALUE: 958 if (kind == IDelta.REMOVED) { 959 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD)); 960 } else { 961 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD_WITH_DEFAULT_VALUE)); 962 } 963 break; 964 case IDelta.API_METHOD_WITHOUT_DEFAULT_VALUE: 965 if (kind == IDelta.REMOVED) { 966 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD)); 967 } else { 968 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD_WITHOUT_DEFAULT_VALUE)); 969 } 970 break; 971 case IDelta.METHOD_WITH_DEFAULT_VALUE: 972 if (kind == IDelta.REMOVED) { 973 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD)); 974 } else { 975 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD_WITH_DEFAULT_VALUE)); 976 } 977 break; 978 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 979 if (kind == IDelta.REMOVED) { 980 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD)); 981 } else { 982 buffer.append(Util.getDeltaFlagsName(IDelta.METHOD_WITHOUT_DEFAULT_VALUE)); 983 } 984 break; 985 default: 986 buffer.append(Util.getDeltaFlagsName(flags)); 987 } 988 } 989 return String.valueOf(buffer); 990 } 991 992 /** 993 * Returns the details of the API delta as a string 994 * 995 * @param delta 996 * @return the details of the delta as a string 997 */ getDetail(IDelta delta)998 public static String getDetail(IDelta delta) { 999 StringBuilder buffer = new StringBuilder(); 1000 switch (delta.getElementType()) { 1001 case IDelta.CLASS_ELEMENT_TYPE: 1002 buffer.append("class"); //$NON-NLS-1$ 1003 break; 1004 case IDelta.ANNOTATION_ELEMENT_TYPE: 1005 buffer.append("annotation"); //$NON-NLS-1$ 1006 break; 1007 case IDelta.INTERFACE_ELEMENT_TYPE: 1008 buffer.append("interface"); //$NON-NLS-1$ 1009 break; 1010 case IDelta.API_COMPONENT_ELEMENT_TYPE: 1011 buffer.append("api component"); //$NON-NLS-1$ 1012 break; 1013 case IDelta.API_BASELINE_ELEMENT_TYPE: 1014 buffer.append("api baseline"); //$NON-NLS-1$ 1015 break; 1016 case IDelta.METHOD_ELEMENT_TYPE: 1017 buffer.append("method"); //$NON-NLS-1$ 1018 break; 1019 case IDelta.CONSTRUCTOR_ELEMENT_TYPE: 1020 buffer.append("constructor"); //$NON-NLS-1$ 1021 break; 1022 case IDelta.ENUM_ELEMENT_TYPE: 1023 buffer.append("enum"); //$NON-NLS-1$ 1024 break; 1025 case IDelta.FIELD_ELEMENT_TYPE: 1026 buffer.append("field"); //$NON-NLS-1$ 1027 break; 1028 default: 1029 break; 1030 } 1031 buffer.append(' '); 1032 switch (delta.getKind()) { 1033 case IDelta.ADDED: 1034 buffer.append("added"); //$NON-NLS-1$ 1035 break; 1036 case IDelta.REMOVED: 1037 buffer.append("removed"); //$NON-NLS-1$ 1038 break; 1039 case IDelta.CHANGED: 1040 buffer.append("changed"); //$NON-NLS-1$ 1041 break; 1042 default: 1043 buffer.append("unknown kind"); //$NON-NLS-1$ 1044 break; 1045 } 1046 buffer.append(' ').append(getDeltaFlagsName(delta.getFlags())).append(' ').append(delta.getTypeName()).append("#").append(delta.getKey()); //$NON-NLS-1$ 1047 return String.valueOf(buffer); 1048 } 1049 1050 /** 1051 * Returns the {@link IDocument} for the specified {@link ICompilationUnit} 1052 * 1053 * @param cu 1054 * @return the {@link IDocument} for the specified {@link ICompilationUnit} 1055 * @throws CoreException 1056 */ getDocument(ICompilationUnit cu)1057 public static IDocument getDocument(ICompilationUnit cu) throws CoreException { 1058 if (cu.getOwner() == null) { 1059 IFile file = (IFile) cu.getResource(); 1060 if (file.exists()) { 1061 ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); 1062 IPath path = cu.getPath(); 1063 bufferManager.connect(path, LocationKind.IFILE, new NullProgressMonitor()); 1064 try { 1065 return bufferManager.getTextFileBuffer(path, LocationKind.IFILE).getDocument(); 1066 } finally { 1067 bufferManager.disconnect(path, LocationKind.IFILE, null); 1068 } 1069 } 1070 } 1071 return new org.eclipse.jface.text.Document(cu.getSource()); 1072 } 1073 1074 /** 1075 * Returns the OSGi profile properties corresponding to the given execution 1076 * environment id, or <code>null</code> if none. 1077 * 1078 * @param eeId OSGi profile identifier 1079 * 1080 * @return the corresponding properties or <code>null</code> if none 1081 */ getEEProfile(String eeId)1082 public static Properties getEEProfile(String eeId) { 1083 String profileName = eeId + ".profile"; //$NON-NLS-1$ 1084 InputStream stream = Util.class.getResourceAsStream("profiles/" + profileName); //$NON-NLS-1$ 1085 if (stream != null) { 1086 try { 1087 Properties profile = new Properties(); 1088 profile.load(stream); 1089 return profile; 1090 } catch (IOException e) { 1091 ApiPlugin.log(e); 1092 } finally { 1093 try { 1094 stream.close(); 1095 } catch (IOException e) { 1096 ApiPlugin.log(e); 1097 } 1098 } 1099 } 1100 return null; 1101 } 1102 1103 /** 1104 * Returns the number of fragments for the given version value, -1 if the 1105 * format is unknown. The version is formed like: [optional plug-in name] 1106 * major.minor.micro.qualifier. 1107 * 1108 * @param version the given version value 1109 * @return the number of fragments for the given version value or -1 if the 1110 * format is unknown 1111 * @throws IllegalArgumentException if version is null 1112 */ getFragmentNumber(String version)1113 public static final int getFragmentNumber(String version) { 1114 if (version == null) { 1115 throw new IllegalArgumentException("The given version should not be null"); //$NON-NLS-1$ 1116 } 1117 int index = version.indexOf(' '); 1118 char[] charArray = version.toCharArray(); 1119 int length = charArray.length; 1120 if (index + 1 >= length) { 1121 return -1; 1122 } 1123 int counter = 1; 1124 for (int i = index + 1; i < length; i++) { 1125 switch (charArray[i]) { 1126 case '0': 1127 case '1': 1128 case '2': 1129 case '3': 1130 case '4': 1131 case '5': 1132 case '6': 1133 case '7': 1134 case '8': 1135 case '9': 1136 continue; 1137 case '.': 1138 counter++; 1139 break; 1140 default: 1141 return -1; 1142 } 1143 } 1144 return counter; 1145 } 1146 getIMember(IDelta delta, IJavaProject javaProject)1147 public static IMember getIMember(IDelta delta, IJavaProject javaProject) { 1148 String typeName = delta.getTypeName(); 1149 if (typeName == null) { 1150 return null; 1151 } 1152 IType type = null; 1153 try { 1154 type = javaProject.findType(typeName.replace('$', '.')); 1155 } catch (JavaModelException e) { 1156 // ignore 1157 } 1158 IType typeInProject = getTypeInSameJavaProject(type, typeName, javaProject); 1159 if (typeInProject != null) { 1160 type = typeInProject; 1161 } 1162 if (type instanceof BinaryType) { 1163 IType sourceType = Util.findSourceTypeinJavaProject(javaProject, delta.getTypeName().replace('$', '.')); 1164 if (sourceType != null) { 1165 type = sourceType; 1166 } 1167 } 1168 if (type == null) { 1169 return null; 1170 } 1171 String key = delta.getKey(); 1172 switch (delta.getElementType()) { 1173 case IDelta.FIELD_ELEMENT_TYPE: { 1174 IField field = type.getField(key); 1175 if (field.exists()) { 1176 return field; 1177 } 1178 } 1179 break; 1180 case IDelta.CLASS_ELEMENT_TYPE: 1181 case IDelta.ANNOTATION_ELEMENT_TYPE: 1182 case IDelta.INTERFACE_ELEMENT_TYPE: 1183 case IDelta.ENUM_ELEMENT_TYPE: 1184 // we report the marker on the type 1185 switch (delta.getKind()) { 1186 case IDelta.ADDED: 1187 switch (delta.getFlags()) { 1188 case IDelta.FIELD: 1189 case IDelta.ENUM_CONSTANT: 1190 IField field = type.getField(key); 1191 if (field.exists()) { 1192 return field; 1193 } 1194 break; 1195 case IDelta.METHOD_WITH_DEFAULT_VALUE: 1196 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 1197 case IDelta.METHOD: 1198 case IDelta.DEFAULT_METHOD: 1199 case IDelta.SUPER_INTERFACE_DEFAULT_METHOD: 1200 case IDelta.CONSTRUCTOR: 1201 return getMethod(type, key); 1202 case IDelta.TYPE_MEMBER: 1203 IType type2 = type.getType(key); 1204 if (type2.exists()) { 1205 return type2; 1206 } 1207 break; 1208 default: 1209 break; 1210 } 1211 break; 1212 case IDelta.REMOVED: 1213 switch (delta.getFlags()) { 1214 case IDelta.API_FIELD: 1215 case IDelta.API_ENUM_CONSTANT: 1216 IField field = type.getField(key); 1217 if (field.exists()) { 1218 return field; 1219 } 1220 break; 1221 case IDelta.API_METHOD_WITH_DEFAULT_VALUE: 1222 case IDelta.API_METHOD_WITHOUT_DEFAULT_VALUE: 1223 case IDelta.API_METHOD: 1224 case IDelta.API_CONSTRUCTOR: 1225 return getMethod(type, key); 1226 default: 1227 break; 1228 } 1229 break; 1230 default: 1231 break; 1232 } 1233 return type; 1234 case IDelta.METHOD_ELEMENT_TYPE: 1235 case IDelta.CONSTRUCTOR_ELEMENT_TYPE: { 1236 return getMethod(type, key); 1237 } 1238 case IDelta.API_COMPONENT_ELEMENT_TYPE: 1239 return type; 1240 default: 1241 break; 1242 } 1243 return null; 1244 } 1245 updateType(String typeName, IJavaProject javaProject)1246 public static IType updateType(String typeName, IJavaProject javaProject) throws JavaModelException { 1247 String typeNameWithDot = typeName.replace('$', '.'); 1248 String typeNameWithSeparator = typeNameWithDot.replace(".", "/"); //$NON-NLS-1$ //$NON-NLS-2$ 1249 IPath path = new Path(typeNameWithSeparator); 1250 IPath pathExceptLastSegment = path.removeLastSegments(1); 1251 IJavaElement packFrag = javaProject.findElement(pathExceptLastSegment, DefaultWorkingCopyOwner.PRIMARY); 1252 if (packFrag instanceof PackageFragment) { 1253 PackageFragment pf = (PackageFragment) packFrag; 1254 ArrayList<?> children = pf.getChildrenOfType(IJavaElement.COMPILATION_UNIT); 1255 for (Object object : children) { 1256 if (object instanceof CompilationUnit) { 1257 CompilationUnit compilationUn = (CompilationUnit) object; 1258 ITypeRoot typeRoot = compilationUn.getTypeRoot(); 1259 if (typeRoot.findPrimaryType() == null) { 1260 continue; 1261 } 1262 if (typeRoot.findPrimaryType().getFullyQualifiedName().equals(typeName.replace('$', '.'))) { 1263 return typeRoot.findPrimaryType(); 1264 } 1265 } 1266 1267 } 1268 ArrayList<?> children2 = pf.getChildrenOfType(IJavaElement.CLASS_FILE); 1269 for (Object object : children2) { 1270 if (object instanceof ClassFile) { 1271 ClassFile compilationUn = (ClassFile) object; 1272 ITypeRoot typeRoot = compilationUn.getTypeRoot(); 1273 if (typeRoot.findPrimaryType() == null) { 1274 continue; 1275 } 1276 if (typeRoot.findPrimaryType().getFullyQualifiedName().equals(typeName.replace('$', '.'))) { 1277 return typeRoot.findPrimaryType(); 1278 } 1279 } 1280 } 1281 } 1282 return null; 1283 1284 } 1285 1286 /** 1287 * Checks if type is not in the same project, then it tries to get the type 1288 * in the same project and return type if it could find one. It return null 1289 * if type is in the same project or if type cannot be found in the same 1290 * project. 1291 * 1292 * @param type 1293 * @param typeName 1294 * @param javaProject 1295 * @return 1296 */ 1297 getTypeInSameJavaProject(IType type, String typeName, IJavaProject javaProject)1298 public static IType getTypeInSameJavaProject(IType type, String typeName, IJavaProject javaProject) { 1299 if (type == null) { 1300 return null; 1301 } 1302 IJavaElement ancestor = type.getAncestor(IJavaElement.JAVA_PROJECT); 1303 IType newType = null; 1304 try { 1305 if (ancestor instanceof IJavaProject) { 1306 IJavaProject pro = (IJavaProject) ancestor; 1307 if (!pro.equals(javaProject)) { 1308 newType = updateType(typeName, javaProject); 1309 if (newType != null) { 1310 return newType; 1311 } 1312 } 1313 } 1314 } 1315 catch (Exception e) { 1316 // return null 1317 } 1318 return newType; 1319 } 1320 getMethod(IType type, String key)1321 private static IMember getMethod(IType type, String key) { 1322 boolean isGeneric = false; 1323 int indexOfTypeVariable = key.indexOf('<'); 1324 int index = 0; 1325 if (indexOfTypeVariable == -1) { 1326 int indexOfParen = key.indexOf('('); 1327 if (indexOfParen == -1) { 1328 return null; 1329 } 1330 index = indexOfParen; 1331 } else { 1332 int indexOfParen = key.indexOf('('); 1333 if (indexOfParen == -1) { 1334 return null; 1335 } 1336 if (indexOfParen < indexOfTypeVariable) { 1337 index = indexOfParen; 1338 } else { 1339 index = indexOfTypeVariable; 1340 isGeneric = true; 1341 } 1342 } 1343 String selector = key.substring(0, index); 1344 String descriptor = key.substring(index, key.length()); 1345 IMethod method = null; 1346 String signature = descriptor.replace('/', '.'); 1347 String[] parameterTypes = null; 1348 if (isGeneric) { 1349 // remove all type variables first 1350 signature = signature.substring(signature.indexOf('(')); 1351 parameterTypes = Signature.getParameterTypes(signature); 1352 } else { 1353 parameterTypes = Signature.getParameterTypes(signature); 1354 } 1355 1356 try { 1357 method = type.getMethod(selector, parameterTypes); 1358 } catch (IllegalArgumentException e) { 1359 ApiPlugin.log(e); 1360 } 1361 if (method == null) { 1362 return null; 1363 } 1364 if (method.exists()) { 1365 return method; 1366 } 1367 // if the method is not null and it doesn't exist, it might be the 1368 // default constructor or a constructor in inner type 1369 if (selector.equals(type.getElementName())) { 1370 if (parameterTypes.length == 0) { 1371 return null; 1372 } 1373 // Perhaps a constructor on an inner type? 1374 IJavaElement parent = type.getParent(); 1375 if (parent instanceof IType) { 1376 String parentTypeSig = Signature.createTypeSignature(((IType) parent).getFullyQualifiedName(), true); 1377 if (Signatures.matches(parentTypeSig, parameterTypes[0])) { 1378 IMethod constructor = type.getMethod(selector, Arrays.copyOfRange(parameterTypes, 1, parameterTypes.length)); 1379 try { 1380 if (constructor.exists() && constructor.isConstructor()) { 1381 return constructor; 1382 } 1383 String contructorSig = Signature.createMethodSignature(Arrays.copyOfRange(parameterTypes, 1, parameterTypes.length), Signature.getReturnType(signature)); 1384 IMethod[] methods = type.findMethods(constructor); 1385 if (methods != null) { 1386 if (methods.length == 1 && methods[0].isConstructor()) { 1387 return methods[0]; 1388 } 1389 // findMethods() checks simple type names, so 1390 // it's possible to have multiple matches with 1391 // different package names 1392 for (IMethod m : methods) { 1393 try { 1394 if (m.isConstructor() && m.getNumberOfParameters() == parameterTypes.length - 1 && Signatures.matchesSignatures(generateBinarySignature(m), contructorSig)) { 1395 return m; 1396 } 1397 } catch (JavaModelException e) { 1398 // ignore 1399 } 1400 } 1401 } 1402 } catch (JavaModelException e) { 1403 // ignore 1404 } 1405 } 1406 } 1407 } 1408 // Let JDT have a go 1409 IMethod[] methods = type.findMethods(method); 1410 if (methods != null && methods.length == 1) { 1411 /* exact match found */ 1412 return methods[0]; 1413 } 1414 if (methods == null || methods.length == 0) { 1415 /* no methods found: may be due to type erasure */ 1416 try { 1417 methods = type.getMethods(); 1418 } catch (JavaModelException e) { 1419 ApiPlugin.log(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind("Unable to retrieve methods for {0}", type.getFullyQualifiedName()), e)); //$NON-NLS-1$ 1420 return null; 1421 } 1422 } 1423 1424 /* 1425 * findMethods() checks simple type names, so it's possible to have 1426 * multiple matches with different package names. Or we may need to 1427 * check with type erasure. 1428 */ 1429 for (IMethod m : methods) { 1430 try { 1431 if (!m.getElementName().equals(selector) || m.getNumberOfParameters() != parameterTypes.length) { 1432 continue; 1433 } 1434 if (Signatures.matchesSignatures(generateBinarySignature(m), signature)) { 1435 return m; 1436 } 1437 } catch (JavaModelException e) { 1438 // ignore 1439 } 1440 } 1441 1442 /* 1443 * Unclear what circumstances that this could happen, so provide more 1444 * information to help understand why 1445 */ 1446 StringBuilder sb = new StringBuilder(); 1447 for (IMethod m : methods) { 1448 sb.append('\n').append(m.getHandleIdentifier()); 1449 } 1450 ApiPlugin.log(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, NLS.bind(UtilMessages.Util_6, new String[] { 1451 selector, descriptor }) + sb.toString())); 1452 // do not default to the enclosing type - see bug 224713 1453 return null; 1454 } 1455 1456 /** 1457 * Generate the binary signature for the provided method. This is the 1458 * type-erased signature written out to the .class file. 1459 * 1460 * @param method the method 1461 * @return the method signature as would be encoded in a .class file 1462 * @throws JavaModelException 1463 */ generateBinarySignature(IMethod method)1464 private static String generateBinarySignature(IMethod method) throws JavaModelException { 1465 ITypeParameter[] typeTPs = method.getDeclaringType().getTypeParameters(); 1466 ITypeParameter[] methodTPs = method.getTypeParameters(); 1467 if (typeTPs.length == 0 && methodTPs.length == 0) { 1468 return method.getSignature(); 1469 } 1470 Map<String, String> lookup = new HashMap<>(); 1471 Stream.concat(Stream.of(typeTPs), Stream.of(methodTPs)).forEach(tp -> { 1472 try { 1473 String sigs[] = tp.getBoundsSignatures(); 1474 lookup.put(tp.getElementName(), sigs.length == 1 ? sigs[0] : "Ljava.lang.Object;"); //$NON-NLS-1$ 1475 } catch (JavaModelException e) { 1476 /* ignore */ 1477 } 1478 }); 1479 String[] parameterTypes = Stream.of(method.getParameterTypes()).map(p -> expandParameterType(p, lookup)).toArray(String[]::new); 1480 return Signature.createMethodSignature(parameterTypes, expandParameterType(method.getReturnType(), lookup)); 1481 } 1482 1483 /** 1484 * Rewrite a parameter type signature with type erasure and using the 1485 * parameterized type bounds lookup table. For example: 1486 * 1487 * <pre> 1488 * expand("QList<QE;>;", {"E" → "Ljava.lang.Object;"}) = "QList;" 1489 * expand("QE;", {"E" → "Ljava.lang.Object;"}) = "Ljava.lang.Object;" 1490 * </pre> 1491 * 1492 * @param parameterTypeSig the type signature for a parameter 1493 * @param bounds the type bounds as expressed on the method and class 1494 * @return a rewritten parameter type signature as would be found in the .class file 1495 */ expandParameterType(String parameterTypeSig, Map<String, String> bounds)1496 private static String expandParameterType(String parameterTypeSig, Map<String, String> bounds) { 1497 String erased = Signature.getTypeErasure(parameterTypeSig); 1498 if (erased.charAt(0) == Signature.C_UNRESOLVED || erased.charAt(0) == Signature.C_TYPE_VARIABLE) { 1499 String repl = bounds.get(Signature.getSignatureSimpleName(erased)); 1500 if (repl != null) { 1501 return repl; 1502 } 1503 } 1504 return erased; 1505 } 1506 1507 /** 1508 * Returns the given input stream as a byte array 1509 * 1510 * @param stream the stream to get as a byte array 1511 * @param length the length to read from the stream or -1 for unknown 1512 * @return the given input stream as a byte array 1513 * @throws IOException 1514 */ getInputStreamAsByteArray(InputStream stream, int length)1515 public static byte[] getInputStreamAsByteArray(InputStream stream, int length) throws IOException { 1516 byte[] contents; 1517 if (length == -1) { 1518 contents = new byte[0]; 1519 int contentsLength = 0; 1520 int amountRead = -1; 1521 do { 1522 // read at least 8K 1523 int amountRequested = Math.max(stream.available(), DEFAULT_READING_SIZE); 1524 // resize contents if needed 1525 if (contentsLength + amountRequested > contents.length) { 1526 System.arraycopy(contents, 0, contents = new byte[contentsLength + amountRequested], 0, contentsLength); 1527 } 1528 // read as many bytes as possible 1529 amountRead = stream.read(contents, contentsLength, amountRequested); 1530 if (amountRead > 0) { 1531 // remember length of contents 1532 contentsLength += amountRead; 1533 } 1534 } while (amountRead != -1); 1535 // resize contents if necessary 1536 if (contentsLength < contents.length) { 1537 System.arraycopy(contents, 0, contents = new byte[contentsLength], 0, contentsLength); 1538 } 1539 } else { 1540 contents = new byte[length]; 1541 int len = 0; 1542 int readSize = 0; 1543 while ((readSize != -1) && (len != length)) { 1544 // See PR 1FMS89U 1545 // We record first the read size. In this case length is the 1546 // actual 1547 // read size. 1548 len += readSize; 1549 readSize = stream.read(contents, len, length - len); 1550 } 1551 } 1552 return contents; 1553 } 1554 1555 /** 1556 * Returns the given input stream's contents as a character array. If a 1557 * length is specified (i.e. if length != -1), this represents the number of 1558 * bytes in the stream. Note the specified stream is not closed in this 1559 * method 1560 * 1561 * @param stream the stream to get convert to the char array 1562 * @param length the length of the input stream, or -1 if unknown 1563 * @param encoding the encoding to use when reading the stream 1564 * @return the given input stream's contents as a character array. 1565 * @throws IOException if a problem occurred reading the stream. 1566 */ getInputStreamAsCharArray(InputStream stream, int length, String encoding)1567 public static char[] getInputStreamAsCharArray(InputStream stream, int length, String encoding) throws IOException { 1568 Charset charset = null; 1569 try { 1570 charset = Charset.forName(encoding); 1571 } catch (IllegalCharsetNameException e) { 1572 System.err.println("Illegal charset name : " + encoding); //$NON-NLS-1$ 1573 return null; 1574 } catch (UnsupportedCharsetException e) { 1575 System.err.println("Unsupported charset : " + encoding); //$NON-NLS-1$ 1576 return null; 1577 } 1578 return getInputStreamAsCharArray(stream, length, charset); 1579 } 1580 1581 /** 1582 * Returns the given input stream's contents as a character array. If a 1583 * length is specified (i.e. if length != -1), this represents the number of 1584 * bytes in the stream. Note the specified stream is not closed in this 1585 * method 1586 * 1587 * @param stream the stream to get convert to the char array 1588 * @param length the length of the input stream, or -1 if unknown 1589 * @param charset the encoding to use when reading the stream 1590 * @return the given input stream's contents as a character array. 1591 * @throws IOException if a problem occurred reading the stream. 1592 */ getInputStreamAsCharArray(InputStream stream, int length, Charset charset)1593 public static char[] getInputStreamAsCharArray(InputStream stream, int length, Charset charset) throws IOException { 1594 CharsetDecoder charsetDecoder = charset.newDecoder(); 1595 1596 charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); 1597 byte[] contents = getInputStreamAsByteArray(stream, length); 1598 ByteBuffer byteBuffer = ByteBuffer.allocate(contents.length); 1599 byteBuffer.put(contents); 1600 byteBuffer.flip(); 1601 CharBuffer charBuffer = charsetDecoder.decode(byteBuffer); 1602 charBuffer.compact(); // ensure pay-load starting at 0 1603 char[] array = charBuffer.array(); 1604 int lengthToBe = charBuffer.position(); 1605 if (array.length > lengthToBe) { 1606 System.arraycopy(array, 0, (array = new char[lengthToBe]), 0, lengthToBe); 1607 } 1608 return array; 1609 } 1610 1611 /** 1612 * Tries to find the 'MANIFEST.MF' file with in the given project in the 1613 * 'META-INF folder'. 1614 * 1615 * @param currentProject 1616 * @return a handle to the manifest file or <code>null</code> if not found 1617 */ getManifestFile(IProject currentProject)1618 public static IResource getManifestFile(IProject currentProject) { 1619 return currentProject.findMember("META-INF/MANIFEST.MF"); //$NON-NLS-1$ 1620 } 1621 1622 /** 1623 * Returns if the given {@link IMarker} is representing an 1624 * {@link org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem} 1625 * or not 1626 * 1627 * @param marker the marker to check 1628 * @return true if the marker is for an 1629 * {@link org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem} 1630 * false otherwise 1631 * @throws CoreException 1632 */ isApiProblemMarker(IMarker marker)1633 public static boolean isApiProblemMarker(IMarker marker) { 1634 return marker.getAttribute(IApiMarkerConstants.API_MARKER_ATTR_ID, -1) > 0; 1635 } 1636 1637 /** 1638 * Returns a reference type for the given fully qualified type name. 1639 * 1640 * @param fullyQualifiedName type name 1641 * @return reference type 1642 */ getType(String fullyQualifiedName)1643 public static IReferenceTypeDescriptor getType(String fullyQualifiedName) { 1644 int index = fullyQualifiedName.lastIndexOf('.'); 1645 String pkg = index == -1 ? DEFAULT_PACKAGE_NAME : fullyQualifiedName.substring(0, index); 1646 String type = index == -1 ? fullyQualifiedName : fullyQualifiedName.substring(index + 1); 1647 return Factory.packageDescriptor(pkg).getType(type); 1648 } 1649 1650 /** 1651 * Returns if the given project is API enabled 1652 * 1653 * @param project the given project 1654 * @return true if the project is API enabled, false otherwise 1655 */ isApiProject(IProject project)1656 public static boolean isApiProject(IProject project) { 1657 try { 1658 return project.hasNature(ApiPlugin.NATURE_ID); 1659 } catch (CoreException e) { 1660 return false; 1661 } 1662 } 1663 1664 /** 1665 * Returns if the given project is a java project 1666 * 1667 * @param project the given project 1668 * @return <code>true</code> if the project is a java project, 1669 * <code>false</code> otherwise 1670 */ isJavaProject(IProject project)1671 public static boolean isJavaProject(IProject project) { 1672 try { 1673 return project.hasNature(JavaCore.NATURE_ID); 1674 } catch (CoreException e) { 1675 return false; 1676 } 1677 } 1678 1679 /** 1680 * Returns if the given project is API enabled 1681 * 1682 * @param project the given project 1683 * @return <code>true</code> if the project is API enabled, 1684 * <code>false</code> otherwise 1685 */ isApiProject(IJavaProject project)1686 public static boolean isApiProject(IJavaProject project) { 1687 if (project != null) { 1688 return isApiProject(project.getProject()); 1689 } 1690 return false; 1691 } 1692 1693 /** 1694 * Returns if the given {@link IApiComponent} is a valid 1695 * {@link IApiComponent} 1696 * 1697 * @param apiComponent the given component 1698 * @return true if the given {@link IApiComponent} is valid, false otherwise 1699 */ isApiToolsComponent(IApiComponent apiComponent)1700 public static boolean isApiToolsComponent(IApiComponent apiComponent) { 1701 File file = new File(apiComponent.getLocation()); 1702 if (file.exists()) { 1703 if (file.isDirectory()) { 1704 // directory binary bundle 1705 File apiDescription = new File(file, IApiCoreConstants.API_DESCRIPTION_XML_NAME); 1706 return apiDescription.exists(); 1707 } 1708 ZipFile zipFile = null; 1709 try { 1710 zipFile = new ZipFile(file); 1711 return zipFile.getEntry(IApiCoreConstants.API_DESCRIPTION_XML_NAME) != null; 1712 } catch (IOException e) { 1713 // ignore 1714 } finally { 1715 try { 1716 if (zipFile != null) { 1717 zipFile.close(); 1718 } 1719 } catch (IOException e) { 1720 // ignore 1721 } 1722 } 1723 } 1724 return false; 1725 } 1726 1727 /** 1728 * Returns if the given {@link IApiComponent} has java package/s 1729 * {@link IApiComponent} 1730 * 1731 * @param apiComponent the given component 1732 * @return true if the given {@link IApiComponent} has java package/s, false 1733 * otherwise 1734 */ hasJavaPackages(IApiComponent apiComponent)1735 public static boolean hasJavaPackages(IApiComponent apiComponent) { 1736 try { 1737 String[] packageNames = apiComponent.getPackageNames(); 1738 return packageNames.length > 0; 1739 1740 } catch (CoreException e) { 1741 ApiPlugin.log("Failed to check java packages for " + apiComponent.getName(), e); //$NON-NLS-1$ 1742 } 1743 return true; 1744 } 1745 1746 /** 1747 * Returns if the specified file name is an archive name. A name is 1748 * considered to be an archive name if it ends with either '.zip' or '.jar' 1749 * 1750 * @param fileName 1751 * @return true if the file name is an archive name false otherwise 1752 */ isArchive(String fileName)1753 public static boolean isArchive(String fileName) { 1754 return isZipJarFile(fileName) || isTGZFile(fileName); 1755 } 1756 1757 /** 1758 * Returns if the given file name represents a 'standard' archive, where the 1759 * name has an extension of *.zip or *.jar 1760 * 1761 * @param fileName 1762 * @return true if the given file name is that of a 'standard' archive, 1763 * false otherwise 1764 */ isZipJarFile(String fileName)1765 public static boolean isZipJarFile(String fileName) { 1766 String normalizedFileName = fileName.toLowerCase(); 1767 return normalizedFileName.endsWith(DOT_ZIP) || normalizedFileName.endsWith(DOT_JAR); 1768 } 1769 1770 /** 1771 * Returns if the given file name represents a G-zip file name, where the 1772 * name has an extension of *.tar.gz or *.tgz 1773 * 1774 * @param fileName 1775 * @return true if the given file name is that of a G-zip archive, false 1776 * otherwise 1777 */ isTGZFile(String fileName)1778 public static boolean isTGZFile(String fileName) { 1779 String normalizedFileName = fileName.toLowerCase(); 1780 return normalizedFileName.endsWith(DOT_TAR_GZ) || normalizedFileName.endsWith(DOT_TGZ); 1781 } 1782 1783 /** 1784 * Returns if the flags are for a class 1785 * 1786 * @param accessFlags the given access flags 1787 * @return 1788 */ isClass(int accessFlags)1789 public static boolean isClass(int accessFlags) { 1790 return (accessFlags & (Opcodes.ACC_ENUM | Opcodes.ACC_ANNOTATION | Opcodes.ACC_INTERFACE)) == 0; 1791 } 1792 1793 /** 1794 * Returns if the specified file name is for a class file. A name is 1795 * considered to be a class file if it ends in '.class' 1796 * 1797 * @param fileName 1798 * @return true if the name is for a class file false otherwise 1799 */ isClassFile(String fileName)1800 public static boolean isClassFile(String fileName) { 1801 return fileName.toLowerCase().endsWith(DOT_CLASS_SUFFIX); 1802 } 1803 isDefault(int accessFlags)1804 public static boolean isDefault(int accessFlags) { 1805 // none of the private, protected or public bit is set 1806 return (accessFlags & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) == 0; 1807 } 1808 isDifferentVersion(String versionToBeChecked, String referenceVersion)1809 public static final boolean isDifferentVersion(String versionToBeChecked, String referenceVersion) { 1810 SinceTagVersion sinceTagVersion1 = null; 1811 SinceTagVersion sinceTagVersion2 = null; 1812 try { 1813 sinceTagVersion1 = new SinceTagVersion(versionToBeChecked); 1814 sinceTagVersion2 = new SinceTagVersion(referenceVersion); 1815 } catch (IllegalArgumentException e) { 1816 // We cannot compare the two versions as their format is unknown 1817 // TODO (olivier) should we report these as malformed tags? 1818 return false; 1819 } 1820 Version version1 = sinceTagVersion1.getVersion(); 1821 Version version2 = sinceTagVersion2.getVersion(); 1822 if (version1.getMajor() != version2.getMajor()) { 1823 return true; 1824 } 1825 if (version1.getMinor() != version2.getMinor()) { 1826 return true; 1827 } 1828 if (version1.getMicro() != version2.getMicro()) { 1829 return true; 1830 } 1831 return false; 1832 } 1833 1834 /** 1835 * Returns if the specified file name is for a java source file. A name is 1836 * considered to be a java source file if it ends in '.java' 1837 * 1838 * @param fileName 1839 * @return true if the name is for a java source file, false otherwise 1840 */ isJavaFileName(String fileName)1841 public static boolean isJavaFileName(String fileName) { 1842 return fileName.toLowerCase().endsWith(DOT_JAVA_SUFFIX); 1843 } 1844 1845 /** 1846 * Returns if the given name is {@link java.lang.Object} 1847 * 1848 * @param name 1849 * @return true if the name is java.lang.Object, false otherwise 1850 */ isJavaLangObject(String name)1851 public static boolean isJavaLangObject(String name) { 1852 return name != null && name.equals(JAVA_LANG_OBJECT); 1853 } 1854 1855 /** 1856 * Return if the name is {@link java.lang.RuntimeException} 1857 * 1858 * @param name 1859 * @return true if the name is java.lang.RuntimeException, false otherwise 1860 */ isJavaLangRuntimeException(String name)1861 public static boolean isJavaLangRuntimeException(String name) { 1862 return name != null && name.equals(JAVA_LANG_RUNTIMEEXCEPTION); 1863 } 1864 isVisible(int modifiers)1865 public static boolean isVisible(int modifiers) { 1866 return Flags.isProtected(modifiers) || Flags.isPublic(modifiers); 1867 } 1868 isBinaryProject(IProject project)1869 public static boolean isBinaryProject(IProject project) { 1870 return org.eclipse.pde.internal.core.WorkspaceModelManager.isBinaryProject(project); 1871 } 1872 1873 /** 1874 * Returns a new XML document. 1875 * 1876 * @return document 1877 * @throws CoreException if unable to create a new document 1878 */ newDocument()1879 public static Document newDocument() throws CoreException { 1880 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 1881 DocumentBuilder docBuilder = null; 1882 try { 1883 docBuilder = dfactory.newDocumentBuilder(); 1884 } catch (ParserConfigurationException e) { 1885 abort("Unable to create new XML document.", e); //$NON-NLS-1$ 1886 } 1887 Document doc = docBuilder.newDocument(); 1888 return doc; 1889 } 1890 1891 /** 1892 * Parses the given string representing an XML document, returning its root 1893 * element. 1894 * 1895 * @param document XML document as a string 1896 * @return the document's root element 1897 * @throws CoreException if unable to parse the document 1898 */ parseDocument(String document)1899 public static Element parseDocument(String document) throws CoreException { 1900 Element root = null; 1901 InputStream stream = null; 1902 try { 1903 DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 1904 parser.setErrorHandler(new DefaultHandler()); 1905 stream = new ByteArrayInputStream(document.getBytes(StandardCharsets.UTF_8)); 1906 root = parser.parse(stream).getDocumentElement(); 1907 } catch (ParserConfigurationException | FactoryConfigurationError | SAXException | IOException e) { 1908 abort("Unable to parse XML document.", e); //$NON-NLS-1$ 1909 } finally { 1910 try { 1911 if (stream != null) { 1912 stream.close(); 1913 } 1914 } catch (IOException e) { 1915 abort("Unable to parse XML document.", e); //$NON-NLS-1$ 1916 } 1917 } 1918 return root; 1919 } 1920 1921 /** 1922 * Save the given contents into the given file. The file parent folder must 1923 * exist. 1924 * 1925 * @param file the given file target 1926 * @param contents the given contents 1927 * @throws IOException if an IOException occurs while saving the file 1928 */ saveFile(File file, String contents)1929 public static void saveFile(File file, String contents) throws IOException { 1930 BufferedWriter writer = null; 1931 try { 1932 writer = new BufferedWriter(new FileWriter(file)); 1933 writer.write(contents); 1934 writer.flush(); 1935 } finally { 1936 if (writer != null) { 1937 try { 1938 writer.close(); 1939 } catch (IOException e) { 1940 // ignore 1941 } 1942 } 1943 } 1944 } 1945 1946 /** 1947 * Returns the contents of the given file as a string, or <code>null</code> 1948 * 1949 * @param file the file to get the contents for 1950 * @return the contents of the file as a {@link String} or <code>null</code> 1951 */ getFileContentAsString(File file)1952 public static String getFileContentAsString(File file) { 1953 String contents = null; 1954 FileInputStream stream = null; 1955 try { 1956 stream = new FileInputStream(file); 1957 char[] array = getInputStreamAsCharArray(stream, -1, StandardCharsets.UTF_8); 1958 contents = new String(array); 1959 } catch (IOException ioe) { 1960 ApiPlugin.log(ioe); 1961 } finally { 1962 if (stream != null) { 1963 try { 1964 stream.close(); 1965 } catch (IOException e) { 1966 // ignore 1967 } 1968 } 1969 } 1970 return contents; 1971 } 1972 1973 /** 1974 * Returns the given string as an {@link InputStream}. It is up to the 1975 * caller to close the new stream. 1976 * 1977 * @param string the string to convert 1978 * @return the {@link InputStream} for the given string 1979 */ getInputStreamFromString(String string)1980 public static InputStream getInputStreamFromString(String string) { 1981 return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); 1982 } 1983 1984 /** 1985 * Serializes the given XML document into a UTF-8 string. 1986 * 1987 * @param document XML document to serialize 1988 * @return a string representing the given document 1989 * @throws CoreException if unable to serialize the document 1990 */ serializeDocument(Document document)1991 public static String serializeDocument(Document document) throws CoreException { 1992 try { 1993 ByteArrayOutputStream s = new ByteArrayOutputStream(); 1994 TransformerFactory factory = TransformerFactory.newInstance(); 1995 Transformer transformer = factory.newTransformer(); 1996 transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ 1997 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ 1998 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$ 1999 DOMSource source = new DOMSource(document); 2000 StreamResult outputTarget = new StreamResult(s); 2001 transformer.transform(source, outputTarget); 2002 return s.toString(IApiCoreConstants.UTF_8); 2003 } catch (TransformerException | IOException e) { 2004 abort("Unable to serialize XML document.", e); //$NON-NLS-1$ 2005 } 2006 return null; 2007 } 2008 2009 /** 2010 * Unzip the contents of the given zip in the given directory (create it if 2011 * it doesn't exist) 2012 * 2013 * @throws IOException, CoreException 2014 */ unzip(String zipPath, String destDirPath)2015 public static void unzip(String zipPath, String destDirPath) throws IOException, CoreException { 2016 byte[] buf = new byte[8192]; 2017 File destDir = new File(destDirPath); 2018 try (InputStream zipIn = new FileInputStream(zipPath); ZipInputStream zis = new ZipInputStream(new BufferedInputStream(zipIn));) { 2019 ZipEntry zEntry; 2020 while ((zEntry = zis.getNextEntry()) != null) { 2021 // if it is empty directory, create it 2022 if (zEntry.isDirectory()) { 2023 new File(destDir, zEntry.getName()).mkdirs(); 2024 continue; 2025 } 2026 // if it is a file, extract it 2027 String filePath = zEntry.getName(); 2028 int lastSeparator = filePath.lastIndexOf("/"); //$NON-NLS-1$ 2029 String fileDir = ""; //$NON-NLS-1$ 2030 if (lastSeparator >= 0) { 2031 fileDir = filePath.substring(0, lastSeparator); 2032 } 2033 // create directory for a file 2034 new File(destDir, fileDir).mkdirs(); 2035 // write file 2036 String destDirCanonicalPath = destDir.getCanonicalPath(); 2037 File outFile = new File(destDir, filePath); 2038 String outFileCanonicalPath = outFile.getCanonicalPath(); 2039 if (!outFileCanonicalPath.startsWith(destDirCanonicalPath + File.separator)) { 2040 throw new CoreException(new Status(IStatus.ERROR, ApiPlugin.PLUGIN_ID, MessageFormat.format("Entry is outside of the target dir: : {0}", filePath), null)); //$NON-NLS-1$ 2041 } 2042 try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outFile))) { 2043 int n = 0; 2044 while ((n = zis.read(buf)) >= 0) { 2045 outputStream.write(buf, 0, n); 2046 } 2047 } 2048 } 2049 } 2050 } 2051 2052 /** 2053 * Unzip the contents of the given zip in the given directory (create it if 2054 * it doesn't exist) 2055 */ guntar(String zipPath, String destDirPath)2056 public static void guntar(String zipPath, String destDirPath) throws TarException, IOException { 2057 TarFile tarFile = new TarFile(zipPath); 2058 Enumeration<?> entries = tarFile.entries(); 2059 byte[] buf = new byte[8192]; 2060 for (; entries.hasMoreElements();) { 2061 TarEntry zEntry; 2062 while ((zEntry = (TarEntry) entries.nextElement()) != null) { 2063 // if it is empty directory, create it 2064 if (zEntry.getFileType() == TarEntry.DIRECTORY) { 2065 new File(destDirPath, zEntry.getName()).mkdirs(); 2066 continue; 2067 } 2068 // if it is a file, extract it 2069 String filePath = zEntry.getName(); 2070 int lastSeparator = filePath.lastIndexOf("/"); //$NON-NLS-1$ 2071 String fileDir = ""; //$NON-NLS-1$ 2072 if (lastSeparator >= 0) { 2073 fileDir = filePath.substring(0, lastSeparator); 2074 } 2075 // create directory for a file 2076 new File(destDirPath, fileDir).mkdirs(); 2077 // write file 2078 File outFile = new File(destDirPath, filePath); 2079 BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outFile)); 2080 int n = 0; 2081 InputStream inputStream = tarFile.getInputStream(zEntry); 2082 BufferedInputStream stream = new BufferedInputStream(inputStream); 2083 while ((n = stream.read(buf)) >= 0) { 2084 outputStream.write(buf, 0, n); 2085 } 2086 outputStream.close(); 2087 stream.close(); 2088 } 2089 } 2090 } 2091 2092 /** 2093 * Gets the .ee file supplied to run tests based on system property. 2094 * 2095 * @return 2096 */ getEEDescriptionFile()2097 public static File getEEDescriptionFile() { 2098 // generate a fake 1.6 ee file 2099 File fakeEEFile = null; 2100 PrintWriter writer = null; 2101 try { 2102 fakeEEFile = createTempFile("eefile", ".ee"); //$NON-NLS-1$ //$NON-NLS-2$ 2103 writer = new PrintWriter(new BufferedWriter(new FileWriter(fakeEEFile))); 2104 writer.print("-Djava.home="); //$NON-NLS-1$ 2105 writer.println(System.getProperty("java.home")); //$NON-NLS-1$ 2106 writer.print("-Dee.bootclasspath="); //$NON-NLS-1$ 2107 writer.println(getJavaClassLibsAsString()); 2108 writer.println("-Dee.language.level=1.6"); //$NON-NLS-1$ 2109 writer.println("-Dee.class.library.level=JavaSE-1.6"); //$NON-NLS-1$ 2110 writer.flush(); 2111 } catch (IOException e) { 2112 // ignore 2113 } finally { 2114 if (writer != null) { 2115 writer.close(); 2116 } 2117 } 2118 return fakeEEFile; 2119 } 2120 2121 /** 2122 * Creates a new file in the users' <code>temp</code> directory 2123 * 2124 * @param prefix 2125 * @param suffix 2126 * @return a new temp file 2127 * @throws IOException 2128 * @since 1.1 2129 */ createTempFile(String prefix, String suffix)2130 public static File createTempFile(String prefix, String suffix) throws IOException { 2131 File file = File.createTempFile(prefix, suffix); 2132 file.deleteOnExit(); 2133 FileManager.getManager().recordTempFileRoot(file.getCanonicalPath()); 2134 return file; 2135 } 2136 2137 /** 2138 * @return a string representation of all of the libraries from the bootpath 2139 * of the current default system VM. 2140 */ getJavaClassLibsAsString()2141 public static String getJavaClassLibsAsString() { 2142 String[] libs = Util.getJavaClassLibs(); 2143 StringBuilder buffer = new StringBuilder(); 2144 for (int i = 0, max = libs.length; i < max; i++) { 2145 if (i > 0) { 2146 buffer.append(File.pathSeparatorChar); 2147 } 2148 buffer.append(libs[i]); 2149 } 2150 return String.valueOf(buffer); 2151 } 2152 2153 /** 2154 * @return an array of the library names from the bootpath of the current 2155 * default system VM 2156 */ getJavaClassLibs()2157 public static String[] getJavaClassLibs() { 2158 // check bootclasspath properties for Sun, JRockit and Harmony VMs 2159 String bootclasspathProperty = System.getProperty("sun.boot.class.path"); //$NON-NLS-1$ 2160 if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) { 2161 // IBM J9 VMs 2162 bootclasspathProperty = System.getProperty("vm.boot.class.path"); //$NON-NLS-1$ 2163 if ((bootclasspathProperty == null) || (bootclasspathProperty.length() == 0)) { 2164 // Harmony using IBM VME 2165 bootclasspathProperty = System.getProperty("org.apache.harmony.boot.class.path"); //$NON-NLS-1$ 2166 } 2167 } 2168 String[] jars = null; 2169 if ((bootclasspathProperty != null) && (bootclasspathProperty.length() != 0)) { 2170 StringTokenizer tokenizer = new StringTokenizer(bootclasspathProperty, File.pathSeparator); 2171 final int size = tokenizer.countTokens(); 2172 jars = new String[size]; 2173 int i = 0; 2174 while (tokenizer.hasMoreTokens()) { 2175 final String fileName = toNativePath(tokenizer.nextToken()); 2176 if (new File(fileName).exists()) { 2177 jars[i] = fileName; 2178 i++; 2179 } 2180 } 2181 if (size != i) { 2182 // resize 2183 System.arraycopy(jars, 0, (jars = new String[i]), 0, i); 2184 } 2185 } else { 2186 String jreDir = System.getProperty("java.home"); //$NON-NLS-1$ 2187 final String osName = System.getProperty("os.name"); //$NON-NLS-1$ 2188 if (jreDir == null) { 2189 return new String[] {}; 2190 } 2191 if (osName.startsWith("Mac")) { //$NON-NLS-1$ 2192 return new String[] { toNativePath(jreDir + "/../Classes/classes.jar") //$NON-NLS-1$ 2193 }; 2194 } 2195 final String vmName = System.getProperty("java.vm.name"); //$NON-NLS-1$ 2196 if ("J9".equals(vmName)) { //$NON-NLS-1$ 2197 return new String[] { toNativePath(jreDir + "/lib/jclMax/classes.zip") //$NON-NLS-1$ 2198 }; 2199 } 2200 String[] jarsNames = null; 2201 ArrayList<String> paths = new ArrayList<>(); 2202 if ("DRLVM".equals(vmName)) { //$NON-NLS-1$ 2203 FilenameFilter jarFilter = (dir, name) -> name.endsWith(DOT_JAR) & !name.endsWith("-src.jar"); //$NON-NLS-1$ 2204 jarsNames = new File(jreDir + "/lib/boot/").list(jarFilter); //$NON-NLS-1$ 2205 addJarEntries(jreDir + "/lib/boot/", jarsNames, paths); //$NON-NLS-1$ 2206 } else { 2207 jarsNames = new String[] { "/lib/vm.jar", //$NON-NLS-1$ 2208 "/lib/rt.jar", //$NON-NLS-1$ 2209 "/lib/core.jar", //$NON-NLS-1$ 2210 "/lib/security.jar", //$NON-NLS-1$ 2211 "/lib/xml.jar", //$NON-NLS-1$ 2212 "/lib/graphics.jar" //$NON-NLS-1$ 2213 }; 2214 addJarEntries(jreDir, jarsNames, paths); 2215 } 2216 jars = new String[paths.size()]; 2217 paths.toArray(jars); 2218 } 2219 return jars; 2220 } 2221 2222 /** 2223 * Makes the given path a path using native path separators as returned by 2224 * File.getPath() and trimming any extra slash. 2225 */ toNativePath(String path)2226 public static String toNativePath(String path) { 2227 String nativePath = path.replace('\\', File.separatorChar).replace('/', File.separatorChar); 2228 return nativePath.endsWith("/") || nativePath.endsWith("\\") ? //$NON-NLS-1$ //$NON-NLS-2$ 2229 nativePath.substring(0, nativePath.length() - 1) : nativePath; 2230 } 2231 addJarEntries(String jreDir, String[] jarNames, ArrayList<String> paths)2232 private static void addJarEntries(String jreDir, String[] jarNames, ArrayList<String> paths) { 2233 for (String jarName : jarNames) { 2234 final String currentName = jreDir + jarName; 2235 File f = new File(currentName); 2236 if (f.exists()) { 2237 paths.add(toNativePath(currentName)); 2238 } 2239 } 2240 } 2241 2242 /** 2243 * Delete a file or directory and insure that the file is no longer present 2244 * on file system. In case of directory, delete all the hierarchy 2245 * underneath. 2246 * 2247 * @param file The file or directory to delete 2248 * @return true iff the file was really delete, false otherwise 2249 */ delete(File file)2250 public static boolean delete(File file) { 2251 if (!file.exists()) { 2252 return true; 2253 } 2254 // flush all directory content 2255 if (file.isDirectory()) { 2256 flushDirectoryContent(file); 2257 } 2258 // remove file 2259 file.delete(); 2260 if (isFileDeleted(file)) { 2261 return true; 2262 } 2263 return waitUntilFileDeleted(file); 2264 } 2265 flushDirectoryContent(File dir)2266 public static void flushDirectoryContent(File dir) { 2267 File[] files = dir.listFiles(); 2268 if (files == null) { 2269 return; 2270 } 2271 for (File file : files) { 2272 delete(file); 2273 } 2274 } 2275 2276 /** 2277 * Wait until the file is _really_ deleted on file system. 2278 * 2279 * @param file Deleted file 2280 * @return true if the file was finally deleted, false otherwise 2281 */ waitUntilFileDeleted(File file)2282 private static boolean waitUntilFileDeleted(File file) { 2283 int count = 0; 2284 int delay = 10; // ms 2285 int maxRetry = DELETE_MAX_WAIT / delay; 2286 int time = 0; 2287 while (count < maxRetry) { 2288 try { 2289 count++; 2290 Thread.sleep(delay); 2291 time += delay; 2292 if (time > DELETE_MAX_TIME) { 2293 DELETE_MAX_TIME = time; 2294 } 2295 if (DELETE_DEBUG) { 2296 System.out.print('.'); 2297 } 2298 if (file.exists()) { 2299 if (file.delete()) { 2300 // SUCCESS 2301 return true; 2302 } 2303 } 2304 if (isFileDeleted(file)) { 2305 // SUCCESS 2306 return true; 2307 } 2308 // Increment waiting delay exponentially 2309 if (count >= 10 && delay <= 100) { 2310 count = 1; 2311 delay *= 10; 2312 maxRetry = DELETE_MAX_WAIT / delay; 2313 if ((DELETE_MAX_WAIT % delay) != 0) { 2314 maxRetry++; 2315 } 2316 } 2317 } catch (InterruptedException ie) { 2318 break; // end loop 2319 } 2320 } 2321 System.err.println(); 2322 System.err.println(" !!! ERROR: " + file + " was never deleted even after having waited " + DELETE_MAX_TIME + "ms!!!"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 2323 System.err.println(); 2324 return false; 2325 } 2326 2327 /** 2328 * Returns whether a file is really deleted or not. Does not only rely on 2329 * {@link File#exists()} method but also look if it's not in its parent 2330 * children {@link #getParentChildFile(File)}. 2331 * 2332 * @param file The file to test if deleted 2333 * @return true if the file does not exist and was not found in its parent 2334 * children. 2335 */ isFileDeleted(File file)2336 public static boolean isFileDeleted(File file) { 2337 return !file.exists() && getParentChildFile(file) == null; 2338 } 2339 2340 /** 2341 * Returns the parent's child file matching the given file or null if not 2342 * found. 2343 * 2344 * @param file The searched file in parent 2345 * @return The parent's child matching the given file or null if not found. 2346 */ getParentChildFile(File file)2347 private static File getParentChildFile(File file) { 2348 File parent = file.getParentFile(); 2349 if (parent == null || !parent.exists()) { 2350 return null; 2351 } 2352 File[] files = parent.listFiles(); 2353 if (files == null) { 2354 return null; 2355 } 2356 int length = files == null ? 0 : files.length; 2357 if (length > 0) { 2358 for (int i = 0; i < length; i++) { 2359 if (files[i] == file) { 2360 return files[i]; 2361 } else if (files[i].equals(file)) { 2362 return files[i]; 2363 } else if (files[i].getPath().equals(file.getPath())) { 2364 return files[i]; 2365 } 2366 } 2367 } 2368 return null; 2369 } 2370 2371 /** 2372 * Turns the given array of strings into a {@link HashSet} 2373 * 2374 * @param values 2375 * @return a new {@link HashSet} of the string array 2376 */ convertAsSet(String[] values)2377 public static Set<String> convertAsSet(String[] values) { 2378 Set<String> set = new HashSet<>(); 2379 if (values != null && values.length != 0) { 2380 Collections.addAll(set, values); 2381 } 2382 return set; 2383 } 2384 2385 /** 2386 * Returns an identifier for the given API component including its version 2387 * identifier (component id + '(' + major + . + minor + . + micro + ')' ) 2388 * 2389 * @param component API component 2390 * @return API component + version identifier 2391 */ getDeltaComponentVersionsId(IApiComponent component)2392 public static String getDeltaComponentVersionsId(IApiComponent component) { 2393 StringBuilder buffer = new StringBuilder(component.getSymbolicName()); 2394 String version = component.getVersion(); 2395 // remove the qualifier part 2396 if (version != null) { 2397 buffer.append(Util.VERSION_SEPARATOR); 2398 try { 2399 Version version2 = new Version(version); 2400 buffer.append(version2.getMajor()).append('.').append(version2.getMinor()).append('.').append(version2.getMicro()); 2401 } catch (IllegalArgumentException e) { 2402 // the version string doesn't follow the Eclipse pattern 2403 // we keep the version as is 2404 buffer.append(version); 2405 } 2406 buffer.append(')'); 2407 } 2408 return String.valueOf(buffer); 2409 } 2410 2411 /** 2412 * Returns an identifier for the given API component including its version 2413 * identifier (component id + _ + major + _ + minor + _ + micro) 2414 * 2415 * @param component API component 2416 * @return API component + version identifier 2417 */ getComponentVersionsId(IApiComponent component)2418 public static String getComponentVersionsId(IApiComponent component) { 2419 StringBuilder buffer = new StringBuilder(component.getSymbolicName()); 2420 String version = component.getVersion(); 2421 // remove the qualifier part 2422 if (version != null) { 2423 buffer.append('_'); 2424 try { 2425 Version version2 = new Version(version); 2426 buffer.append(version2.getMajor()).append('.').append(version2.getMinor()).append('.').append(version2.getMicro()); 2427 } catch (IllegalArgumentException e) { 2428 // the version string doesn't follow the Eclipse pattern 2429 // we keep the version as is 2430 buffer.append(version); 2431 } 2432 } 2433 return String.valueOf(buffer); 2434 } 2435 getDescriptorName(IApiType descriptor)2436 public static String getDescriptorName(IApiType descriptor) { 2437 String typeName = descriptor.getName(); 2438 int index = typeName.lastIndexOf('$'); 2439 if (index != -1) { 2440 return typeName.replace('$', '.'); 2441 } 2442 return typeName; 2443 } 2444 getDeltaArgumentString(IDelta delta)2445 public static String getDeltaArgumentString(IDelta delta) { 2446 String[] arguments = delta.getArguments(); 2447 switch (delta.getFlags()) { 2448 case IDelta.TYPE_MEMBER: 2449 case IDelta.TYPE: 2450 return arguments[0]; 2451 case IDelta.METHOD: 2452 case IDelta.DEFAULT_METHOD: 2453 case IDelta.SUPER_INTERFACE_DEFAULT_METHOD: 2454 case IDelta.CONSTRUCTOR: 2455 case IDelta.ENUM_CONSTANT: 2456 case IDelta.METHOD_WITH_DEFAULT_VALUE: 2457 case IDelta.METHOD_WITHOUT_DEFAULT_VALUE: 2458 case IDelta.FIELD: 2459 return arguments[1]; 2460 case IDelta.INCREASE_ACCESS: 2461 switch (delta.getElementType()) { 2462 case IDelta.FIELD_ELEMENT_TYPE: 2463 case IDelta.METHOD_ELEMENT_TYPE: 2464 case IDelta.CONSTRUCTOR_ELEMENT_TYPE: 2465 return arguments[1]; 2466 default: 2467 return arguments[0]; 2468 } 2469 default: 2470 break; 2471 } 2472 return EMPTY_STRING; 2473 } 2474 2475 /** 2476 * Returns the string representation of the {@link IApiElement} type 2477 * 2478 * @param type 2479 * @return the string of the {@link IApiElement} type 2480 */ getApiElementType(int type)2481 public static String getApiElementType(int type) { 2482 switch (type) { 2483 case IApiElement.API_TYPE_CONTAINER: 2484 return "API_TYPE_CONTAINER"; //$NON-NLS-1$ 2485 case IApiElement.API_TYPE_ROOT: 2486 return "API_TYPE_ROOT"; //$NON-NLS-1$ 2487 case IApiElement.BASELINE: 2488 return "BASELINE"; //$NON-NLS-1$ 2489 case IApiElement.COMPONENT: 2490 return "COMPONENT"; //$NON-NLS-1$ 2491 case IApiElement.FIELD: 2492 return "FIELD"; //$NON-NLS-1$ 2493 case IApiElement.METHOD: 2494 return "METHOD"; //$NON-NLS-1$ 2495 case IApiElement.TYPE: 2496 return "TYPE"; //$NON-NLS-1$ 2497 default: 2498 return "UNKNOWN"; //$NON-NLS-1$ 2499 } 2500 } 2501 isConstructor(String referenceMemberName)2502 public static boolean isConstructor(String referenceMemberName) { 2503 return Arrays.equals(ConstantPool.Init, referenceMemberName.toCharArray()); 2504 } 2505 isManifest(IPath path)2506 public static boolean isManifest(IPath path) { 2507 return MANIFEST_PROJECT_RELATIVE_PATH.equals(path); 2508 } 2509 touchCorrespondingResource(IProject project, IResource resource, String typeName)2510 public static void touchCorrespondingResource(IProject project, IResource resource, String typeName) { 2511 if (typeName != null && typeName != FilterStore.GLOBAL) { 2512 if (Util.isManifest(resource.getProjectRelativePath())) { 2513 try { 2514 IJavaProject javaProject = JavaCore.create(project); 2515 IType findType = javaProject.findType(typeName); 2516 IType typeInProject = Util.getTypeInSameJavaProject(findType, typeName, javaProject); 2517 if (typeInProject != null) { 2518 findType = typeInProject; 2519 } 2520 if (findType != null) { 2521 ICompilationUnit compilationUnit = findType.getCompilationUnit(); 2522 if (compilationUnit != null) { 2523 IResource cuResource = compilationUnit.getResource(); 2524 if (cuResource != null) { 2525 cuResource.touch(null); 2526 } 2527 } 2528 } 2529 } catch (CoreException e) { 2530 ApiPlugin.log(e); 2531 } 2532 } else { 2533 try { 2534 resource.touch(null); 2535 } catch (CoreException e) { 2536 ApiPlugin.log(e); 2537 } 2538 } 2539 } 2540 } 2541 getTypeNameFromMarker(IMarker marker)2542 public static String getTypeNameFromMarker(IMarker marker) { 2543 return marker.getAttribute(IApiMarkerConstants.MARKER_ATTR_PROBLEM_TYPE_NAME, null); 2544 } 2545 getReexportedComponents(IApiComponent component)2546 public static IApiComponent[] getReexportedComponents(IApiComponent component) { 2547 try { 2548 IRequiredComponentDescription[] requiredComponents = component.getRequiredComponents(); 2549 int length = requiredComponents.length; 2550 if (length != 0) { 2551 List<IApiComponent> reexportedComponents = null; 2552 IApiBaseline baseline = component.getBaseline(); 2553 for (int i = 0; i < length; i++) { 2554 IRequiredComponentDescription description = requiredComponents[i]; 2555 if (description.isExported()) { 2556 String id = description.getId(); 2557 IApiComponent reexportedComponent = baseline.getApiComponent(id); 2558 if (reexportedComponent != null) { 2559 if (reexportedComponents == null) { 2560 reexportedComponents = new ArrayList<>(); 2561 } 2562 reexportedComponents.add(reexportedComponent); 2563 } 2564 } 2565 } 2566 if (reexportedComponents == null || reexportedComponents.isEmpty()) { 2567 return null; 2568 } 2569 return reexportedComponents.toArray(new IApiComponent[reexportedComponents.size()]); 2570 } 2571 } catch (CoreException e) { 2572 ApiPlugin.log(e); 2573 } 2574 return null; 2575 } 2576 2577 /** 2578 * Returns the {@link IResource} to create markers on when building. If the 2579 * {@link IType} is <code>null</code> or the type cannot be located (does 2580 * not exist) than the MANIFEST.MF will be returned. <code>null</code> can 2581 * be returned in the case that the project does not have a manifest file. 2582 * 2583 * @param project the project to look in for the {@link IResource} 2584 * @param type the type we are looking for the resource for, or 2585 * <code>null</code> 2586 * @return the {@link IResource} associated with the given {@link IType} or 2587 * the MANIFEST.MF file, or <code>null</code> if the project does 2588 * not have a manifest 2589 */ getResource(IProject project, IType type)2590 public static IResource getResource(IProject project, IType type) { 2591 try { 2592 if (type != null) { 2593 ICompilationUnit unit = type.getCompilationUnit(); 2594 if (unit != null) { 2595 IResource resource = unit.getCorrespondingResource(); 2596 if (resource != null && resource.exists()) { 2597 return resource; 2598 } 2599 } 2600 } 2601 } catch (JavaModelException e) { 2602 ApiPlugin.log(e); 2603 } 2604 return getManifestFile(project); 2605 } 2606 2607 /** 2608 * Default comparator that orders {@link IApiComponent} by their ID 2609 */ 2610 public static final Comparator<Object> componentsorter = (o1, o2) -> { 2611 if (o1 instanceof IApiComponent && o2 instanceof IApiComponent) { 2612 return ((IApiComponent) o1).getSymbolicName().compareTo(((IApiComponent) o2).getSymbolicName()); 2613 } 2614 if (o1 instanceof SkippedComponent && o2 instanceof SkippedComponent) { 2615 return ((SkippedComponent) o1).getComponentId().compareTo(((SkippedComponent) o2).getComponentId()); 2616 } 2617 if (o1 instanceof String && o2 instanceof String) { 2618 return ((String) o1).compareTo((String) o2); 2619 } 2620 return -1; 2621 }; 2622 2623 /** 2624 * Initializes the exclude set with regex support. The API baseline is used 2625 * to determine which bundles should be added to the list when processing 2626 * regex expressions. 2627 * 2628 * @param location 2629 * @param baseline 2630 * @return the list of bundles to be excluded 2631 * @throws CoreException if the location does not describe a includes file 2632 * or an IOException occurs 2633 */ initializeRegexFilterList(String location, IApiBaseline baseline, boolean debug)2634 public static FilteredElements initializeRegexFilterList(String location, IApiBaseline baseline, boolean debug) throws CoreException { 2635 FilteredElements excludedElements = new FilteredElements(); 2636 if (location != null) { 2637 File file = new File(location); 2638 char[] contents = null; 2639 try (InputStream stream = new BufferedInputStream(new FileInputStream(file));) { 2640 contents = getInputStreamAsCharArray(stream, -1, StandardCharsets.ISO_8859_1); 2641 } catch (FileNotFoundException e) { 2642 abort(NLS.bind(UtilMessages.Util_couldNotFindFilterFile, location), e); 2643 } catch (IOException e) { 2644 abort(NLS.bind(UtilMessages.Util_problemWithFilterFile, location), e); 2645 } 2646 if (contents != null) { 2647 try(LineNumberReader reader = new LineNumberReader(new StringReader(new String(contents)));){ 2648 String line = null; 2649 while ((line = reader.readLine()) != null) { 2650 line = line.trim(); 2651 if (line.startsWith("#") || line.length() == 0) { //$NON-NLS-1$ 2652 continue; 2653 } 2654 if (line.startsWith(REGULAR_EXPRESSION_START)) { 2655 if (baseline != null) { 2656 Util.collectRegexIds(line, excludedElements, baseline.getApiComponents(), debug); 2657 } 2658 } else { 2659 excludedElements.addExactMatch(line); 2660 } 2661 } 2662 } catch (IOException e) { 2663 abort(NLS.bind(UtilMessages.Util_problemWithFilterFile, location), e); 2664 } 2665 } 2666 } 2667 return excludedElements; 2668 } 2669 2670 /** 2671 * Collects the set of component ids that match a given regex in the exclude 2672 * file 2673 * 2674 * @param line 2675 * @param list 2676 * @param components 2677 */ collectRegexIds(String line, FilteredElements excludedElements, IApiComponent[] components, boolean debug)2678 public static void collectRegexIds(String line, FilteredElements excludedElements, IApiComponent[] components, boolean debug) throws CoreException { 2679 if (line.startsWith(REGULAR_EXPRESSION_START)) { 2680 String componentname = line; 2681 // regular expression 2682 componentname = componentname.substring(2); 2683 Pattern pattern = null; 2684 try { 2685 if (debug) { 2686 System.out.println("Pattern to match : " + componentname); //$NON-NLS-1$ 2687 } 2688 pattern = Pattern.compile(componentname); 2689 String componentid = null; 2690 for (IApiComponent component : components) { 2691 componentid = component.getSymbolicName(); 2692 if (pattern.matcher(componentid).matches()) { 2693 if (debug) { 2694 System.out.println(componentid + " matched the pattern " + componentname); //$NON-NLS-1$ 2695 } 2696 excludedElements.addPartialMatch(componentid); 2697 } else if (debug) { 2698 System.out.println(componentid + " didn't match the pattern " + componentname); //$NON-NLS-1$ 2699 } 2700 } 2701 } catch (PatternSyntaxException e) { 2702 abort(NLS.bind(UtilMessages.comparison_invalidRegularExpression, componentname), e); 2703 } 2704 } 2705 } 2706 2707 /** 2708 * Default comparator that orders {@link File}s by their name 2709 */ 2710 public static final Comparator<Object> filesorter = (o1, o2) -> { 2711 if (o1 instanceof File && o2 instanceof File) { 2712 return ((File) o1).getName().compareTo(((File) o2).getName()); 2713 } 2714 return 0; 2715 }; 2716 2717 /** 2718 * Returns true if the given {@link IApiType} is API or not, where API is 2719 * defined as having API visibility in an API description and having either 2720 * the public of protected Java flag set 2721 * 2722 * @param visibility 2723 * @param typeDescriptor 2724 * @return true if the given type is API, false otherwise 2725 */ isAPI(int visibility, IApiType typeDescriptor)2726 public static boolean isAPI(int visibility, IApiType typeDescriptor) { 2727 int access = typeDescriptor.getModifiers(); 2728 return VisibilityModifiers.isAPI(visibility) && (Flags.isPublic(access) || Flags.isProtected(access)); 2729 } 2730 2731 /** 2732 * Simple method to walk an array and call <code>toString()</code> on each 2733 * of the entries. Does not descend into sub-collections. 2734 * 2735 * @param array the array 2736 * @return the comma-separated string representation of the the array 2737 * @since 1.0.3 2738 */ deepToString(Object[] array)2739 public static String deepToString(Object[] array) { 2740 StringBuilder buffer = new StringBuilder(); 2741 for (int i = 0; i < array.length; i++) { 2742 buffer.append(array[i].toString()); 2743 if (i < array.length - 1) { 2744 buffer.append(','); 2745 } 2746 } 2747 return buffer.toString(); 2748 } 2749 getSinceVersionTagPrefererenceKey(int id)2750 public static String getSinceVersionTagPrefererenceKey(int id) { 2751 int problemCategory = ApiProblemFactory.getProblemCategory(id); 2752 int problemKind = ApiProblemFactory.getProblemKind(id); 2753 switch (problemCategory) { 2754 case IApiProblem.CATEGORY_SINCETAGS: { 2755 switch (problemKind) { 2756 case IApiProblem.SINCE_TAG_INVALID: 2757 return IApiProblemTypes.INVALID_SINCE_TAG_VERSION; 2758 case IApiProblem.SINCE_TAG_MALFORMED: 2759 return IApiProblemTypes.MALFORMED_SINCE_TAG; 2760 case IApiProblem.SINCE_TAG_MISSING: 2761 return IApiProblemTypes.MISSING_SINCE_TAG; 2762 default: 2763 break; 2764 } 2765 } 2766 break; 2767 case IApiProblem.CATEGORY_VERSION: { 2768 switch (problemKind) { 2769 case IApiProblem.MAJOR_VERSION_CHANGE_NO_BREAKAGE: 2770 return IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_REPORT_MAJOR_WITHOUT_BREAKING_CHANGE; 2771 2772 case IApiProblem.MINOR_VERSION_CHANGE_NO_NEW_API: 2773 case IApiProblem.MICRO_VERSION_CHANGE_UNNECESSARILY: 2774 case IApiProblem.MINOR_VERSION_CHANGE_UNNECESSARILY: 2775 return IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION_REPORT_MINOR_WITHOUT_API_CHANGE; 2776 2777 case IApiProblem.MINOR_VERSION_CHANGE_EXECUTION_ENV_CHANGED: 2778 return IApiProblemTypes.CHANGED_EXECUTION_ENV; 2779 2780 default: 2781 return IApiProblemTypes.INCOMPATIBLE_API_COMPONENT_VERSION; 2782 2783 } 2784 } 2785 default: 2786 break; 2787 2788 2789 } 2790 2791 return null; 2792 } 2793 getUsagePrefererenceKey(int id)2794 public static String getUsagePrefererenceKey(int id) { 2795 int problemKind = ApiProblemFactory.getProblemKind(id); 2796 int problemFlag = ApiProblemFactory.getProblemFlags(id); 2797 2798 switch (problemKind) { 2799 case IApiProblem.ILLEGAL_IMPLEMENT: 2800 return IApiProblemTypes.ILLEGAL_IMPLEMENT; 2801 2802 case IApiProblem.ILLEGAL_EXTEND: 2803 return IApiProblemTypes.ILLEGAL_EXTEND; 2804 case IApiProblem.ILLEGAL_INSTANTIATE: 2805 return IApiProblemTypes.ILLEGAL_INSTANTIATE; 2806 case IApiProblem.ILLEGAL_OVERRIDE: 2807 return IApiProblemTypes.ILLEGAL_OVERRIDE; 2808 case IApiProblem.ILLEGAL_REFERENCE: 2809 return IApiProblemTypes.ILLEGAL_REFERENCE; 2810 2811 case IApiProblem.API_LEAK: { 2812 switch (problemFlag) { 2813 case IApiProblem.LEAK_BY_EXTENDING_NO_EXTEND_CLASS_TYPE: 2814 case IApiProblem.LEAK_BY_EXTENDING_NO_EXTEND_INTERFACE_TYPE: 2815 case IApiProblem.LEAK_EXTENDS: 2816 return IApiProblemTypes.LEAK_EXTEND; 2817 case IApiProblem.LEAK_IMPLEMENTS: 2818 return IApiProblemTypes.LEAK_IMPLEMENT; 2819 case IApiProblem.LEAK_FIELD: 2820 return IApiProblemTypes.LEAK_FIELD_DECL; 2821 case IApiProblem.LEAK_RETURN_TYPE: 2822 return IApiProblemTypes.LEAK_METHOD_RETURN_TYPE; 2823 case IApiProblem.LEAK_CONSTRUCTOR_PARAMETER: 2824 case IApiProblem.LEAK_METHOD_PARAMETER: 2825 return IApiProblemTypes.LEAK_METHOD_PARAM; 2826 2827 default: 2828 break; 2829 } 2830 break; 2831 } 2832 case IApiProblem.UNSUPPORTED_TAG_USE: 2833 return IApiProblemTypes.INVALID_JAVADOC_TAG; 2834 2835 case IApiProblem.UNSUPPORTED_ANNOTATION_USE: 2836 return IApiProblemTypes.INVALID_ANNOTATION; 2837 2838 case IApiProblem.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES: 2839 return IApiProblemTypes.INVALID_REFERENCE_IN_SYSTEM_LIBRARIES; 2840 2841 case IApiProblem.MISSING_EE_DESCRIPTIONS: 2842 return IApiProblemTypes.MISSING_EE_DESCRIPTIONS; 2843 2844 // this is usage?? 2845 case IApiProblem.UNUSED_PROBLEM_FILTERS: 2846 return IApiProblemTypes.UNUSED_PROBLEM_FILTERS; 2847 2848 2849 default: 2850 break; 2851 } 2852 return null; 2853 2854 } 2855 getComponentResolutionKey(int id)2856 public static String getComponentResolutionKey(int id) { 2857 int problemKind = ApiProblemFactory.getProblemKind(id); 2858 2859 2860 switch (problemKind) { 2861 case IApiProblem.API_COMPONENT_RESOLUTION: 2862 return IApiProblemTypes.REPORT_RESOLUTION_ERRORS_API_COMPONENT; 2863 case IApiProblem.UNUSED_PROBLEM_FILTERS: 2864 return IApiProblemTypes.UNUSED_PROBLEM_FILTERS; 2865 default: 2866 break; 2867 } 2868 return null; 2869 } 2870 getAPIUseScanKey(int id)2871 public static String getAPIUseScanKey(int id) { 2872 2873 int problemKind = ApiProblemFactory.getProblemKind(id); 2874 2875 switch (problemKind) { 2876 case IApiProblem.API_USE_SCAN_TYPE_PROBLEM: 2877 return IApiProblemTypes.API_USE_SCAN_TYPE_SEVERITY; 2878 case IApiProblem.API_USE_SCAN_METHOD_PROBLEM: 2879 return IApiProblemTypes.API_USE_SCAN_METHOD_SEVERITY; 2880 case IApiProblem.API_USE_SCAN_FIELD_PROBLEM: 2881 return IApiProblemTypes.API_USE_SCAN_FIELD_SEVERITY; 2882 default: 2883 break; 2884 } 2885 return null; 2886 2887 } 2888 getAPIToolPreferenceKey(int id)2889 public static String getAPIToolPreferenceKey(int id) { 2890 String key = null; 2891 int category = ApiProblemFactory.getProblemCategory(id); 2892 2893 if (category == IApiProblem.CATEGORY_USAGE) { 2894 key = Util.getUsagePrefererenceKey(id); 2895 2896 } 2897 if (category == IApiProblem.CATEGORY_COMPATIBILITY) { 2898 2899 key = Util.getDeltaPrefererenceKey(ApiProblemFactory.getProblemElementKind(id), ApiProblemFactory.getProblemKind(id), ApiProblemFactory.getProblemFlags(id)); 2900 } 2901 if (category == IApiProblem.CATEGORY_SINCETAGS || category == IApiProblem.CATEGORY_VERSION) { 2902 key = Util.getSinceVersionTagPrefererenceKey(id); 2903 2904 } 2905 if (category == IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION) { 2906 key = Util.getComponentResolutionKey(id); 2907 2908 } 2909 if (category == IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM) { 2910 key = Util.getAPIUseScanKey(id); 2911 2912 } 2913 return key; 2914 } 2915 getAPIToolPreferenceTab(int id)2916 public static int getAPIToolPreferenceTab(int id) { 2917 int category = ApiProblemFactory.getProblemCategory(id); 2918 int tab = -1; 2919 if (category == IApiProblem.CATEGORY_USAGE) { 2920 tab = 0; 2921 int problemKind = ApiProblemFactory.getProblemKind(id); 2922 switch (problemKind) { 2923 case IApiProblem.UNUSED_PROBLEM_FILTERS: 2924 return 3; 2925 default: 2926 break; 2927 } 2928 } 2929 if (category == IApiProblem.CATEGORY_COMPATIBILITY) { 2930 tab = 1; 2931 } 2932 if (category == IApiProblem.CATEGORY_SINCETAGS || category == IApiProblem.CATEGORY_VERSION) { 2933 tab = 2; 2934 } 2935 if (category == IApiProblem.CATEGORY_API_COMPONENT_RESOLUTION) { 2936 tab = 3; 2937 } 2938 if (category == IApiProblem.CATEGORY_API_USE_SCAN_PROBLEM) { 2939 tab = 4; 2940 } 2941 return tab; 2942 } 2943 2944 /** 2945 * Tries to find SourceType of name typeName in the project. It returns null 2946 * if it cannot find SourceType of name typeName 2947 * 2948 * @param javaProject 2949 * @param typeName 2950 * @return 2951 */ 2952 findSourceTypeinJavaProject(IJavaProject javaProject, String typeName)2953 public static IType findSourceTypeinJavaProject(IJavaProject javaProject, String typeName) { 2954 IType type = null; 2955 try { 2956 String pkgName = typeName.substring(0, typeName.lastIndexOf('.')); 2957 if (javaProject instanceof JavaProject) { 2958 JavaProject jp = (JavaProject) javaProject; 2959 NameLookup newNameLookup = null; 2960 try { 2961 newNameLookup = jp.newNameLookup(DefaultWorkingCopyOwner.PRIMARY); 2962 2963 } catch (JavaModelException e) { 2964 ApiPlugin.log(e); 2965 2966 } 2967 IPackageFragment[] findPackageFragment = newNameLookup.findPackageFragments(pkgName, false); 2968 for (IPackageFragment iJavaElement : findPackageFragment) { 2969 type = newNameLookup.findType(typeName.substring(typeName.lastIndexOf('.') + 1, typeName.length()), iJavaElement, false, NameLookup.ACCEPT_ALL); 2970 if (type instanceof SourceType) { 2971 break; 2972 2973 } 2974 } 2975 } 2976 } catch (Exception e) { 2977 // return null 2978 } 2979 return type; 2980 } 2981 2982 2983 } 2984