1 /*
   2  * Copyright (c) 1999, 2014, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.sjavac.comp;
  27 
  28 import javax.lang.model.element.Element;
  29 import javax.lang.model.element.TypeElement;
  30 import java.util.Arrays;
  31 import java.util.ArrayList;
  32 import java.util.Comparator;
  33 import java.util.List;
  34 import java.util.LinkedList;
  35 import java.util.HashMap;
  36 import java.util.HashSet;
  37 import java.util.Map;
  38 import java.util.Set;
  39 
  40 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  41 import com.sun.tools.javac.util.Context;
  42 import com.sun.tools.javac.util.Log;
  43 import com.sun.tools.javac.util.Name;
  44 
  45 /** Utility class containing dependency information between packages
  46  *  and the pubapi for a package.
  47  *
  48  * <p><b>This is NOT part of any supported API.
  49  * If you write code that depends on this, you do so at your own
  50  * risk.  This code and its internal interfaces are subject to change
  51  * or deletion without notice.</b></p>
  52  */
  53 public class Dependencies {
  54     protected static final Context.Key<Dependencies> dependenciesKey = new Context.Key<>();
  55 
  56     // The log to be used for error reporting.
  57     protected Log log;
  58     // Map from package name to packages that the package depends upon.
  59     protected Map<Name,Set<Name>> deps;
  60     // Map from package name to non-sourcefile classes that the package depends upon.
  61     protected Map<Name,Set<ClassSymbol>> classDeps;
  62     // This is the set of all packages that are supplied
  63     // through the java files at the command line.
  64     protected Set<Name> explicitPackages;
  65 
  66     // This is the set of all classes found outside of the source, ie the classpath.
  67     protected Set<Name> classpathClasses;
  68 
  69     // Map from a class name to its public api.
  70     protected Map<Name,List<String>> publicApiPerClass;
  71 
  72     public static Dependencies instance(Context context) {
  73         Dependencies instance = context.get(dependenciesKey);
  74         if (instance == null)
  75             instance = new Dependencies(context);
  76         return instance;
  77     }
  78 
  79     private Dependencies(Context context) {
  80         context.put(dependenciesKey, this);
  81         log = Log.instance(context);
  82         deps = new HashMap<>();
  83         classDeps = new HashMap<>();
  84         explicitPackages = new HashSet<>();
  85         publicApiPerClass = new HashMap<>();
  86     }
  87 
  88     /**
  89      * Fetch the set of dependencies that are relevant to the compile
  90      * that has just been performed. I.e. we are only interested in
  91      * dependencies for classes that were explicitly compiled.
  92      * @return
  93      */
  94     public Map<String,Set<String>> getSourcefileDependencies() {
  95         Map<String,Set<String>> new_deps = new HashMap<>();
  96         for (Name pkg : explicitPackages) {
  97             Set<Name> set = deps.get(pkg);
  98             if (set != null) {
  99                 String pkg_name = pkg.toString();
 100                 Set<String> new_set = new_deps.get(pkg_name);
 101                 if (new_set == null) {
 102                     new_set = new HashSet<>();
 103                     // Modules beware....
 104                     new_deps.put(":"+pkg_name, new_set);
 105                 }
 106                 for (Name d : set) {
 107                     new_set.add(":"+d.toString());
 108                 }
 109             }
 110         }
 111         return new_deps;
 112     }
 113 
 114     /**
 115      * Fetch the set of classpath dependencies that our sources depend upon.
 116      * @return
 117      */
 118     public Map<String,Set<String>> getClasspathDependencies() {
 119         Map<String,Set<String>> new_deps = new HashMap<>();
 120 
 121         for (Name pkg : classDeps.keySet()) {
 122             if (explicitPackages.contains(pkg)) {
 123                 continue;
 124             }
 125             Set<ClassSymbol> set = classDeps.get(pkg);
 126             String pkg_name = pkg.toString();
 127             Set<String> new_set = new_deps.get(pkg_name);
 128             if (new_set == null) {
 129                 new_set = new HashSet<>();
 130                 // Modules beware....
 131                 new_deps.put(":"+pkg_name, new_set);
 132             }
 133             for (ClassSymbol c : set) {
 134                 new_set.add(":. "+c.fullname);
 135             }
 136         }
 137         return new_deps;
 138     }
 139 
 140     /**
 141      * Convert the map from class names to their pubapi to a map
 142      * from package names to their pubapi (which is the sorted concatenation
 143      * of all the class pubapis)
 144      */
 145     public Map<String,List<String>> getPublicApis() {
 146         // The result map, to be returned.
 147         Map<String,List<String>> publicApiPerPackage = new HashMap<>();
 148         // Remember the Name for the sortable String version of the name.
 149         // I.e. where the dot (.) before the class name is replaced with bang (!).
 150         Map<String,Name> backToName = new HashMap<>();
 151         // Sort all the classes on their fullname that includes the package path.
 152         // Thus all classes belonging to the same package will be in consecutive order.
 153         Name[] names = publicApiPerClass.keySet().toArray(new Name[0]);
 154         List<String> fullnames = new ArrayList<>();
 155         for (Name n : names) {
 156             String tmp = n.toString();
 157             int p = tmp.lastIndexOf('.');
 158             String s = tmp.substring(0,p)+"!"+tmp.substring(p+1);
 159             fullnames.add(s);
 160             backToName.put(s, n);
 161             }
 162         String[] sorted_fullnames = fullnames.toArray(new String[0]);
 163         Arrays.sort(sorted_fullnames);
 164         // Now sorted_fullnames has a list of classes sorted, but with all classes inside
 165         // a package grouped together. This would not happen if we did not use !.
 166         String currPkg = "";
 167         List<String> currPublicApi = null;
 168 
 169         for (String n : sorted_fullnames) {
 170             int lastBang = n.lastIndexOf('!');
 171             assert(lastBang != -1);
 172             String pkgName = n.substring(0, lastBang);
 173             if (!pkgName.equals(currPkg)) {
 174                 if (!currPkg.equals("")) {
 175                     // Add default module name ":"
 176                     publicApiPerPackage.put(":"+currPkg, currPublicApi);
 177                 }
 178                 currPublicApi = new LinkedList<>();
 179                 currPkg = pkgName;
 180             }
 181             currPublicApi.addAll(publicApiPerClass.get(backToName.get(n)));
 182         }
 183         if (currPkg != "" && currPublicApi != null) {
 184             publicApiPerPackage.put(":"+currPkg, currPublicApi);
 185         }
 186         return publicApiPerPackage;
 187     }
 188 
 189     /**
 190      * Visit the api of a source class and construct a pubapi string and
 191      * store it into the pubapi_perclass map.
 192      */
 193     public void visitPubapiOfSource(TypeElement e) {
 194         visitPubapi(e);
 195         Name p = ((ClassSymbol)e).packge().fullname;
 196         explicitPackages.add(p);
 197      }
 198 
 199     /**
 200      * Visit the api of a classpath class and construct a pubapi string and
 201      * store it into the pubapi_perclass map.
 202      */
 203     public void visitPubapiOfClasspath(TypeElement e) {
 204         visitPubapi(e);
 205     }
 206 
 207     /**
 208      * Visit the api of a class and construct a list of api strings and
 209      * store it into the pubapi_perclass map.
 210      */
 211     private void visitPubapi(TypeElement e) {
 212         Name n = ((ClassSymbol)e).fullname;
 213         assert(publicApiPerClass.get(n) == null);
 214 
 215         PubapiVisitor v = new PubapiVisitor();
 216         v.construct(e);
 217         publicApiPerClass.put(n, v.api);
 218     }
 219     
 220     /**
 221      * Visit the api of a class and return the constructed pubapi string.
 222      */
 223     public static List<String> constructPubapi(TypeElement e, String class_loc_info) {
 224 
 225         PubapiVisitor v = new PubapiVisitor();
 226         if (class_loc_info != null) {
 227             v.classLocInfo(class_loc_info);
 228         }
 229         v.construct(e);
 230         return v.api;
 231     }
 232 
 233     /**
 234      * Collect a package dependency. currPkg is marked as depending on depPkg.
 235      */
 236     public void reportPackageDep(Name currPkg, Name depPkg) {
 237         if (!currPkg.equals(depPkg)) {
 238             Set<Name> theset = deps.get(currPkg);
 239             if (theset==null) {
 240                 theset = new HashSet<>();
 241                 deps.put(currPkg, theset);
 242             }
 243             theset.add(depPkg);
 244         }
 245     }
 246 
 247     /**
 248      * Collect a classpath class dependency. currPkg is marked as depending on depCls.
 249      */
 250     public void reportClassDep(ClassSymbol depCls) {
 251         String s = depCls.classfile != null ? depCls.classfile.toString() : ""; 
 252         if (s.startsWith("RegularFileObject[") && 
 253             s.endsWith(".java]")) {
 254             // This was a sourcepath dependency, ignore it.
 255             return;
 256         }
 257         Name pkg = depCls.packge().fullname;
 258         Set<ClassSymbol> theset = classDeps.get(pkg);
 259         if (theset==null) {
 260             theset = new HashSet<>();
 261             classDeps.put(pkg, theset);
 262         }
 263         theset.add(depCls);
 264     }
 265 
 266 }