| /* |
| * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| package com.sun.classanalyzer; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.Map; |
| |
| import com.sun.classanalyzer.Module.Reference; |
| import java.util.LinkedList; |
| import java.util.TreeMap; |
| |
| /** |
| * |
| * @author Mandy Chung |
| */ |
| public abstract class AnnotatedDependency implements Comparable<AnnotatedDependency> { |
| |
| final Klass from; |
| final List<String> classes; |
| protected boolean optional; |
| String description; |
| Klass.Method method; |
| private List<Filter> filters = null; |
| |
| public AnnotatedDependency(Klass klass) { |
| this(klass, false); |
| } |
| |
| public AnnotatedDependency(Klass klass, boolean optional) { |
| this.from = klass; |
| this.classes = new ArrayList<String>(); |
| this.optional = optional; |
| } |
| |
| abstract String getTag(); |
| |
| abstract boolean isDynamic(); |
| |
| void setMethod(Klass.Method m) { |
| this.method = m; |
| } |
| |
| void addElement(String element, List<String> value) { |
| if (element.equals("value")) { |
| addValue(value); |
| } else if (element.equals("description")) { |
| description = value.get(0); |
| } else if (element.equals("optional")) { |
| optional = value.get(0).equals("1") || Boolean.parseBoolean(value.get(0)); |
| } |
| } |
| |
| void addValue(List<String> value) { |
| for (String s : value) { |
| if ((s = s.trim()).length() > 0) { |
| classes.add(s); |
| } |
| } |
| } |
| |
| List<String> getValue() { |
| return classes; |
| } |
| |
| boolean isOptional() { |
| return optional; |
| } |
| |
| boolean isEmpty() { |
| return classes.isEmpty(); |
| } |
| |
| boolean matches(String classname) { |
| synchronized (this) { |
| // initialize filters |
| if (filters == null) { |
| filters = new ArrayList<Filter>(); |
| for (String pattern : classes) { |
| filters.add(new Filter(pattern)); |
| } |
| |
| } |
| } |
| |
| for (Filter f : filters) { |
| if (f.matches(classname)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| for (String v : getValue()) { |
| if (sb.length() == 0) { |
| sb.append(getTag()); |
| sb.append("\n"); |
| } else { |
| sb.append("\n"); |
| } |
| sb.append(" "); |
| sb.append(from.getClassName()).append(" -> "); |
| sb.append(v); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public int compareTo(AnnotatedDependency o) { |
| if (from == o.from) { |
| if (this.getClass().getName().equals(o.getClass().getName())) { |
| String s1 = classes.isEmpty() ? "" : classes.get(0); |
| String s2 = o.classes.isEmpty() ? "" : o.classes.get(0); |
| return s1.compareTo(s2); |
| } else { |
| return this.getClass().getName().compareTo(o.getClass().getName()); |
| } |
| |
| } else { |
| return from.compareTo(o.from); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashcode = 7 + 73 * from.hashCode(); |
| for (String s : classes) { |
| hashcode ^= s.hashCode(); |
| } |
| return hashcode; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof AnnotatedDependency)) { |
| return false; |
| } |
| AnnotatedDependency other = (AnnotatedDependency) obj; |
| boolean ret = this.from.equals(other.from) && this.classes.size() == other.classes.size(); |
| if (ret == true) { |
| for (int i = 0; i < this.classes.size(); i++) { |
| ret = ret && this.classes.get(i).equals(other.classes.get(i)); |
| } |
| } |
| return ret; |
| } |
| |
| static class ClassForName extends AnnotatedDependency { |
| |
| public ClassForName(Klass klass, boolean optional) { |
| super(klass, optional); |
| } |
| |
| @Override |
| String getTag() { |
| if (this.optional) { |
| return TAG + "(optional)"; |
| } else { |
| return TAG; |
| } |
| } |
| |
| @Override |
| boolean isDynamic() { |
| return true; |
| } |
| static final String TYPE = "sun.annotation.ClassForName"; |
| static final String TAG = "@ClassForName"; |
| } |
| |
| static class NativeFindClass extends AnnotatedDependency { |
| |
| public NativeFindClass(Klass klass, boolean optional) { |
| super(klass, optional); |
| } |
| |
| @Override |
| String getTag() { |
| if (this.optional) { |
| return TAG + "(optional)"; |
| } else { |
| return TAG; |
| } |
| } |
| |
| @Override |
| boolean isDynamic() { |
| return true; |
| } |
| static final String TYPE = "sun.annotation.NativeFindClass"; |
| static final String TAG = "@NativeFindClass"; |
| } |
| |
| static class Provider extends AnnotatedDependency { |
| |
| private List<String> services = new ArrayList<String>(); |
| |
| Provider(Klass klass) { |
| super(klass, true); |
| } |
| |
| @Override |
| boolean isDynamic() { |
| return true; |
| } |
| |
| public List<String> services() { |
| return services; |
| } |
| |
| @Override |
| void addElement(String element, List<String> value) { |
| if (element.equals("service")) { |
| List<String> configFiles = new ArrayList<String>(); |
| for (String s : value) { |
| if ((s = s.trim()).length() > 0) { |
| configFiles.add(metaInfPath + s); |
| } |
| } |
| addValue(configFiles); |
| } |
| } |
| |
| @Override |
| void addValue(List<String> value) { |
| for (String s : value) { |
| if ((s = s.trim()).length() > 0) { |
| if (s.startsWith("META-INF")) { |
| services.add(s); |
| readServiceConfiguration(s, classes); |
| } else { |
| throw new RuntimeException("invalid value" + s); |
| } |
| } |
| } |
| } |
| |
| boolean isEmpty() { |
| return services.isEmpty(); |
| } |
| static final String metaInfPath = |
| "META-INF" + File.separator + "services" + File.separator; |
| |
| static void readServiceConfiguration(String config, List<String> names) { |
| BufferedReader br = null; |
| try { |
| InputStream is = ClassPath.open(config); |
| if (is != null) { |
| // Properties doesn't perserve the order of the input file |
| br = new BufferedReader(new InputStreamReader(is, "utf-8")); |
| int lc = 1; |
| while ((lc = parseLine(br, lc, names)) >= 0); |
| } |
| } catch (IOException ex) { |
| throw new RuntimeException(ex); |
| } finally { |
| if (br != null) { |
| try { |
| br.close(); |
| } catch (IOException ex) { |
| throw new RuntimeException(ex); |
| } |
| } |
| } |
| } |
| |
| // Parse a single line from the given configuration file, adding the name |
| // on the line to the names list. |
| // |
| private static int parseLine(BufferedReader r, int lc, List<String> names) throws IOException { |
| String ln = r.readLine(); |
| if (ln == null) { |
| return -1; |
| } |
| int ci = ln.indexOf('#'); |
| if (ci >= 0) { |
| ln = ln.substring(0, ci); |
| } |
| ln = ln.trim(); |
| int n = ln.length(); |
| if (n != 0) { |
| if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) { |
| throw new RuntimeException("Illegal configuration-file syntax"); |
| } |
| int cp = ln.codePointAt(0); |
| if (!Character.isJavaIdentifierStart(cp)) { |
| throw new RuntimeException("Illegal provider-class name: " + ln); |
| } |
| for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { |
| cp = ln.codePointAt(i); |
| if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { |
| throw new RuntimeException("Illegal provider-class name: " + ln); |
| } |
| } |
| if (!names.contains(ln)) { |
| names.add(ln); |
| } |
| } |
| return lc + 1; |
| } |
| |
| @Override |
| String getTag() { |
| return TAG; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (!(obj instanceof AnnotatedDependency)) { |
| return false; |
| } |
| Provider other = (Provider) obj; |
| boolean ret = this.from.equals(other.from) && |
| this.services.size() == other.services.size(); |
| if (ret == true) { |
| for (int i = 0; i < this.services.size(); i++) { |
| ret = ret && this.services.get(i).equals(other.services.get(i)); |
| } |
| } |
| return ret; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashcode = 7 + 73 * from.hashCode(); |
| for (String s : services) { |
| hashcode ^= s.hashCode(); |
| } |
| return hashcode; |
| } |
| |
| @Override |
| public List<String> getValue() { |
| List<String> result = new ArrayList<String>(); |
| result.addAll(services); |
| return result; |
| } |
| static final String TYPE = "sun.annotation.Provider"; |
| static final String TAG = "@Provider"; |
| } |
| |
| static class OptionalDependency extends AnnotatedDependency { |
| |
| static boolean isOptional(Klass from, Klass to) { |
| synchronized (OptionalDependency.class) { |
| if (optionalDepsMap == null) { |
| // Build a map of classes to its optional dependencies |
| initDependencies(); |
| } |
| } |
| for (Reference ref : optionalDepsMap.keySet()) { |
| if (ref.referrer() == from && ref.referree() == to) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| OptionalDependency(Klass klass) { |
| super(klass, true); |
| } |
| |
| @Override |
| boolean isDynamic() { |
| return false; |
| } |
| |
| @Override |
| String getTag() { |
| return TAG; |
| } |
| static final String TYPE = "sun.annotation.Optional"; |
| static final String TAG = "@Optional"; |
| } |
| |
| static class CompilerInline extends AnnotatedDependency { |
| |
| public CompilerInline(Klass klass) { |
| super(klass); |
| } |
| |
| @Override |
| String getTag() { |
| return TAG; |
| } |
| |
| @Override |
| boolean isDynamic() { |
| return false; |
| } |
| static final String TYPE = "sun.annotation.Inline"; |
| static final String TAG = "@Inline"; |
| } |
| |
| static class Filter { |
| |
| final String pattern; |
| final String regex; |
| |
| Filter(String pattern) { |
| this.pattern = pattern; |
| |
| boolean isRegex = false; |
| for (int i = 0; i < pattern.length(); i++) { |
| char p = pattern.charAt(i); |
| if (p == '*' || p == '[' || p == ']') { |
| isRegex = true; |
| break; |
| } |
| } |
| |
| if (isRegex) { |
| this.regex = convertToRegex(pattern); |
| } else { |
| this.regex = null; |
| } |
| } |
| |
| private String convertToRegex(String pattern) { |
| StringBuilder sb = new StringBuilder(); |
| int i = 0; |
| int index = 0; |
| int plen = pattern.length(); |
| while (i < plen) { |
| char p = pattern.charAt(i); |
| if (p == '*') { |
| sb.append("(").append(pattern.substring(index, i)).append(")"); |
| if (i + 1 < plen && pattern.charAt(i + 1) == '*') { |
| sb.append(".*"); |
| index = i + 2; |
| } else { |
| sb.append("[^\\.]*"); |
| index = i + 1; |
| } |
| } else if (p == '[') { |
| int j = i + 1; |
| while (j < plen) { |
| if (pattern.charAt(j) == ']') { |
| break; |
| } |
| j++; |
| } |
| if (j >= plen || pattern.charAt(j) != ']') { |
| throw new RuntimeException("Malformed pattern " + pattern); |
| } |
| sb.append("(").append(pattern.substring(index, i)).append(")"); |
| sb.append(pattern.substring(i, j + 1)); |
| index = j + 1; |
| i = j; |
| } |
| i++; |
| } |
| if (index < plen) { |
| sb.append("(").append(pattern.substring(index, plen)).append(")"); |
| } |
| return sb.toString(); |
| } |
| |
| boolean matches(String name) { |
| if (regex == null) { |
| // the pattern is not a regex |
| return name.equals(pattern); |
| } else { |
| return name.matches(regex); |
| } |
| } |
| } |
| |
| static boolean isValidType(String type) { |
| if (type.endsWith("(optional)")) { |
| int len = type.length() - "(optional)".length(); |
| type = type.substring(0, len); |
| } |
| return type.equals(ClassForName.TYPE) || type.equals(ClassForName.TAG) || |
| type.equals(NativeFindClass.TYPE) || type.equals(NativeFindClass.TAG) || |
| type.equals(Provider.TYPE) || type.equals(Provider.TAG) || |
| type.equals(CompilerInline.TYPE) || type.equals(CompilerInline.TAG) || |
| type.equals(OptionalDependency.TYPE) || type.equals(OptionalDependency.TAG); |
| } |
| |
| static AnnotatedDependency newAnnotatedDependency(String tag, String value, Klass klass) { |
| AnnotatedDependency dep = newAnnotatedDependency(tag, klass); |
| if (dep != null) { |
| dep.addValue(Collections.singletonList(value)); |
| } |
| return dep; |
| } |
| static List<AnnotatedDependency> annotatedDependencies = new LinkedList<AnnotatedDependency>(); |
| static List<AnnotatedDependency> optionalDependencies = new LinkedList<AnnotatedDependency>(); |
| |
| static AnnotatedDependency newAnnotatedDependency(String type, Klass klass) { |
| boolean optional = false; |
| if (type.endsWith("(optional)")) { |
| optional = true; |
| int len = type.length() - "(optional)".length(); |
| type = type.substring(0, len); |
| } |
| |
| if (type.equals(OptionalDependency.TYPE) || type.equals(OptionalDependency.TAG)) { |
| return newOptionalDependency(klass); |
| } |
| |
| AnnotatedDependency dep; |
| if (type.equals(ClassForName.TYPE) || type.equals(ClassForName.TAG)) { |
| dep = new ClassForName(klass, optional); |
| } else if (type.equals(NativeFindClass.TYPE) || type.equals(NativeFindClass.TAG)) { |
| dep = new NativeFindClass(klass, optional); |
| } else if (type.equals(Provider.TYPE) || type.equals(Provider.TAG)) { |
| dep = new Provider(klass); |
| } else if (type.equals(CompilerInline.TYPE) || type.equals(CompilerInline.TAG)) { |
| dep = new CompilerInline(klass); |
| } else { |
| return null; |
| } |
| klass.addAnnotatedDep(dep); |
| annotatedDependencies.add(dep); |
| return dep; |
| } |
| |
| static OptionalDependency newOptionalDependency(Klass klass) { |
| OptionalDependency dep = new OptionalDependency(klass); |
| optionalDependencies.add(dep); |
| return dep; |
| } |
| static Map<Reference, Set<AnnotatedDependency>> annotatedDepsMap = null; |
| static Map<Reference, Set<AnnotatedDependency>> optionalDepsMap = null; |
| |
| static Map<Reference, Set<AnnotatedDependency>> getReferences(Module m) { |
| // ensure it's initialized |
| initDependencies(); |
| |
| Map<Reference, Set<AnnotatedDependency>> result = new TreeMap<Reference, Set<AnnotatedDependency>>(); |
| for (Reference ref : annotatedDepsMap.keySet()) { |
| if (m.contains(ref.referrer()) && m.isModuleDependence(ref.referree())) { |
| result.put(ref, annotatedDepsMap.get(ref)); |
| } |
| } |
| return result; |
| } |
| |
| static Set<Module.Dependency> getDependencies(Module m) { |
| // ensure it's initialized |
| initDependencies(); |
| |
| Set<Module.Dependency> deps = new TreeSet<Module.Dependency>(); |
| for (Reference ref : annotatedDepsMap.keySet()) { |
| if (m.contains(ref.referrer())) { |
| Module other = m.getModuleDependence(ref.referree()); |
| if (other != null) { |
| for (AnnotatedDependency ad : annotatedDepsMap.get(ref)) { |
| Module.Dependency d = new Module.Dependency(other, ad.isOptional(), ad.isDynamic()); |
| deps.add(d); |
| } |
| } |
| } |
| } |
| return deps; |
| } |
| |
| synchronized static void initDependencies() { |
| if (annotatedDepsMap != null) { |
| return; |
| } |
| |
| // Build a map of references to its dependencies |
| annotatedDepsMap = new TreeMap<Reference, Set<AnnotatedDependency>>(); |
| optionalDepsMap = new TreeMap<Reference, Set<AnnotatedDependency>>(); |
| |
| for (Klass k : Klass.getAllClasses()) { |
| for (AnnotatedDependency ad : annotatedDependencies) { |
| if (ad.matches(k.getClassName())) { |
| Reference ref = new Reference(ad.from, k); |
| Set<AnnotatedDependency> set = annotatedDepsMap.get(ref); |
| if (set == null) { |
| set = new TreeSet<AnnotatedDependency>(); |
| annotatedDepsMap.put(ref, set); |
| } |
| set.add(ad); |
| } |
| } |
| |
| for (AnnotatedDependency ad : optionalDependencies) { |
| if (ad.matches(k.getClassName())) { |
| Reference ref = new Reference(ad.from, k); |
| Set<AnnotatedDependency> set = optionalDepsMap.get(ref); |
| if (set == null) { |
| set = new TreeSet<AnnotatedDependency>(); |
| optionalDepsMap.put(ref, set); |
| } |
| set.add(ad); |
| } |
| } |
| } |
| } |
| } |