1 /******************************************************************************* 2 * Copyright (c) 2011, 2018 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.generator; 15 16 import java.io.BufferedInputStream; 17 import java.io.BufferedReader; 18 import java.io.BufferedWriter; 19 import java.io.ByteArrayOutputStream; 20 import java.io.DataOutputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileNotFoundException; 24 import java.io.FileReader; 25 import java.io.FileWriter; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.LineNumberReader; 29 import java.io.PrintWriter; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.Collections; 33 import java.util.Enumeration; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.Iterator; 37 import java.util.LinkedHashMap; 38 import java.util.LinkedHashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Map.Entry; 42 import java.util.Properties; 43 import java.util.Set; 44 import java.util.SortedSet; 45 import java.util.StringTokenizer; 46 import java.util.TreeSet; 47 import java.util.zip.ZipEntry; 48 import java.util.zip.ZipException; 49 import java.util.zip.ZipFile; 50 import java.util.zip.ZipOutputStream; 51 52 import org.eclipse.core.runtime.CoreException; 53 import org.eclipse.jdt.core.Flags; 54 import org.eclipse.jdt.core.Signature; 55 import org.eclipse.jdt.core.ToolFactory; 56 import org.eclipse.jdt.core.compiler.CharOperation; 57 import org.eclipse.jdt.core.util.IAttributeNamesConstants; 58 import org.eclipse.jdt.core.util.IClassFileAttribute; 59 import org.eclipse.jdt.core.util.IClassFileReader; 60 import org.eclipse.jdt.core.util.IFieldInfo; 61 import org.eclipse.jdt.core.util.IInnerClassesAttribute; 62 import org.eclipse.jdt.core.util.IInnerClassesAttributeEntry; 63 import org.eclipse.jdt.core.util.IMethodInfo; 64 import org.eclipse.jdt.core.util.ISignatureAttribute; 65 import org.eclipse.pde.api.tools.generator.util.Util; 66 import org.eclipse.pde.api.tools.internal.IApiXmlConstants; 67 import org.eclipse.pde.api.tools.internal.provisional.ProfileModifiers; 68 import org.objectweb.asm.AnnotationVisitor; 69 import org.objectweb.asm.Attribute; 70 import org.objectweb.asm.ClassReader; 71 import org.objectweb.asm.ClassVisitor; 72 import org.objectweb.asm.ClassWriter; 73 import org.objectweb.asm.FieldVisitor; 74 import org.objectweb.asm.MethodVisitor; 75 import org.objectweb.asm.Opcodes; 76 import org.w3c.dom.DOMException; 77 import org.w3c.dom.Document; 78 import org.w3c.dom.Element; 79 80 /** 81 * This is a java application to generate the EE descriptions for specified EE 82 * names: 83 * <p> 84 * Accepted names are: 85 * </p> 86 * <ol> 87 * <li>JRE-1.1,</li> 88 * <li>J2SE-1.2,</li> 89 * <li>J2SE-1.3,</li> 90 * <li>J2SE-1.4,</li> 91 * <li>J2SE-1.5,</li> 92 * <li>JavaSE-1.6,</li> 93 * <li>JavaSE-1.7,</li> 94 * <li>JavaSE-1.8,</li> 95 * <li>CDC-1.0_Foundation-1.0,</li> 96 * <li>CDC-1.1_Foundation-1.1,</li> 97 * <li>OSGi_Minimum-1.0</li>, 98 * <li>OSGi_Minimum-1.1,</li> 99 * <li>OSGi_Minimum-1.2.</li> 100 * </ol> 101 * This can be called using: -output c:/EE_descriptions -config 102 * C:\OSGi_profiles\configuration.properties -EEs 103 * JRE-1.1,J2SE-1.2,J2SE-1.3,J2SE- 104 * 1.4,J2SE-1.5,JavaSE-1.6,JavaSE-1.7,JavaSE-1.8,JavaSE-9,CDC- 105 * 1.0_Foundation-1.0,CDC-1.1_Foundation-1.1,OSGi_Minimum-1.0,OSGi_Minimum-1.1,OSGi_Minimum-1. 106 * 2 107 */ 108 public class EEGenerator { 109 static class AbstractNode { 110 protected int addedProfileValue = -1; 111 protected int removedProfileValue = -1; 112 persistAnnotations(Element element)113 public void persistAnnotations(Element element) { 114 if (this.addedProfileValue != -1) { 115 element.setAttribute(IApiXmlConstants.ATTR_ADDED_PROFILE, Integer.toString(this.addedProfileValue)); 116 } 117 if (this.removedProfileValue != -1) { 118 element.setAttribute(IApiXmlConstants.ATTR_REMOVED_PROFILE, Integer.toString(this.removedProfileValue)); 119 } 120 } 121 persistAnnotations(Element element, String OSGiProfileName)122 public void persistAnnotations(Element element, String OSGiProfileName) { 123 int value = ProfileModifiers.getValue(OSGiProfileName); 124 if (value != -1) { 125 element.setAttribute(IApiXmlConstants.ATTR_PROFILE, Integer.toString(value)); 126 } 127 } 128 setAddedProfileValue(int value)129 public void setAddedProfileValue(int value) { 130 if (this.addedProfileValue != -1) { 131 System.err.println("Remove profile value is already set"); //$NON-NLS-1$ 132 } 133 this.addedProfileValue = value; 134 } setRemovedProfileValue(int value)135 public void setRemovedProfileValue(int value) { 136 if (this.removedProfileValue != -1) { 137 System.err.println("Remove profile value is already set"); //$NON-NLS-1$ 138 } 139 this.removedProfileValue = value; 140 } 141 } 142 static class Field extends AbstractNode implements Comparable<Field> { 143 char[] name; 144 char[] type; 145 Field(char[] fname, char[] ftype)146 Field(char[] fname, char[] ftype) { 147 this.name = fname; 148 if (ftype != null) { 149 this.type = CharOperation.replaceOnCopy(ftype, '/', '.'); 150 } 151 } 152 @Override compareTo(Field field)153 public int compareTo(Field field) { 154 return CharOperation.compareTo(this.name, field.name); 155 } 156 @Override equals(Object obj)157 public boolean equals(Object obj) { 158 if (this == obj) { 159 return true; 160 } 161 if (obj == null) { 162 return false; 163 } 164 if (!(obj instanceof Field)) { 165 return false; 166 } 167 Field other = (Field) obj; 168 return Arrays.equals(name, other.name); 169 } 170 getStatus()171 public int getStatus() { 172 return -1; 173 } 174 175 @Override hashCode()176 public int hashCode() { 177 final int prime = 31; 178 int result = 1; 179 result = prime * result + Arrays.hashCode(name); 180 result = prime * result + Arrays.hashCode(type); 181 return result; 182 } 183 persistXML(Document document, Element parent)184 public void persistXML(Document document, Element parent) { 185 Element field = document.createElement(IApiXmlConstants.ELEMENT_FIELD); 186 parent.appendChild(field); 187 field.setAttribute(IApiXmlConstants.ATTR_NAME, new String(this.name)); 188 field.setAttribute(IApiXmlConstants.ATTR_STATUS, Integer.toString(getStatus())); 189 } 190 persistXML(Document document, Element parent, String OSGiProfileName)191 public void persistXML(Document document, Element parent, String OSGiProfileName) { 192 Element field = document.createElement(IApiXmlConstants.ELEMENT_FIELD); 193 parent.appendChild(field); 194 field.setAttribute(IApiXmlConstants.ATTR_NAME, new String(this.name)); 195 persistAnnotations(field, OSGiProfileName); 196 } 197 198 @Override toString()199 public String toString() { 200 StringBuilder builder = new StringBuilder(); 201 builder.append("Field : ") //$NON-NLS-1$ 202 .append(this.name).append(' ').append(Signature.toCharArray(this.type)); 203 return String.valueOf(builder); 204 } 205 } 206 static class Method extends AbstractNode implements Comparable<Method> { 207 public static final char[] NO_GENERIC_SIGNATURE = new char[0]; 208 char[] genericSignature; 209 int modifiers; 210 char[] selector; 211 char[] signature; 212 Method(int mods, char[] select, char[] sig, char[] genericsig)213 Method(int mods, char[] select, char[] sig, char[] genericsig) { 214 this.selector = select; 215 this.signature = sig; 216 this.modifiers = mods; 217 if (genericsig == null) { 218 this.genericSignature = NO_GENERIC_SIGNATURE; 219 } else { 220 this.genericSignature = genericsig; 221 } 222 } 223 @Override compareTo(Method method)224 public int compareTo(Method method) { 225 int compare = CharOperation.compareTo(this.selector, method.selector); 226 if (compare == 0) { 227 int compareTo = CharOperation.compareTo(this.signature, method.signature); 228 if (compareTo == 0) { 229 return this.getStatus() - method.getStatus(); 230 } 231 return compareTo; 232 } 233 return compare; 234 } 235 236 @Override equals(Object obj)237 public boolean equals(Object obj) { 238 if (this == obj) { 239 return true; 240 } 241 if (obj == null) { 242 return false; 243 } 244 if (!(obj instanceof Method)) { 245 return false; 246 } 247 Method other = (Method) obj; 248 if (!Arrays.equals(selector, other.selector)) { 249 return false; 250 } 251 return Arrays.equals(signature, other.signature); 252 } 253 getStatus()254 public int getStatus() { 255 return -1; 256 } 257 258 @Override hashCode()259 public int hashCode() { 260 final int prime = 31; 261 int result = 1; 262 result = prime * result + Arrays.hashCode(selector); 263 result = prime * result + Arrays.hashCode(signature); 264 return result; 265 } 266 persistXML(Document document, Element parent)267 public void persistXML(Document document, Element parent) { 268 Element method = document.createElement(IApiXmlConstants.ELEMENT_METHOD); 269 parent.appendChild(method); 270 method.setAttribute(IApiXmlConstants.ATTR_NAME, new String(this.selector)); 271 method.setAttribute(IApiXmlConstants.ATTR_SIGNATURE, new String(this.signature)); 272 method.setAttribute(IApiXmlConstants.ATTR_STATUS, Integer.toString(getStatus())); 273 } 274 persistXML(Document document, Element parent, String OSGiProfileName)275 public void persistXML(Document document, Element parent, String OSGiProfileName) { 276 Element method = document.createElement(IApiXmlConstants.ELEMENT_METHOD); 277 parent.appendChild(method); 278 method.setAttribute(IApiXmlConstants.ATTR_NAME, new String(this.selector)); 279 method.setAttribute(IApiXmlConstants.ATTR_SIGNATURE, new String(this.signature)); 280 persistAnnotations(method, OSGiProfileName); 281 } 282 283 @Override toString()284 public String toString() { 285 StringBuilder builder = new StringBuilder(); 286 builder.append("Method : "); //$NON-NLS-1$ 287 if (this.genericSignature != null && this.genericSignature.length != 0) { 288 builder.append(Signature.toCharArray(this.genericSignature, this.selector, null, true, true)); 289 } else { 290 builder.append(Signature.toCharArray(this.signature, this.selector, null, true, true)); 291 } 292 return String.valueOf(builder); 293 } 294 } 295 296 static class Package extends AbstractNode implements Comparable<Package> { 297 String name; 298 List<Type> types; 299 Package(String pname)300 public Package(String pname) { 301 this.name = pname; 302 } addType(Type type)303 public void addType(Type type) { 304 if (this.types == null) { 305 this.types = new ArrayList<>(); 306 } 307 this.types.add(type); 308 } collectTypes(Map<String, Type> result)309 public void collectTypes(Map<String, Type> result) { 310 Collections.sort(this.types); 311 for (Type type : this.types) { 312 String typeName = new String(type.name); 313 result.put(typeName, type); 314 } 315 } 316 @Override compareTo(Package package1)317 public int compareTo(Package package1) { 318 return this.name.compareTo(package1.name); 319 } 320 @Override equals(Object obj)321 public boolean equals(Object obj) { 322 if (this == obj) { 323 return true; 324 } 325 if (obj == null) { 326 return false; 327 } 328 if (!(obj instanceof Package)) { 329 return false; 330 } 331 Package other = (Package) obj; 332 if (name == null) { 333 if (other.name != null) { 334 return false; 335 } 336 } else if (!name.equals(other.name)) { 337 return false; 338 } 339 return true; 340 } 341 getType(Type typeToFind)342 public Type getType(Type typeToFind) { 343 if (this.types == null) { 344 return null; 345 } 346 int index = this.types.indexOf(typeToFind); 347 if (index != -1) { 348 return this.types.get(index); 349 } 350 return null; 351 } 352 @Override hashCode()353 public int hashCode() { 354 final int prime = 31; 355 int result = 1; 356 result = prime * result + ((name == null) ? 0 : name.hashCode()); 357 return result; 358 } persistAsClassStubsForZip(ZipOutputStream zipOutputStream, ProfileInfo info)359 public void persistAsClassStubsForZip(ZipOutputStream zipOutputStream, ProfileInfo info) throws IOException { 360 if (this.types == null) { 361 return; 362 } 363 Collections.sort(this.types); 364 for (Type type : this.types) { 365 StringBuilder buffer = new StringBuilder(this.name.replace('.', '/')); 366 String simpleName = type.getSimpleName(); 367 buffer.append('/').append(simpleName.replace('.', '$')); 368 byte[] classFileBytes = info.getClassFileBytes(type); 369 if (classFileBytes != null) { 370 Util.writeZipFileEntry(zipOutputStream, String.valueOf(buffer), classFileBytes); 371 } 372 } 373 } persistXML(Document document, Element element, String OSGiProfileName)374 public void persistXML(Document document, Element element, String OSGiProfileName) { 375 Element pkg = document.createElement(IApiXmlConstants.ELEMENT_PACKAGE); 376 pkg.setAttribute(IApiXmlConstants.ATTR_NAME, this.name); 377 element.appendChild(pkg); 378 if (this.types == null) { 379 return; 380 } 381 Collections.sort(this.types); 382 for (Iterator<Type> iterator2 = this.types.iterator(); iterator2.hasNext();) { 383 iterator2.next().persistXML(document, pkg, OSGiProfileName); 384 } 385 } size()386 public int size() { 387 return this.types == null ? 0 : this.types.size(); 388 } 389 @Override toString()390 public String toString() { 391 StringBuilder builder = new StringBuilder(); 392 builder.append("Package : "); //$NON-NLS-1$ 393 builder.append(this.name).append(Util.LINE_SEPARATOR); 394 return String.valueOf(builder); 395 } 396 } 397 398 static class ProfileInfo { 399 private static final String BLACK_LIST_NAME = "_blackList_.txt"; //$NON-NLS-1$ 400 private static final String CDC_SUBDIR = "cdc"; //$NON-NLS-1$ 401 private static final String JRE_SUBDIR = "jre"; //$NON-NLS-1$ 402 private static final String OSGI_SUBDIR = "osgi"; //$NON-NLS-1$ 403 private static final String OTHER_PACKAGES = "org.osgi.framework.system.packages"; //$NON-NLS-1$ 404 checkDocStatus(ProfileInfo info, Type type, ZipFile docZip, String docURL, String docRoot)405 private static boolean checkDocStatus(ProfileInfo info, Type type, ZipFile docZip, String docURL, String docRoot) { 406 if (docZip == null && docURL == null) { 407 // if no doc to validate we accept it if on white list 408 if (DEBUG) { 409 System.out.println("No javadoc zip or url for " + info.profileName); //$NON-NLS-1$ 410 } 411 return info.isOnWhiteList(type); 412 } 413 String typeName = getDocTypeName(docRoot, type); 414 if (DEBUG) { 415 System.out.println("Retrieving javadoc for type: " + typeName); //$NON-NLS-1$ 416 } 417 if (docZip == null) { 418 char[] contents = info.getOnlineDocContents(docURL, typeName); 419 if (contents == null) { 420 if (DEBUG) { 421 System.out.println("Found no doc for " + typeName + " - check whitelist"); //$NON-NLS-1$ //$NON-NLS-2$ 422 } 423 return info.isOnWhiteList(type); 424 } 425 return true; 426 } 427 return docZip.getEntry(typeName) != null || info.isOnWhiteList(type); 428 } 429 getDocTypeName(String docRoot, Type type)430 public static String getDocTypeName(String docRoot, Type type) { 431 StringBuilder buffer = new StringBuilder(docRoot); 432 char[] typeNameForDoc = CharOperation.replaceOnCopy(type.name, '.', '/'); 433 typeNameForDoc = CharOperation.replaceOnCopy(typeNameForDoc, '$', '.'); 434 buffer.append(typeNameForDoc); 435 buffer.append(".html"); //$NON-NLS-1$ 436 return String.valueOf(buffer); 437 } 438 getProfileInfo(String profileName, String jreLib, String osgiProfile, String jreDoc, String jreURL, String docRoot, String cacheLocation, String whiteList)439 public static ProfileInfo getProfileInfo(String profileName, String jreLib, String osgiProfile, String jreDoc, String jreURL, String docRoot, String cacheLocation, String whiteList) { 440 ProfileInfo info = new ProfileInfo(profileName, jreLib, osgiProfile, jreDoc, jreURL, docRoot, cacheLocation, whiteList); 441 if (info.getProfileName() == null) { 442 return null; 443 } 444 return info; 445 } 446 initializePackages(String fileName)447 private static Set<String> initializePackages(String fileName) { 448 Set<String> knownPackages = null; 449 try { 450 Properties allProperties = new Properties(); 451 BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(fileName)); 452 allProperties.load(inputStream); 453 inputStream.close(); 454 String property = allProperties.getProperty(OTHER_PACKAGES); 455 if (property != null && property.length() != 0) { 456 String[] packages = property.split(","); //$NON-NLS-1$ 457 for (String package1 : packages) { 458 if (knownPackages == null) { 459 knownPackages = new LinkedHashSet<>(); 460 } 461 knownPackages.add(package1); 462 } 463 } else { 464 knownPackages = Collections.emptySet(); 465 } 466 } catch (IOException e) { 467 // ignore 468 } 469 return knownPackages; 470 } initializeWhiteList(String fileName)471 private static Set<String> initializeWhiteList(String fileName) { 472 if (fileName == null) { 473 return Collections.emptySet(); 474 } 475 Set<String> values = new HashSet<>(); 476 try (LineNumberReader reader = new LineNumberReader(new BufferedReader(new FileReader(fileName)))) { 477 String line; 478 while ((line = reader.readLine()) != null) { 479 String trimmedLine = line.trim(); 480 if (!trimmedLine.isEmpty()) { 481 // only non-empty lines are added trimmed on both ends 482 values.add(trimmedLine); 483 } 484 } 485 } catch (IOException e) { 486 // ignore 487 } 488 return values; 489 } 490 491 File[] allFiles; 492 // set of type names that don't have javadoc 493 Set<String> blackList; 494 String cacheLocation; 495 Map<String, Package> data; 496 String docRoot; 497 long generatedSize; 498 String JREdoc; 499 String JRElib; 500 String JREURL; 501 502 String OSGiProfile; 503 String profileName; 504 long totalSize; 505 506 Set<String> whiteList; 507 ProfileInfo(String profilename, String jreLib, String osgiProfile, String jreDoc, String jreURL, String docroot, String cacheloc, String whitelist)508 private ProfileInfo(String profilename, String jreLib, String osgiProfile, String jreDoc, String jreURL, String docroot, String cacheloc, String whitelist) { 509 this.JREdoc = jreDoc; 510 this.JREURL = jreURL; 511 this.JRElib = jreLib; 512 this.OSGiProfile = osgiProfile; 513 this.docRoot = docroot; 514 this.profileName = profilename; 515 this.cacheLocation = cacheloc; 516 this.whiteList = initializeWhiteList(whitelist); 517 } 518 addToBlackList(String typeName)519 private void addToBlackList(String typeName) { 520 if (this.blackList != null) { 521 this.blackList.add(typeName); 522 return; 523 } 524 this.blackList = new TreeSet<>(); 525 this.blackList.add(typeName); 526 } 527 dumpToCache(String typeName, char[] contents)528 public void dumpToCache(String typeName, char[] contents) { 529 if (!CACHE_ENABLED || this.cacheLocation == null) { 530 return; 531 } 532 File cacheDir = new File(this.cacheLocation); 533 if (!cacheDir.exists()) { 534 if (!cacheDir.mkdirs()) { 535 System.err.println("Cache creation failed for " + typeName + " for profile " + this.getProfileName()); //$NON-NLS-1$ //$NON-NLS-2$ 536 return; 537 } 538 } 539 File profileCache = new File(cacheDir, getProfileFileName()); 540 if (!profileCache.exists()) { 541 if (!profileCache.mkdirs()) { 542 System.err.println("Cache creation failed for " + typeName + " for profile " + this.getProfileName()); //$NON-NLS-1$ //$NON-NLS-2$ 543 return; 544 } 545 } 546 File docType = new File(profileCache, typeName); 547 if (docType.exists()) { 548 // already in the cache 549 return; 550 } else { 551 File parentFile = docType.getParentFile(); 552 if (!parentFile.exists()) { 553 if (!parentFile.mkdirs()) { 554 System.err.println("Cache creation failed for " + typeName + " for profile " + this.getProfileName()); //$NON-NLS-1$ //$NON-NLS-2$ 555 return; 556 } 557 } 558 try (BufferedWriter writer = new BufferedWriter(new FileWriter(docType))) { 559 writer.write(contents); 560 writer.flush(); 561 } catch (IOException e) { 562 e.printStackTrace(); 563 } 564 } 565 } 566 generateEEDescription(String outputDir)567 void generateEEDescription(String outputDir) { 568 if (this.data == null) { 569 System.err.println("No data to persist for " + this.getProfileName()); //$NON-NLS-1$ 570 return; 571 } 572 String subDir = getSubDir(); 573 persistData(outputDir, subDir); 574 persistDataAsClassFilesInZipFormat(outputDir, subDir); 575 } 576 getAllTypes()577 public Map<String, Type> getAllTypes() { 578 Map<String, Type> result = new LinkedHashMap<>(); 579 Set<String> keySet = this.data.keySet(); 580 String[] sortedKeys = new String[keySet.size()]; 581 keySet.toArray(sortedKeys); 582 Arrays.sort(sortedKeys); 583 for (String key : sortedKeys) { 584 Package package1 = this.data.get(key); 585 if (package1 != null) { 586 package1.collectTypes(result); 587 } else { 588 System.err.println("Missing package for profile info XML serialization: " + key); //$NON-NLS-1$ 589 } 590 } 591 return result; 592 } 593 getCacheRoot()594 private File getCacheRoot() { 595 File cacheDir = new File(this.cacheLocation); 596 if (cacheDir.exists() || cacheDir.mkdirs()) { 597 File profileCache = new File(cacheDir, getProfileFileName()); 598 if (profileCache.exists() || profileCache.mkdirs()) { 599 return profileCache; 600 } 601 } 602 return null; 603 } 604 getClassFileBytes(Type type)605 public byte[] getClassFileBytes(Type type) { 606 if (this.allFiles == null) { 607 throw new IllegalStateException("No jar files to open"); //$NON-NLS-1$ 608 } 609 String typeName = new String(type.name); 610 byte[] classFileBytes = null; 611 try { 612 String zipFileEntryName = typeName.replace('.', '/') + ".class"; //$NON-NLS-1$ 613 loop: for (int i = 0, max = this.allFiles.length; i < max; i++) { 614 if (allFiles[i].toString().endsWith(".jmod") && !zipFileEntryName.startsWith("classes/")) { //$NON-NLS-1$ //$NON-NLS-2$ 615 zipFileEntryName = "classes/" + zipFileEntryName; //$NON-NLS-1$ 616 } 617 try (ZipFile zipFile = new ZipFile(allFiles[i])) { 618 ZipEntry zipEntry = zipFile.getEntry(zipFileEntryName); 619 if (zipEntry == null) { 620 continue loop; 621 } 622 try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry))) { 623 classFileBytes = Util.getInputStreamAsByteArray(inputStream, -1); 624 break loop; 625 } 626 } 627 } 628 } catch (ZipException e) { 629 e.printStackTrace(); 630 } catch (IOException e) { 631 e.printStackTrace(); 632 } 633 if (classFileBytes == null) { 634 throw new IllegalStateException("Could not retrieve byte[] for " + typeName); //$NON-NLS-1$ 635 } 636 ClassReader classReader = new ClassReader(classFileBytes); 637 StubClassAdapter visitor = new StubClassAdapter(type); 638 classReader.accept(visitor, ClassReader.SKIP_DEBUG); 639 if (visitor.shouldIgnore()) { 640 return null; 641 } 642 643 return visitor.getStub().getBytes(); 644 } 645 getFromCache(String typeName)646 public char[] getFromCache(String typeName) { 647 if (!CACHE_ENABLED || this.cacheLocation == null) { 648 return null; 649 } 650 File profileCache = getCacheRoot(); 651 if (profileCache != null) { 652 File docType = new File(profileCache, typeName); 653 if (docType.exists()) { 654 try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(docType))) { 655 return Util.getInputStreamAsCharArray(stream, -1, null); 656 } catch (IOException e) { 657 e.printStackTrace(); 658 } 659 } 660 } 661 return null; 662 } 663 getOnlineDocContents(String docURL, String typeName)664 char[] getOnlineDocContents(String docURL, String typeName) { 665 // check the doc online 666 String typeDocURL = docURL + typeName; 667 char[] contents = this.getFromCache(typeName); 668 if (contents == null && !ONLY_USE_CACHE) { 669 // check the black list 670 if (this.isOnBlackList(typeName)) { 671 return null; 672 } 673 contents = Util.getURLContents(typeDocURL); 674 if (contents == null) { 675 this.addToBlackList(typeName); 676 return null; 677 } else { 678 this.dumpToCache(typeName, contents); 679 } 680 } 681 return contents; 682 } 683 getProfileFileName()684 public String getProfileFileName() { 685 return Util.getProfileFileName(getProfileName()); 686 } 687 getProfileName()688 public String getProfileName() { 689 return this.profileName; 690 } 691 getSubDir()692 private String getSubDir() { 693 if (Util.getProfileFileName(ProfileModifiers.CDC_1_0_FOUNDATION_1_0_NAME).equals(this.profileName) || Util.getProfileFileName(ProfileModifiers.CDC_1_1_FOUNDATION_1_1_NAME).equals(this.profileName)) { 694 return CDC_SUBDIR; 695 } else if (Util.getProfileFileName(ProfileModifiers.OSGI_MINIMUM_1_0_NAME).equals(this.profileName) || Util.getProfileFileName(ProfileModifiers.OSGI_MINIMUM_1_1_NAME).equals(this.profileName) || Util.getProfileFileName(ProfileModifiers.OSGI_MINIMUM_1_2_NAME).equals(this.profileName)) { 696 return OSGI_SUBDIR; 697 } else { 698 return JRE_SUBDIR; 699 } 700 } 701 initializeBlackList()702 private Set<String> initializeBlackList() { 703 File cacheRoot = getCacheRoot(); 704 File blackListFile = new File(cacheRoot, BLACK_LIST_NAME); 705 Set<String> values = new TreeSet<>(); 706 if (blackListFile.exists()) { 707 try (LineNumberReader reader = new LineNumberReader( 708 new BufferedReader(new FileReader(blackListFile)))) { 709 String line; 710 while ((line = reader.readLine()) != null) { 711 String trimmedLine = line.trim(); 712 if (!trimmedLine.isEmpty()) { 713 // only non-empty lines are added trimmed on both 714 // ends 715 values.add(trimmedLine); 716 } 717 } 718 } catch (IOException e) { 719 // ignore 720 } 721 } 722 return values; 723 } 724 initializeData()725 public void initializeData() throws IOException { 726 String pname = this.getProfileName(); 727 if (pname == null) { 728 // invalid profile info 729 System.err.println("Info are invalid"); //$NON-NLS-1$ 730 return; 731 } 732 733 if (DEBUG) { 734 System.out.println("Profile : " + pname); //$NON-NLS-1$ 735 } 736 long time = System.currentTimeMillis(); 737 this.allFiles = Util.getAllFiles(new File(this.JRElib), 738 pathname -> pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".jar") //$NON-NLS-1$ 739 || pathname.getName().toLowerCase().endsWith(".jmod")); //$NON-NLS-1$ 740 if (allFiles == null) { 741 System.err.println("No jar files to proceed"); //$NON-NLS-1$ 742 return; 743 } 744 // initialize known packages 745 String osgiProfileName = this.OSGiProfile; 746 Set<String> knownPackages = initializePackages(osgiProfileName); 747 // known packages should be part of the white list by default 748 if (this.whiteList != null && !this.whiteList.isEmpty()) { 749 this.whiteList.addAll(knownPackages); 750 } else { 751 this.whiteList = Collections.unmodifiableSet(knownPackages); 752 } 753 Map<String, Type> allVisibleTypes = new LinkedHashMap<>(); 754 Map<String, Type> allTypes = new LinkedHashMap<>(); 755 this.totalSize = 0; 756 for (File currentFile : allFiles) { 757 this.totalSize += currentFile.length(); 758 759 try (ZipFile zipFile = new ZipFile(currentFile)) { 760 for (Enumeration<? extends ZipEntry> enumeration = zipFile.entries(); enumeration.hasMoreElements();) { 761 ZipEntry zipEntry = enumeration.nextElement(); 762 if (!zipEntry.getName().endsWith(".class")) //$NON-NLS-1$ 763 { 764 continue; 765 } 766 InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(zipEntry)); 767 IClassFileReader classFileReader = null; 768 try { 769 classFileReader = ToolFactory.createDefaultClassFileReader(inputStream, IClassFileReader.ALL_BUT_METHOD_BODIES); 770 } finally { 771 inputStream.close(); 772 } 773 if (classFileReader == null) { 774 continue; 775 } 776 char[] className = classFileReader.getClassName(); 777 char[] packageName = null; 778 int lastIndexOf = CharOperation.lastIndexOf('/', className); 779 if (lastIndexOf == -1) { 780 packageName = new char[0]; 781 } else { 782 packageName = CharOperation.subarray(className, 0, lastIndexOf); 783 } 784 if (this.isMatching(knownPackages, packageName)) { 785 Type type = Type.newType(this, classFileReader, this.JREdoc, this.JREURL, this.docRoot); 786 if (type != null) { 787 if (type.isProtected() || type.isPublic()) { 788 allVisibleTypes.put(type.getFullQualifiedName(), type); 789 } 790 allTypes.put(type.getFullQualifiedName(), type); 791 } 792 } 793 } 794 } 795 } 796 // list all results 797 List<Type> visibleTypes = new ArrayList<>(); 798 visibleTypes.addAll(allVisibleTypes.values()); 799 // check transitive closure to make sure all methods/fields from 800 // superclass are visible when resolving fields/methods 801 while (!visibleTypes.isEmpty()) { 802 Type type = visibleTypes.remove(0); 803 String superclassName = type.getSuperclassName(); 804 while (superclassName != null) { 805 if (allVisibleTypes.get(superclassName) == null) { 806 // look for required type 807 Type currentSuperclass = allTypes.get(superclassName); 808 if (currentSuperclass == null) { 809 if (DEBUG) { 810 System.out.println("Missing type: " + superclassName); //$NON-NLS-1$ 811 } 812 break; 813 } else { 814 allVisibleTypes.put(currentSuperclass.getFullQualifiedName(), currentSuperclass); 815 visibleTypes.add(currentSuperclass); 816 if (DEBUG) { 817 System.out.println("Reinject type: " + currentSuperclass.getFullQualifiedName()); //$NON-NLS-1$ 818 } 819 } 820 } 821 Type superclass = allVisibleTypes.get(superclassName); 822 superclassName = superclass.getSuperclassName(); 823 } 824 } 825 List<Type> isInDoc = new ArrayList<>(); 826 ZipFile docZip = null; 827 if (this.JREdoc != null) { 828 try { 829 docZip = new ZipFile(this.JREdoc); 830 } catch (FileNotFoundException e) { 831 // no zip file 832 } 833 } 834 try { 835 for (Type type : allVisibleTypes.values()) { 836 if (checkDocStatus(this, type, docZip, this.JREURL, this.docRoot)) { 837 isInDoc.add(type); 838 } 839 } 840 } finally { 841 if (docZip != null) { 842 docZip.close(); 843 } 844 } 845 HashMap<String, Package> typesPerPackage = new LinkedHashMap<>(); 846 for (Type type : isInDoc) { 847 String packageName = type.getPackage(); 848 Package package1 = typesPerPackage.get(packageName); 849 if (package1 == null) { 850 package1 = new Package(packageName); 851 typesPerPackage.put(packageName, package1); 852 } 853 package1.addType(type); 854 } 855 this.data = typesPerPackage; 856 if (DEBUG) { 857 System.out.println("Time spent for gathering datas for " + pname + " : " + (System.currentTimeMillis() - time) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 858 } 859 860 if (this.blackList != null) { 861 // persist black list 862 this.persistBlackList(); 863 } 864 } 865 isMatching(Set<String> knownPackages, char[] packageName)866 private boolean isMatching(Set<String> knownPackages, char[] packageName) { 867 if (CharOperation.indexOf("java/".toCharArray(), packageName, true) == 0) { //$NON-NLS-1$ 868 return true; 869 } 870 if (knownPackages.isEmpty()) { 871 return false; 872 } 873 String currentPackage = new String(CharOperation.replaceOnCopy(packageName, '/', '.')); 874 return knownPackages.contains(currentPackage) || this.isOnWhiteList(currentPackage); 875 } 876 isOnBlackList(String typeName)877 private boolean isOnBlackList(String typeName) { 878 if (this.blackList != null) { 879 return this.blackList.contains(typeName); 880 } 881 // retrieve black list if it exists 882 this.blackList = initializeBlackList(); 883 return this.blackList.contains(typeName); 884 } isOnWhiteList(String packageName)885 private boolean isOnWhiteList(String packageName) { 886 return packageName.startsWith("java.") || packageName.startsWith("javax.") || this.whiteList.contains(packageName); //$NON-NLS-1$ //$NON-NLS-2$ 887 } isOnWhiteList(Type type)888 private boolean isOnWhiteList(Type type) { 889 return isOnWhiteList(type.getPackage()); 890 } persistBlackList()891 private void persistBlackList() { 892 if (this.blackList == null || this.blackList.isEmpty()) { 893 return; 894 } 895 File cacheRoot = getCacheRoot(); 896 File blackListFile = new File(cacheRoot, BLACK_LIST_NAME); 897 if (!blackListFile.exists()) { 898 try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(blackListFile)));) { 899 for (Iterator<String> iterator = this.blackList.iterator(); iterator.hasNext();) { 900 writer.println(iterator.next()); 901 } 902 writer.flush(); 903 } catch (IOException e) { 904 // ignore 905 } 906 } 907 } 908 persistChildren(Document document, Element xmlElement, Map<String, Package> packageMap, String OSGiProfileName)909 private void persistChildren(Document document, Element xmlElement, Map<String, Package> packageMap, String OSGiProfileName) { 910 Set<String> keySet = packageMap.keySet(); 911 String[] sortedKeys = new String[keySet.size()]; 912 keySet.toArray(sortedKeys); 913 Arrays.sort(sortedKeys); 914 for (String key : sortedKeys) { 915 Package package1 = packageMap.get(key); 916 if (package1 != null) { 917 package1.persistXML(document, xmlElement, OSGiProfileName); 918 } else { 919 System.err.println("Missing package for profile info XML serialization: " + key); //$NON-NLS-1$ 920 } 921 } 922 } 923 persistChildrenAsClassFile(ZipOutputStream zipOutputStream, Map<String, Package> packageMap, ProfileInfo info)924 private void persistChildrenAsClassFile(ZipOutputStream zipOutputStream, Map<String, Package> packageMap, ProfileInfo info) throws IOException { 925 Set<String> keySet = packageMap.keySet(); 926 String[] sortedKeys = new String[keySet.size()]; 927 keySet.toArray(sortedKeys); 928 Arrays.sort(sortedKeys); 929 for (String key : sortedKeys) { 930 Package package1 = packageMap.get(key); 931 if (package1 != null) { 932 package1.persistAsClassStubsForZip(zipOutputStream, info); 933 } else { 934 System.err.println("Missing package for profile info zip serialization: " + key); //$NON-NLS-1$ 935 } 936 } 937 } persistData(String rootName, String subDirName)938 private void persistData(String rootName, String subDirName) { 939 try { 940 Document document = org.eclipse.pde.api.tools.internal.util.Util.newDocument(); 941 Element component = document.createElement(IApiXmlConstants.ELEMENT_COMPONENT); 942 String profileName2 = this.getProfileName(); 943 component.setAttribute(IApiXmlConstants.ATTR_ID, profileName2); 944 component.setAttribute(IApiXmlConstants.ATTR_VERSION, IApiXmlConstants.API_DESCRIPTION_CURRENT_VERSION); 945 document.appendChild(component); 946 persistChildren(document, component, this.data, profileName2); 947 String contents = org.eclipse.pde.api.tools.internal.util.Util.serializeDocument(document); 948 String fileName = profileName2 + ".xml"; //$NON-NLS-1$ 949 Util.write(rootName, subDirName, fileName, contents); 950 } catch (DOMException e) { 951 e.printStackTrace(); 952 } catch (CoreException e) { 953 e.printStackTrace(); 954 } 955 } 956 persistDataAsClassFilesInZipFormat(String rootName, String subDirName)957 private void persistDataAsClassFilesInZipFormat(String rootName, String subDirName) { 958 ZipOutputStream zipOutputStream = null; 959 String profileName2 = this.getProfileFileName(); 960 File root = new File(rootName); 961 if (!root.exists()) { 962 root.mkdirs(); 963 } 964 File subDir = new File(root, subDirName); 965 if (!subDir.exists()) { 966 subDir.mkdir(); 967 } 968 if (profileName2.indexOf('/') != 0) { 969 profileName2 = profileName2.replace('/', '_'); 970 } 971 File file = new File(subDir, profileName2 + ".zip"); //$NON-NLS-1$ 972 try { 973 zipOutputStream = Util.getOutputStream(file); 974 } catch (FileNotFoundException e) { 975 e.printStackTrace(); 976 } 977 if (zipOutputStream == null) { 978 System.err.println("Could not create the output file : " + file.getAbsolutePath()); //$NON-NLS-1$ 979 return; 980 } 981 try { 982 persistChildrenAsClassFile(zipOutputStream, this.data, this); 983 zipOutputStream.flush(); 984 } catch (IOException e) { 985 e.printStackTrace(); 986 } finally { 987 try { 988 zipOutputStream.close(); 989 } catch (IOException e) { 990 // ignore 991 } 992 } 993 this.generatedSize = file.length(); 994 System.out.println("The stub for the profile " + this.profileName + " was generated from " + this.totalSize + " bytes."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 995 System.out.println("Its generated size is " + this.generatedSize + " bytes."); //$NON-NLS-1$ //$NON-NLS-2$ 996 System.out.println("Ratio : " + (((double) this.generatedSize / (double) this.totalSize) * 100.0) + "%"); //$NON-NLS-1$ //$NON-NLS-2$ 997 } 998 @Override toString()999 public String toString() { 1000 return this.getProfileName(); 1001 } 1002 } 1003 1004 static class StubClass { 1005 private static final int CURRENT_VERSION = 3; 1006 int access; 1007 int classNameIndex; 1008 List<StubField> fields; 1009 1010 int[] interfacesIndexes; 1011 List<StubMethod> methods; 1012 Map<String, Integer> pool; 1013 int poolIndex; 1014 int superNameIndex; 1015 StubClass(int acc, String className2, String superName2, String[] interfaces2)1016 public StubClass(int acc, String className2, String superName2, String[] interfaces2) { 1017 this.access = acc; 1018 this.pool = new LinkedHashMap<>(); 1019 this.classNameIndex = getIndex(className2); 1020 this.superNameIndex = superName2 != null ? getIndex(superName2) : -1; 1021 if (interfaces2 != null) { 1022 this.interfacesIndexes = new int[interfaces2.length]; 1023 for (int i = 0; i < interfaces2.length; i++) { 1024 this.interfacesIndexes[i] = getIndex(interfaces2[i]); 1025 } 1026 } 1027 } 1028 addField(String fieldName)1029 public void addField(String fieldName) { 1030 if (this.fields == null) { 1031 this.fields = new ArrayList<>(); 1032 } 1033 this.fields.add(new StubField(getIndex(fieldName))); 1034 } 1035 addMethod(String methodName, String desc)1036 public StubMethod addMethod(String methodName, String desc) { 1037 if (this.methods == null) { 1038 this.methods = new ArrayList<>(); 1039 } 1040 StubMethod method = new StubMethod(getIndex(methodName), getIndex(desc)); 1041 this.methods.add(method); 1042 return method; 1043 } 1044 getBytes()1045 public byte[] getBytes() { 1046 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 1047 1048 try (DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { 1049 outputStream.writeShort(CURRENT_VERSION); 1050 outputStream.writeShort(this.poolIndex); 1051 for (Entry<String, Integer> next : this.pool.entrySet()) { 1052 outputStream.writeUTF(next.getKey()); 1053 outputStream.writeShort(next.getValue().intValue()); 1054 } 1055 outputStream.writeChar(this.access); 1056 outputStream.writeShort(this.classNameIndex); 1057 outputStream.writeShort(this.superNameIndex); 1058 int length = this.interfacesIndexes != null ? this.interfacesIndexes.length : 0; 1059 outputStream.writeShort(length); 1060 for (int i = 0; i < length; i++) { 1061 outputStream.writeShort(this.interfacesIndexes[i]); 1062 } 1063 int fieldsLength = this.fields == null ? 0 : this.fields.size(); 1064 outputStream.writeShort(fieldsLength); 1065 for (int i = 0; i < fieldsLength; i++) { 1066 outputStream.writeShort(this.fields.get(i).nameIndex); 1067 } 1068 int methodsLength = this.methods == null ? 0 : this.methods.size(); 1069 outputStream.writeShort(methodsLength); 1070 for (int i = 0; i < methodsLength; i++) { 1071 StubMethod stubMethod = this.methods.get(i); 1072 outputStream.writeShort(stubMethod.selectorIndex); 1073 outputStream.writeShort(stubMethod.signatureIndex); 1074 outputStream.writeByte(stubMethod.isPolymorphic ? 1 : 0); 1075 } 1076 outputStream.flush(); 1077 } catch (IOException e) { 1078 e.printStackTrace(); 1079 } 1080 this.pool = null; 1081 this.fields = null; 1082 this.methods = null; 1083 return byteArrayOutputStream.toByteArray(); 1084 } 1085 getIndex(String name)1086 private int getIndex(String name) { 1087 Integer integer = this.pool.get(name); 1088 if (integer != null) { 1089 return integer.intValue(); 1090 } 1091 int value = this.poolIndex++; 1092 this.pool.put(name, Integer.valueOf(value)); 1093 return value; 1094 } 1095 } 1096 1097 /** 1098 * Class adapter 1099 */ 1100 static class StubClassAdapter extends ClassVisitor { 1101 static final int IGNORE_CLASS_FILE = 0x100; 1102 1103 int flags; 1104 String name; 1105 StubClass stub; 1106 Type type; 1107 1108 /** 1109 * Constructor 1110 * @param stubtype 1111 */ StubClassAdapter(Type stubtype)1112 public StubClassAdapter(Type stubtype) { 1113 super(Opcodes.ASM6, new ClassWriter(0)); 1114 this.type = stubtype; 1115 } 1116 getStub()1117 public StubClass getStub() { 1118 return this.stub; 1119 } 1120 1121 /** 1122 * @return if this class file should be ignored or not 1123 */ shouldIgnore()1124 public boolean shouldIgnore() { 1125 return (this.flags & IGNORE_CLASS_FILE) != 0; 1126 } 1127 1128 @Override visit(int version, int access, String className, String signature, String superName, String[] interfaces)1129 public void visit(int version, int access, String className, String signature, String superName, String[] interfaces) { 1130 this.name = className; 1131 this.stub = new StubClass(access, className, superName, interfaces); 1132 } 1133 1134 @Override visitAnnotation(String arg0, boolean arg1)1135 public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) { 1136 return null; 1137 } 1138 1139 @Override visitAttribute(Attribute attr)1140 public void visitAttribute(Attribute attr) { 1141 if ("Synthetic".equals(attr.type)) { //$NON-NLS-1$ 1142 this.flags |= IGNORE_CLASS_FILE; 1143 } 1144 } 1145 1146 @Override visitEnd()1147 public void visitEnd() { 1148 } 1149 1150 @Override visitField(int access, String fieldName, String desc, String signature, Object value)1151 public FieldVisitor visitField(int access, String fieldName, String desc, String signature, Object value) { 1152 if (type.getField(fieldName) == null) { 1153 return null; 1154 } 1155 this.stub.addField(fieldName); 1156 return null; 1157 } 1158 1159 @Override visitInnerClass(String innerClassName, String outerName, String innerName, int access)1160 public void visitInnerClass(String innerClassName, String outerName, String innerName, int access) { 1161 if (this.name.equals(innerClassName) && (outerName == null)) { 1162 // local class 1163 this.flags |= IGNORE_CLASS_FILE; 1164 } 1165 } 1166 1167 @Override visitMethod(int access, String methodName, String desc, String signature, String[] exceptions)1168 public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) { 1169 if ("<clinit>".equals(methodName)) { //$NON-NLS-1$ 1170 return null; 1171 } 1172 if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) { 1173 return null; 1174 } 1175 if (((access & Opcodes.ACC_BRIDGE) != 0) || ((access & Opcodes.ACC_SYNTHETIC) != 0)) { 1176 return null; 1177 } 1178 if (this.type.getMethod(methodName, desc) == null) { 1179 return null; 1180 } 1181 final StubMethod method = this.stub.addMethod(methodName, desc); 1182 return new MethodVisitor(Opcodes.ASM6, super.visitMethod(access, methodName, desc, signature, exceptions)) { 1183 @Override 1184 public AnnotationVisitor visitAnnotation(String sig, boolean visible) { 1185 if (visible && "Ljava/lang/invoke/MethodHandle$PolymorphicSignature;".equals(sig)) { //$NON-NLS-1$ 1186 method.isPolymorphic(); 1187 } 1188 return super.visitAnnotation(sig, visible); 1189 } 1190 }; 1191 } 1192 @Override 1193 public void visitOuterClass(String arg0, String arg1, String arg2) { 1194 // ignore 1195 } 1196 @Override 1197 public void visitSource(String arg0, String arg1) { 1198 // ignore 1199 } 1200 } 1201 1202 static class StubField { 1203 int nameIndex; 1204 1205 public StubField(int index) { 1206 this.nameIndex = index; 1207 } 1208 } 1209 1210 static class StubMethod { 1211 boolean isPolymorphic; 1212 int selectorIndex; 1213 int signatureIndex; 1214 1215 public StubMethod(int selectorindex, int sigindex) { 1216 this.selectorIndex = selectorindex; 1217 this.signatureIndex = sigindex; 1218 } 1219 1220 public void isPolymorphic() { 1221 this.isPolymorphic = true; 1222 } 1223 } 1224 1225 static class Type extends AbstractNode implements Comparable<Type> { 1226 1227 static boolean isFinal(int accessFlags) { 1228 return (accessFlags & Flags.AccFinal) != 0; 1229 } 1230 static boolean isPrivate(int accessFlags) { 1231 return (accessFlags & Flags.AccPrivate) != 0; 1232 } 1233 static boolean isProtected(int accessFlags) { 1234 return (accessFlags & Flags.AccProtected) != 0; 1235 } 1236 static boolean isPublic(int accessFlags) { 1237 return (accessFlags & Flags.AccPublic) != 0; 1238 } 1239 static boolean isStatic(int accessFlags) { 1240 return (accessFlags & Flags.AccStatic) != 0; 1241 } 1242 1243 private static boolean isVisibleField(int typeAccessFlags, int fieldAccessFlags) { 1244 if (isPublic(fieldAccessFlags)) { 1245 return true; 1246 } 1247 if (isProtected(fieldAccessFlags)) { 1248 return !isFinal(typeAccessFlags); 1249 } 1250 return false; 1251 } 1252 1253 private static boolean isVisibleMethod(int typeAccessFlags, int methodAccessFlags) { 1254 if (isPublic(methodAccessFlags)) { 1255 return true; 1256 } 1257 if (isProtected(methodAccessFlags)) { 1258 return !isFinal(typeAccessFlags); 1259 } 1260 return false; 1261 } 1262 1263 public static Type newType(ProfileInfo info, IClassFileReader reader, String docZipFileName, String docURL, String docRoot) { 1264 IInnerClassesAttribute innerClassesAttribute = reader.getInnerClassesAttribute(); 1265 if (innerClassesAttribute != null) { 1266 // search the right entry 1267 IInnerClassesAttributeEntry[] entries = innerClassesAttribute.getInnerClassAttributesEntries(); 1268 for (IInnerClassesAttributeEntry entry : entries) { 1269 char[] innerClassName = entry.getInnerClassName(); 1270 if (innerClassName != null) { 1271 if (CharOperation.equals(reader.getClassName(), innerClassName)) { 1272 int accessFlags2 = entry.getAccessFlags(); 1273 if (isPrivate(accessFlags2)) { 1274 return null; 1275 } 1276 } 1277 } 1278 } 1279 } 1280 return new Type(reader, docZipFileName); 1281 } 1282 1283 Set<Field> fields; 1284 Set<Method> methods; 1285 int modifiers; 1286 1287 char[] name; 1288 char[] superclassName; 1289 char[][] superinterfacesNames; 1290 1291 private Type(IClassFileReader reader, String docZipFileName) { 1292 ZipFile docZip = null; 1293 try { 1294 if (docZipFileName != null) { 1295 try { 1296 docZip = new ZipFile(docZipFileName); 1297 } catch (FileNotFoundException e) { 1298 // no zip file 1299 } 1300 } 1301 char[] className = reader.getClassName(); 1302 className = CharOperation.replaceOnCopy(className, '/', '.'); 1303 this.name = className; 1304 if (DEBUG) { 1305 System.out.println("Adding type: " + String.valueOf(className)); //$NON-NLS-1$ 1306 } 1307 char[] scname = reader.getSuperclassName(); 1308 if (scname != null) { 1309 scname = CharOperation.replaceOnCopy(scname, '/', '.'); 1310 this.superclassName = scname; 1311 } 1312 char[][] interfaceNames = CharOperation.deepCopy(reader.getInterfaceNames()); 1313 for (char[] interfaceName : interfaceNames) { 1314 CharOperation.replace(interfaceName, '/', '.'); 1315 } 1316 this.superinterfacesNames = interfaceNames; 1317 this.modifiers = reader.getAccessFlags(); 1318 IFieldInfo[] fieldInfos = reader.getFieldInfos(); 1319 int length = fieldInfos.length; 1320 for (int i = 0; i < length; i++) { 1321 IFieldInfo fieldInfo = fieldInfos[i]; 1322 if (isVisibleField(this.modifiers, fieldInfo.getAccessFlags())) { 1323 if (fields == null) { 1324 this.fields = new HashSet<>(); 1325 } 1326 Field field = new Field(fieldInfo.getName(), fieldInfo.getDescriptor()); 1327 fields.add(field); 1328 if (DEBUG) { 1329 System.out.println("Adding field: " + field); //$NON-NLS-1$ 1330 } 1331 } 1332 } 1333 IMethodInfo[] methodInfos = reader.getMethodInfos(); 1334 length = methodInfos.length; 1335 for (IMethodInfo methodInfo : methodInfos) { 1336 IClassFileAttribute[] attributes = methodInfo.getAttributes(); 1337 ISignatureAttribute signatureAttribute = null; 1338 for (IClassFileAttribute currentAttribute : attributes) { 1339 if (CharOperation.equals(currentAttribute.getAttributeName(), IAttributeNamesConstants.SIGNATURE)) { 1340 signatureAttribute = (ISignatureAttribute) currentAttribute; 1341 break; 1342 } 1343 } 1344 char[] signature = null; 1345 if (signatureAttribute != null) { 1346 signature = signatureAttribute.getSignature(); 1347 } else { 1348 signature = methodInfo.getDescriptor(); 1349 } 1350 int accessFlags = methodInfo.getAccessFlags(); 1351 if (isVisibleMethod(this.modifiers, accessFlags)) { 1352 if (methods == null) { 1353 this.methods = new HashSet<>(); 1354 } 1355 Method method = new Method(accessFlags, methodInfo.getName(), methodInfo.getDescriptor(), signatureAttribute == null ? null : signature); 1356 methods.add(method); 1357 if (DEBUG) { 1358 System.out.println("Adding method: " + method); //$NON-NLS-1$ 1359 } 1360 } 1361 } 1362 } catch (IOException e) { 1363 // no zip file 1364 System.err.println("Missing doc zip at " + docZipFileName); //$NON-NLS-1$ 1365 } finally { 1366 if (docZip != null) { 1367 try { 1368 docZip.close(); 1369 } catch (IOException e) { 1370 // ignore 1371 } 1372 } 1373 } 1374 } 1375 1376 public void addField(Field f) { 1377 if (this.fields == null) { 1378 this.fields = new HashSet<>(); 1379 } 1380 this.fields.add(f); 1381 } 1382 public void addMethod(Method m) { 1383 if (this.methods == null) { 1384 this.methods = new HashSet<>(); 1385 } 1386 this.methods.add(m); 1387 } 1388 @Override 1389 public int compareTo(Type type) { 1390 return this.getSimpleName().compareTo(type.getSimpleName()); 1391 } 1392 1393 @Override 1394 public boolean equals(Object obj) { 1395 if (this == obj) { 1396 return true; 1397 } 1398 if (obj == null) { 1399 return false; 1400 } 1401 if (!(obj instanceof Type)) { 1402 return false; 1403 } 1404 Type other = (Type) obj; 1405 return Arrays.equals(name, other.name); 1406 } 1407 public Field getField(String fname) { 1408 if (this.fields == null) { 1409 return null; 1410 } 1411 Field fieldToFind = new Field(fname.toCharArray(), null); 1412 for (Field currentField : this.fields) { 1413 if (fieldToFind.equals(currentField)) { 1414 return currentField; 1415 } 1416 } 1417 return null; 1418 } 1419 1420 public String getFullQualifiedName() { 1421 return String.valueOf(this.name); 1422 } 1423 public String getSuperclassName() { 1424 if (this.superclassName == null) { 1425 return null; 1426 } 1427 return String.valueOf(this.superclassName); 1428 } 1429 public Method getMethod(String selector, String signature) { 1430 if (this.methods == null) { 1431 return null; 1432 } 1433 Method methodToFind = new Method(0, selector.toCharArray(), signature.toCharArray(), null); 1434 for (Method currentMethod : this.methods) { 1435 if (methodToFind.equals(currentMethod)) { 1436 return currentMethod; 1437 } 1438 } 1439 return null; 1440 } 1441 public String getPackage() { 1442 int index = CharOperation.lastIndexOf('.', this.name); 1443 return new String(CharOperation.subarray(this.name, 0, index)); 1444 } 1445 public String getSimpleName() { 1446 return Util.getSimpleName(this.name); 1447 } 1448 1449 @Override 1450 public int hashCode() { 1451 final int prime = 31; 1452 int result = 1; 1453 result = prime * result + Arrays.hashCode(name); 1454 return result; 1455 } 1456 1457 public boolean isProtected() { 1458 return isProtected(this.modifiers); 1459 } 1460 1461 public boolean isPublic() { 1462 return isPublic(this.modifiers); 1463 } 1464 1465 public void persistXML(Document document, Element parent, String OSGiProfileName) { 1466 Element type = document.createElement(IApiXmlConstants.ELEMENT_TYPE); 1467 parent.appendChild(type); 1468 type.setAttribute(IApiXmlConstants.ATTR_NAME, getSimpleName()); 1469 if (this.superclassName != null) { 1470 type.setAttribute(IApiXmlConstants.ATTR_SUPER_CLASS, new String(this.superclassName)); 1471 } 1472 if (this.superinterfacesNames != null && this.superinterfacesNames.length != 0) { 1473 type.setAttribute(IApiXmlConstants.ATTR_SUPER_INTERFACES, Util.getInterfaces(this.superinterfacesNames)); 1474 } 1475 type.setAttribute(IApiXmlConstants.ATTR_INTERFACE, Boolean.toString((this.modifiers & Flags.AccInterface) != 0)); 1476 persistAnnotations(type, OSGiProfileName); 1477 if (this.fields != null) { 1478 Field[] allFields = new Field[this.fields.size()]; 1479 this.fields.toArray(allFields); 1480 Arrays.sort(allFields); 1481 for (Field allField : allFields) { 1482 allField.persistXML(document, type, OSGiProfileName); 1483 } 1484 } 1485 if (this.methods != null) { 1486 Method[] allMethods = new Method[this.methods.size()]; 1487 this.methods.toArray(allMethods); 1488 Arrays.sort(allMethods); 1489 for (Method allMethod : allMethods) { 1490 allMethod.persistXML(document, type, OSGiProfileName); 1491 } 1492 } 1493 } 1494 @Override 1495 public String toString() { 1496 StringBuilder buffer = new StringBuilder(); 1497 buffer.append(this.getPackage() + "." + this.getSimpleName()).append(Util.LINE_SEPARATOR); //$NON-NLS-1$ 1498 // list all fields 1499 1500 if (this.fields != null) { 1501 Field[] allFields = new Field[this.fields.size()]; 1502 this.fields.toArray(allFields); 1503 Arrays.sort(allFields); 1504 for (Field field : allFields) { 1505 buffer.append("\t") //$NON-NLS-1$ 1506 .append(field).append(Util.LINE_SEPARATOR); 1507 } 1508 } 1509 if (this.methods != null) { 1510 Method[] allMethods = new Method[this.methods.size()]; 1511 this.methods.toArray(allMethods); 1512 Arrays.sort(allMethods); 1513 for (Method method : allMethods) { 1514 buffer.append("\t").append(method).append(Util.LINE_SEPARATOR); //$NON-NLS-1$ 1515 } 1516 } 1517 return String.valueOf(buffer); 1518 } 1519 } 1520 static SortedSet<String> ACCEPTED_EEs; 1521 static boolean CACHE_ENABLED = true; 1522 static boolean DEBUG = false; 1523 static boolean ONLY_USE_CACHE = false; 1524 static final String PROPERTY_CACHE_LOCATION = ".cacheLocation"; //$NON-NLS-1$ 1525 static final String PROPERTY_DOC_ROOT = ".docRoot"; //$NON-NLS-1$ 1526 static final String PROPERTY_JRE_DOC = ".jreDoc"; //$NON-NLS-1$ 1527 static final String PROPERTY_JRE_LIB = ".jreLib"; //$NON-NLS-1$ 1528 static final String PROPERTY_JRE_URL = ".jreURL"; //$NON-NLS-1$ 1529 static final String PROPERTY_OSGI_PROFILE = ".osgiProfile"; //$NON-NLS-1$ 1530 static final String PROPERTY_WHITE_LIST = ".whiteList"; //$NON-NLS-1$ 1531 1532 static { 1533 String[] ees = new String[] { "JRE-1.1", //$NON-NLS-1$ 1534 "J2SE-1.2", //$NON-NLS-1$ 1535 "J2SE-1.3", //$NON-NLS-1$ 1536 "J2SE-1.4", //$NON-NLS-1$ 1537 "J2SE-1.5", //$NON-NLS-1$ 1538 "JavaSE-1.6", //$NON-NLS-1$ 1539 "JavaSE-1.7", //$NON-NLS-1$ 1540 "JavaSE-1.8", //$NON-NLS-1$ 1541 "JavaSE-9", //$NON-NLS-1$ , 1542 "CDC-1.0_Foundation-1.0", //$NON-NLS-1$ 1543 "CDC-1.1_Foundation-1.1", //$NON-NLS-1$ 1544 "OSGi_Minimum-1.0", //$NON-NLS-1$ 1545 "OSGi_Minimum-1.1", //$NON-NLS-1$ 1546 "OSGi_Minimum-1.2" //$NON-NLS-1$ 1547 }; 1548 ACCEPTED_EEs = new TreeSet<>(); 1549 for (String ee : ees) { 1550 ACCEPTED_EEs.add(ee); 1551 } 1552 } 1553 private static String getAllEEValues() { 1554 StringBuilder buffer = new StringBuilder(); 1555 for (String ee : ACCEPTED_EEs) { 1556 if (buffer.length() != 0) { 1557 buffer.append(", "); //$NON-NLS-1$ 1558 } 1559 buffer.append(ee); 1560 } 1561 return String.valueOf(buffer); 1562 } 1563 1564 public static void main(String[] args) { 1565 EEGenerator generator = new EEGenerator(); 1566 generator.configure(args); 1567 if (!generator.isInitialized()) { 1568 System.err.println("Usage: -output <path to root to output files> -config <path to configuration file> -EEs <list of EE to generate separated with commas>"); //$NON-NLS-1$ 1569 return; 1570 } 1571 String property = System.getProperty("DEBUG"); //$NON-NLS-1$ 1572 DEBUG = (property != null) && "true".equalsIgnoreCase(property); //$NON-NLS-1$ 1573 generator.run(); 1574 } 1575 private ProfileInfo[] allProfiles; 1576 String configurationFile; 1577 1578 String[] EEToGenerate; 1579 1580 String output; 1581 1582 private boolean checkFileProperty(String property) { 1583 if (property == null) { 1584 return false; 1585 } 1586 File jreDocFile = new File(property); 1587 return jreDocFile.exists() && jreDocFile.isFile(); 1588 } 1589 1590 private boolean checkJREProperty(String property) { 1591 if (property == null) { 1592 return false; 1593 } 1594 File jreLibFolder = new File(property); 1595 return jreLibFolder.exists() && jreLibFolder.isDirectory(); 1596 } 1597 1598 private void configure(String[] args) { 1599 String currentArg = null; 1600 int argCount = args.length; 1601 int index = -1; 1602 final int DEFAULT = 0; 1603 final int OUTPUT = 1; 1604 final int CONFIG = 2; 1605 final int EEs = 3; 1606 int mode = DEFAULT; 1607 while (++index < argCount) { 1608 currentArg = args[index]; 1609 switch (mode) { 1610 case DEFAULT: 1611 if ("-output".equals(currentArg)) { //$NON-NLS-1$ 1612 if (this.output != null) 1613 { 1614 throw new IllegalArgumentException("output value is already set"); //$NON-NLS-1$ 1615 } 1616 mode = OUTPUT; 1617 continue; 1618 } 1619 if ("-config".equals(currentArg)) { //$NON-NLS-1$ 1620 if (this.configurationFile != null) 1621 { 1622 throw new IllegalArgumentException("configuration value is already set"); //$NON-NLS-1$ 1623 } 1624 mode = CONFIG; 1625 continue; 1626 } 1627 if ("-EEs".equals(currentArg)) { //$NON-NLS-1$ 1628 if (this.EEToGenerate != null) 1629 { 1630 throw new IllegalArgumentException("EEs value is already set"); //$NON-NLS-1$ 1631 } 1632 mode = EEs; 1633 continue; 1634 } 1635 // ignore unknown arguments - might be passed in by the 1636 // Eclipse application 1637 continue; 1638 case OUTPUT: 1639 this.output = currentArg; 1640 mode = DEFAULT; 1641 continue; 1642 case CONFIG: 1643 this.configurationFile = currentArg; 1644 mode = DEFAULT; 1645 continue; 1646 case EEs: 1647 String listOfEEs = currentArg; 1648 StringTokenizer tokenizer = new StringTokenizer(listOfEEs, ","); //$NON-NLS-1$ 1649 List<String> list = new ArrayList<>(); 1650 while (tokenizer.hasMoreTokens()) { 1651 String currentEE = tokenizer.nextToken().trim(); 1652 if (ACCEPTED_EEs.contains(currentEE)) { 1653 list.add(currentEE); 1654 } else { 1655 throw new IllegalArgumentException("Wrong EE value: " + currentEE + " accepted values are: " + getAllEEValues()); //$NON-NLS-1$ //$NON-NLS-2$ 1656 } 1657 } 1658 if (!list.isEmpty()) { 1659 list.toArray(this.EEToGenerate = new String[list.size()]); 1660 } 1661 mode = DEFAULT; 1662 continue; 1663 default: 1664 break; 1665 } 1666 } 1667 if (this.output == null) { 1668 throw new IllegalArgumentException("output value is missing"); //$NON-NLS-1$ 1669 } 1670 // check output 1671 File file = new File(this.output); 1672 if (!file.exists()) { 1673 if (!file.mkdirs()) { 1674 this.output = null; 1675 throw new IllegalArgumentException("Could not create the output dir"); //$NON-NLS-1$ 1676 } 1677 } 1678 // check configuration file 1679 File configuration = new File(this.configurationFile); 1680 if (!configuration.exists()) { 1681 this.configurationFile = null; 1682 throw new IllegalArgumentException("Configuration file doesn't exist"); //$NON-NLS-1$ 1683 } 1684 Properties properties = new Properties(); 1685 BufferedReader reader = null; 1686 try { 1687 reader = new BufferedReader(new FileReader(configuration)); 1688 properties.load(reader); 1689 } catch (IOException e) { 1690 e.printStackTrace(); 1691 throw new IllegalArgumentException("Could not properly initialize the properties"); //$NON-NLS-1$ 1692 } finally { 1693 if (reader != null) { 1694 try { 1695 reader.close(); 1696 } catch (IOException e) { 1697 // ignore 1698 } 1699 } 1700 } 1701 List<ProfileInfo> infos = new ArrayList<>(); 1702 for (String EE : EEToGenerate) { 1703 // Retrieve all properties for each EE 1704 // JRELIB, OSGI_PROFILE, JRE_DOC, JRE_URL, CACHE, WHITE_LIST 1705 String key = EE + PROPERTY_JRE_LIB; 1706 String jreLibProperty = properties.getProperty(key, null); 1707 if (!checkJREProperty(jreLibProperty)) { 1708 throw new IllegalArgumentException("Wrong property value : " + key); //$NON-NLS-1$ 1709 } 1710 key = EE + PROPERTY_CACHE_LOCATION; 1711 String cacheLocationProperty = properties.getProperty(key, null); 1712 if (cacheLocationProperty != null) { 1713 if (cacheLocationProperty.isEmpty()) { 1714 cacheLocationProperty = null; 1715 } 1716 } 1717 key = EE + PROPERTY_DOC_ROOT; 1718 String docRootProperty = properties.getProperty(key, ""); //$NON-NLS-1$ 1719 key = EE + PROPERTY_JRE_DOC; 1720 String jreDocProperty = properties.getProperty(key, null); 1721 if (jreDocProperty != null && !jreDocProperty.isEmpty()) { 1722 if (!checkFileProperty(jreDocProperty)) { 1723 throw new IllegalArgumentException("Wrong property value : " + key); //$NON-NLS-1$ 1724 } 1725 } else { 1726 jreDocProperty = null; 1727 } 1728 key = EE + PROPERTY_JRE_URL; 1729 String jreUrlProperty = properties.getProperty(key, null); 1730 if (jreUrlProperty != null && !jreUrlProperty.isEmpty()) { 1731 if (Util.getURLContents(jreUrlProperty + docRootProperty + "java/lang/Object.html") == null) { //$NON-NLS-1$ 1732 throw new IllegalArgumentException("Wrong property value : " + key); //$NON-NLS-1$ 1733 } 1734 } else { 1735 jreUrlProperty = null; 1736 } 1737 key = EE + PROPERTY_OSGI_PROFILE; 1738 String osgiProfileProperty = properties.getProperty(key, null); 1739 if (osgiProfileProperty != null && !osgiProfileProperty.isEmpty()) { 1740 if (!checkFileProperty(osgiProfileProperty)) { 1741 throw new IllegalArgumentException("Wrong property value : " + key); //$NON-NLS-1$ 1742 } 1743 } else { 1744 osgiProfileProperty = null; 1745 } 1746 key = EE + PROPERTY_WHITE_LIST; 1747 String whiteListProperty = properties.getProperty(key, null); 1748 if (whiteListProperty != null && !whiteListProperty.isEmpty()) { 1749 if (!checkFileProperty(whiteListProperty)) { 1750 throw new IllegalArgumentException("Wrong property value : " + key); //$NON-NLS-1$ 1751 } 1752 } else { 1753 whiteListProperty = null; 1754 } 1755 infos.add(ProfileInfo.getProfileInfo(EE, jreLibProperty, osgiProfileProperty, jreDocProperty, jreUrlProperty, docRootProperty, cacheLocationProperty, whiteListProperty)); 1756 } 1757 if (infos.isEmpty()) { 1758 throw new IllegalArgumentException("Profile infos cannot be empty"); //$NON-NLS-1$ 1759 } 1760 infos.toArray(allProfiles = new ProfileInfo[infos.size()]); 1761 } 1762 1763 private boolean isInitialized() { 1764 return this.configurationFile != null && this.EEToGenerate != null && this.output != null; 1765 } 1766 1767 private void run() { 1768 if (allProfiles == null) { 1769 System.err.println("No descriptions to generate"); //$NON-NLS-1$ 1770 return; 1771 } 1772 int numberOfProfiles = allProfiles.length; 1773 for (int i = 0; i < numberOfProfiles; i++) { 1774 ProfileInfo profileInfo = allProfiles[i]; 1775 if (profileInfo != null) { 1776 try { 1777 profileInfo.initializeData(); 1778 } catch (IOException e) { 1779 e.printStackTrace(); 1780 } 1781 // persist the EE description 1782 profileInfo.generateEEDescription(this.output); 1783 } 1784 } 1785 } 1786 } 1787