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 }