1 /* 2 * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 package com.sun.classanalyzer; 25 26 import java.io.BufferedReader; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.InputStreamReader; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.TreeSet; 36 import java.util.Map; 37 38 import com.sun.classanalyzer.Module.Reference; 39 import com.sun.classanalyzer.ModuleInfo.Dependence; 40 import java.util.LinkedList; 41 import java.util.TreeMap; 42 43 /** 44 * 45 * @author Mandy Chung 46 */ 47 public abstract class AnnotatedDependency implements Comparable<AnnotatedDependency> { 48 49 final Klass from; 50 final List<String> classes; 51 protected boolean optional; 52 String description; 53 Klass.Method method; 54 private List<Filter> filters = null; 55 56 public AnnotatedDependency(Klass klass) { 57 this(klass, false); 58 } 59 60 public AnnotatedDependency(Klass klass, boolean optional) { 61 this.from = klass; 62 this.classes = new ArrayList<String>(); 63 this.optional = optional; 64 } 65 66 abstract String getTag(); 67 68 abstract boolean isDynamic(); 69 70 void setMethod(Klass.Method m) { 71 this.method = m; 72 } 73 74 void addElement(String element, List<String> value) { 75 if (element.equals("value")) { 76 addValue(value); 77 } else if (element.equals("description")) { 78 description = value.get(0); 79 } else if (element.equals("optional")) { 80 optional = value.get(0).equals("1") || Boolean.parseBoolean(value.get(0)); 81 } 82 } 83 84 void addValue(List<String> value) { 85 for (String s : value) { 86 if ((s = s.trim()).length() > 0) { 87 classes.add(s); 88 } 89 } 90 } 91 92 List<String> getValue() { 93 return classes; 94 } 95 96 boolean isOptional() { 97 return optional; 98 } 99 100 boolean isEmpty() { 101 return classes.isEmpty(); 102 } 103 104 boolean matches(String classname) { 105 synchronized (this) { 106 // initialize filters 107 if (filters == null) { 108 filters = new ArrayList<Filter>(); 109 for (String pattern : classes) { 110 filters.add(new Filter(pattern)); 111 } 112 113 } 114 } 115 116 for (Filter f : filters) { 117 if (f.matches(classname)) { 118 return true; 119 } 120 } 121 return false; 122 } 123 124 @Override 125 public String toString() { 126 StringBuilder sb = new StringBuilder(); 127 for (String v : getValue()) { 128 if (sb.length() == 0) { 129 sb.append(getTag()); 130 sb.append("\n"); 131 } else { 132 sb.append("\n"); 133 } 134 sb.append(" "); 135 sb.append(from.getClassName()).append(" -> "); 136 sb.append(v); 137 } 138 return sb.toString(); 139 } 140 141 @Override 142 public int compareTo(AnnotatedDependency o) { 143 if (from == o.from) { 144 if (this.getClass().getName().equals(o.getClass().getName())) { 145 String s1 = classes.isEmpty() ? "" : classes.get(0); 146 String s2 = o.classes.isEmpty() ? "" : o.classes.get(0); 147 return s1.compareTo(s2); 148 } else { 149 return this.getClass().getName().compareTo(o.getClass().getName()); 150 } 151 152 } else { 153 return from.compareTo(o.from); 154 } 155 } 156 157 @Override 158 public int hashCode() { 159 int hashcode = 7 + 73 * from.hashCode(); 160 for (String s : classes) { 161 hashcode ^= s.hashCode(); 162 } 163 return hashcode; 164 } 165 166 @Override 167 public boolean equals(Object obj) { 168 if (!(obj instanceof AnnotatedDependency)) { 169 return false; 170 } 171 AnnotatedDependency other = (AnnotatedDependency) obj; 172 boolean ret = this.from.equals(other.from) && this.classes.size() == other.classes.size(); 173 if (ret == true) { 174 for (int i = 0; i < this.classes.size(); i++) { 175 ret = ret && this.classes.get(i).equals(other.classes.get(i)); 176 } 177 } 178 return ret; 179 } 180 181 static class ClassForName extends AnnotatedDependency { 182 183 public ClassForName(Klass klass, boolean optional) { 184 // workaround: treat all Class.forName as optional 185 super(klass, true /* optional */); 186 } 187 188 @Override 189 String getTag() { 190 if (this.optional) { 191 return TAG + "(optional)"; 192 } else { 193 return TAG; 194 } 195 } 196 197 @Override 198 boolean isDynamic() { 199 return true; 200 } 201 static final String TYPE = "sun.annotation.ClassForName"; 202 static final String TAG = "@ClassForName"; 203 } 204 205 static class NativeFindClass extends AnnotatedDependency { 206 207 public NativeFindClass(Klass klass, boolean optional) { 208 super(klass, optional); 209 } 210 211 @Override 212 String getTag() { 213 if (this.optional) { 214 return TAG + "(optional)"; 215 } else { 216 return TAG; 217 } 218 } 219 220 @Override 221 boolean isDynamic() { 222 return true; 223 } 224 static final String TYPE = "sun.annotation.NativeFindClass"; 225 static final String TAG = "@NativeFindClass"; 226 } 227 228 static class Provider extends AnnotatedDependency { 229 230 private List<String> services = new ArrayList<String>(); 231 232 Provider(Klass klass) { 233 super(klass, true /* optional */); 234 } 235 236 @Override 237 boolean isDynamic() { 238 return true; 239 } 240 241 public List<String> services() { 242 return services; 243 } 244 245 @Override 246 void addElement(String element, List<String> value) { 247 if (element.equals("service")) { 248 List<String> configFiles = new ArrayList<String>(); 249 for (String s : value) { 250 if ((s = s.trim()).length() > 0) { 251 configFiles.add(metaInfPath + s); 252 } 253 } 254 addValue(configFiles); 255 } 256 } 257 258 @Override 259 void addValue(List<String> value) { 260 for (String s : value) { 261 if ((s = s.trim()).length() > 0) { 262 if (s.startsWith("META-INF")) { 263 services.add(s); 264 readServiceConfiguration(s, classes); 265 } else { 266 throw new RuntimeException("invalid value" + s); 267 } 268 } 269 } 270 } 271 272 boolean isEmpty() { 273 return services.isEmpty(); 274 } 275 static final String metaInfPath = 276 "META-INF" + File.separator + "services" + File.separator; 277 278 static void readServiceConfiguration(String config, List<String> names) { 279 BufferedReader br = null; 280 try { 281 InputStream is = ClassPaths.open(config); 282 if (is != null) { 283 // Properties doesn't perserve the order of the input file 284 br = new BufferedReader(new InputStreamReader(is, "utf-8")); 285 int lc = 1; 286 while ((lc = parseLine(br, lc, names)) >= 0); 287 } 288 } catch (IOException ex) { 289 throw new RuntimeException(ex); 290 } finally { 291 if (br != null) { 292 try { 293 br.close(); 294 } catch (IOException ex) { 295 throw new RuntimeException(ex); 296 } 297 } 298 } 299 } 300 301 // Parse a single line from the given configuration file, adding the name 302 // on the line to the names list. 303 // 304 private static int parseLine(BufferedReader r, int lc, List<String> names) throws IOException { 305 String ln = r.readLine(); 306 if (ln == null) { 307 return -1; 308 } 309 int ci = ln.indexOf('#'); 310 if (ci >= 0) { 311 ln = ln.substring(0, ci); 312 } 313 ln = ln.trim(); 314 int n = ln.length(); 315 if (n != 0) { 316 if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) { 317 throw new RuntimeException("Illegal configuration-file syntax"); 318 } 319 int cp = ln.codePointAt(0); 320 if (!Character.isJavaIdentifierStart(cp)) { 321 throw new RuntimeException("Illegal provider-class name: " + ln); 322 } 323 for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { 324 cp = ln.codePointAt(i); 325 if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { 326 throw new RuntimeException("Illegal provider-class name: " + ln); 327 } 328 } 329 if (!names.contains(ln)) { 330 names.add(ln); 331 } 332 } 333 return lc + 1; 334 } 335 336 @Override 337 String getTag() { 338 return TAG; 339 } 340 341 @Override 342 public boolean equals(Object obj) { 343 if (!(obj instanceof AnnotatedDependency)) { 344 return false; 345 } 346 Provider other = (Provider) obj; 347 boolean ret = this.from.equals(other.from) && 348 this.services.size() == other.services.size(); 349 if (ret == true) { 350 for (int i = 0; i < this.services.size(); i++) { 351 ret = ret && this.services.get(i).equals(other.services.get(i)); 352 } 353 } 354 return ret; 355 } 356 357 @Override 358 public int hashCode() { 359 int hashcode = 7 + 73 * from.hashCode(); 360 for (String s : services) { 361 hashcode ^= s.hashCode(); 362 } 363 return hashcode; 364 } 365 366 @Override 367 public List<String> getValue() { 368 List<String> result = new ArrayList<String>(); 369 result.addAll(services); 370 return result; 371 } 372 static final String TYPE = "sun.annotation.Provider"; 373 static final String TAG = "@Provider"; 374 } 375 376 static class OptionalDependency extends AnnotatedDependency { 377 378 static boolean isOptional(Klass from, Klass to) { 379 synchronized (OptionalDependency.class) { 380 if (optionalDepsMap == null) { 381 // Build a map of classes to its optional dependencies 382 initDependencies(); 383 } 384 } 385 for (Reference ref : optionalDepsMap.keySet()) { 386 if (ref.referrer() == from && ref.referree() == to) { 387 return true; 388 } 389 } 390 return false; 391 } 392 393 OptionalDependency(Klass klass) { 394 super(klass, true); 395 } 396 397 @Override 398 boolean isDynamic() { 399 return false; 400 } 401 402 @Override 403 String getTag() { 404 return TAG; 405 } 406 static final String TYPE = "sun.annotation.Optional"; 407 static final String TAG = "@Optional"; 408 } 409 410 static class CompilerInline extends AnnotatedDependency { 411 412 public CompilerInline(Klass klass) { 413 super(klass); 414 } 415 416 @Override 417 String getTag() { 418 return TAG; 419 } 420 421 @Override 422 boolean isDynamic() { 423 return false; 424 } 425 static final String TYPE = "sun.annotation.Inline"; 426 static final String TAG = "@Inline"; 427 } 428 429 static class Filter { 430 431 final String pattern; 432 final String regex; 433 434 Filter(String pattern) { 435 this.pattern = pattern; 436 437 boolean isRegex = false; 438 for (int i = 0; i < pattern.length(); i++) { 439 char p = pattern.charAt(i); 440 if (p == '*' || p == '[' || p == ']') { 441 isRegex = true; 442 break; 443 } 444 } 445 446 if (isRegex) { 447 this.regex = convertToRegex(pattern); 448 } else { 449 this.regex = null; 450 } 451 } 452 453 private String convertToRegex(String pattern) { 454 StringBuilder sb = new StringBuilder(); 455 int i = 0; 456 int index = 0; 457 int plen = pattern.length(); 458 while (i < plen) { 459 char p = pattern.charAt(i); 460 if (p == '*') { 461 sb.append("(").append(pattern.substring(index, i)).append(")"); 462 if (i + 1 < plen && pattern.charAt(i + 1) == '*') { 463 sb.append(".*"); 464 index = i + 2; 465 } else { 466 sb.append("[^\\.]*"); 467 index = i + 1; 468 } 469 } else if (p == '[') { 470 int j = i + 1; 471 while (j < plen) { 472 if (pattern.charAt(j) == ']') { 473 break; 474 } 475 j++; 476 } 477 if (j >= plen || pattern.charAt(j) != ']') { 478 throw new RuntimeException("Malformed pattern " + pattern); 479 } 480 sb.append("(").append(pattern.substring(index, i)).append(")"); 481 sb.append(pattern.substring(i, j + 1)); 482 index = j + 1; 483 i = j; 484 } 485 i++; 486 } 487 if (index < plen) { 488 sb.append("(").append(pattern.substring(index, plen)).append(")"); 489 } 490 return sb.toString(); 491 } 492 493 boolean matches(String name) { 494 if (regex == null) { 495 // the pattern is not a regex 496 return name.equals(pattern); 497 } else { 498 return name.matches(regex); 499 } 500 } 501 } 502 503 static boolean isValidType(String type) { 504 if (type.endsWith("(optional)")) { 505 int len = type.length() - "(optional)".length(); 506 type = type.substring(0, len); 507 } 508 return type.equals(ClassForName.TYPE) || type.equals(ClassForName.TAG) || 509 type.equals(NativeFindClass.TYPE) || type.equals(NativeFindClass.TAG) || 510 type.equals(Provider.TYPE) || type.equals(Provider.TAG) || 511 type.equals(CompilerInline.TYPE) || type.equals(CompilerInline.TAG) || 512 type.equals(OptionalDependency.TYPE) || type.equals(OptionalDependency.TAG); 513 } 514 515 static AnnotatedDependency newAnnotatedDependency(String tag, String value, Klass klass) { 516 AnnotatedDependency dep = newAnnotatedDependency(tag, klass); 517 if (dep != null) { 518 dep.addValue(Collections.singletonList(value)); 519 } 520 return dep; 521 } 522 static List<AnnotatedDependency> annotatedDependencies = new LinkedList<AnnotatedDependency>(); 523 static List<AnnotatedDependency> optionalDependencies = new LinkedList<AnnotatedDependency>(); 524 525 static AnnotatedDependency newAnnotatedDependency(String type, Klass klass) { 526 boolean optional = false; 527 if (type.endsWith("(optional)")) { 528 optional = true; 529 int len = type.length() - "(optional)".length(); 530 type = type.substring(0, len); 531 } 532 533 if (type.equals(OptionalDependency.TYPE) || type.equals(OptionalDependency.TAG)) { 534 return newOptionalDependency(klass); 535 } 536 537 AnnotatedDependency dep; 538 if (type.equals(ClassForName.TYPE) || type.equals(ClassForName.TAG)) { 539 dep = new ClassForName(klass, optional); 540 } else if (type.equals(NativeFindClass.TYPE) || type.equals(NativeFindClass.TAG)) { 541 dep = new NativeFindClass(klass, optional); 542 } else if (type.equals(Provider.TYPE) || type.equals(Provider.TAG)) { 543 dep = new Provider(klass); 544 } else if (type.equals(CompilerInline.TYPE) || type.equals(CompilerInline.TAG)) { 545 dep = new CompilerInline(klass); 546 } else { 547 return null; 548 } 549 klass.addAnnotatedDep(dep); 550 annotatedDependencies.add(dep); 551 return dep; 552 } 553 554 static OptionalDependency newOptionalDependency(Klass klass) { 555 OptionalDependency dep = new OptionalDependency(klass); 556 optionalDependencies.add(dep); 557 return dep; 558 } 559 static Map<Reference, Set<AnnotatedDependency>> annotatedDepsMap = null; 560 static Map<Reference, Set<AnnotatedDependency>> optionalDepsMap = null; 561 562 static Map<Reference, Set<AnnotatedDependency>> getReferences(Module m) { 563 // ensure it's initialized 564 initDependencies(); 565 566 Map<Reference, Set<AnnotatedDependency>> result = new TreeMap<Reference, Set<AnnotatedDependency>>(); 567 for (Reference ref : annotatedDepsMap.keySet()) { 568 if (m.contains(ref.referrer()) && m.isModuleDependence(ref.referree())) { 569 result.put(ref, annotatedDepsMap.get(ref)); 570 } 571 } 572 return result; 573 } 574 575 static Set<Dependence> getDependencies(Module m) { 576 // ensure it's initialized 577 initDependencies(); 578 579 Set<Dependence> deps = new TreeSet<Dependence>(); 580 for (Reference ref : annotatedDepsMap.keySet()) { 581 if (m.contains(ref.referrer())) { 582 Module other = m.getModuleDependence(ref.referree()); 583 if (other != null) { 584 for (AnnotatedDependency ad : annotatedDepsMap.get(ref)) { 585 Dependence d = new Dependence(other, ad.isOptional()); 586 deps.add(d); 587 } 588 } 589 } 590 } 591 return deps; 592 } 593 594 synchronized static void initDependencies() { 595 if (annotatedDepsMap != null) { 596 return; 597 } 598 599 // Build a map of references to its dependencies 600 annotatedDepsMap = new TreeMap<Reference, Set<AnnotatedDependency>>(); 601 optionalDepsMap = new TreeMap<Reference, Set<AnnotatedDependency>>(); 602 603 for (Klass k : Klass.getAllClasses()) { 604 for (AnnotatedDependency ad : annotatedDependencies) { 605 if (ad.matches(k.getClassName())) { 606 Reference ref = new Reference(ad.from, k); 607 Set<AnnotatedDependency> set = annotatedDepsMap.get(ref); 608 if (set == null) { 609 set = new TreeSet<AnnotatedDependency>(); 610 annotatedDepsMap.put(ref, set); 611 } 612 set.add(ad); 613 } 614 } 615 616 for (AnnotatedDependency ad : optionalDependencies) { 617 if (ad.matches(k.getClassName())) { 618 Reference ref = new Reference(ad.from, k); 619 Set<AnnotatedDependency> set = optionalDepsMap.get(ref); 620 if (set == null) { 621 set = new TreeSet<AnnotatedDependency>(); 622 optionalDepsMap.put(ref, set); 623 } 624 set.add(ad); 625 } 626 } 627 } 628 } 629 }