1 /*
   2  * Copyright (c) 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 package com.sun.tools.jdeps;
  26 
  27 import java.util.Collections;
  28 import java.util.HashMap;
  29 import java.util.HashSet;
  30 import java.util.Map;
  31 import java.util.Objects;
  32 import java.util.Set;
  33 
  34 /**
  35  * JDeps internal representation of module for dependency analysis.
  36  */
  37 final class Module extends Archive {
  38     private final String moduleName;
  39     private final Map<String, Boolean> requires;
  40     private final Map<String, Set<String>> exports;
  41     private final Set<String> packages;
  42 
  43     private Module(ClassFileReader reader, String name,
  44                    Map<String, Boolean> requires,
  45                    Map<String, Set<String>> exports,
  46                    Set<String> packages) {
  47         super(name, reader);
  48         this.moduleName = name;
  49         this.requires = Collections.unmodifiableMap(requires);
  50         this.exports = Collections.unmodifiableMap(exports);
  51         this.packages = Collections.unmodifiableSet(packages);
  52     }
  53 
  54     public String name() {
  55         return moduleName;
  56     }
  57 
  58     public Map<String, Boolean> requires() {
  59         return requires;
  60     }
  61 
  62     public Map<String, Set<String>> exports() {
  63         return exports;
  64     }
  65 
  66     public Set<String> packages() {
  67         return packages;
  68     }
  69 
  70     /**
  71      * Tests if this module can read m
  72      */
  73     public boolean canRead(Module m) {
  74         // ## TODO: handle "re-exported=true"
  75         // all JDK modules require all modules containing its direct dependences
  76         // should not be an issue
  77         return requires.containsKey(m.name());
  78     }
  79 
  80     /**
  81      * Tests if a given fully-qualified name is an exported type.
  82      */
  83     public boolean isExported(String cn) {
  84         int i = cn.lastIndexOf('.');
  85         String pn = i > 0 ? cn.substring(0, i) : "";
  86 
  87         return isExportedPackage(pn);
  88     }
  89 
  90     /**
  91      * Tests if a given package name is exported.
  92      */
  93     public boolean isExportedPackage(String pn) {
  94         return exports.containsKey(pn) ? exports.get(pn).isEmpty() : false;
  95     }
  96 
  97     /**
  98      * Tests if the given classname is accessible to module m
  99      */
 100     public boolean isAccessibleTo(String classname, Module m) {
 101         int i = classname.lastIndexOf('.');
 102         String pn = i > 0 ? classname.substring(0, i) : "";
 103         if (!packages.contains(pn)) {
 104             throw new IllegalArgumentException(classname + " is not a member of module " + name());
 105         }
 106 
 107         if (m != null && !m.canRead(this)) {
 108             trace("%s not readable by %s%n", this.name(), m.name());
 109             return false;
 110         }
 111 
 112         // exported API
 113         Set<String> ms = exports().get(pn);
 114         String mname = m != null ? m.name() : "unnamed";
 115         if (ms == null) {
 116             trace("%s not exported in %s%n", classname, this.name());
 117         } else if (!(ms.isEmpty() || ms.contains(mname))) {
 118             trace("%s not permit to %s %s%n", classname, mname, ms);
 119         }
 120         return ms != null && (ms.isEmpty() || ms.contains(mname));
 121     }
 122 
 123     private static final boolean traceOn = Boolean.getBoolean("jdeps.debug");
 124     private void trace(String fmt, Object... args) {
 125         if (traceOn) {
 126             System.err.format(fmt, args);
 127         }
 128     }
 129 
 130     @Override
 131     public boolean equals(Object ob) {
 132         if (!(ob instanceof Module))
 133             return false;
 134         Module that = (Module)ob;
 135         return (moduleName.equals(that.moduleName)
 136                 && requires.equals(that.requires)
 137                 && exports.equals(that.exports)
 138                 && packages.equals(that.packages));
 139     }
 140 
 141     @Override
 142     public int hashCode() {
 143         int hc = moduleName.hashCode();
 144         hc = hc * 43 + requires.hashCode();
 145         hc = hc * 43 + exports.hashCode();
 146         hc = hc * 43 + packages.hashCode();
 147         return hc;
 148     }
 149 
 150     @Override
 151     public String toString() {
 152         return name();
 153     }
 154 
 155     public final static class Builder {
 156         String name;
 157         ClassFileReader reader;
 158         final Map<String, Boolean> requires = new HashMap<>();
 159         final Map<String, Set<String>> exports = new HashMap<>();
 160         final Set<String> packages = new HashSet<>();
 161 
 162         public Builder() {
 163         }
 164 
 165         public Builder name(String n) {
 166             name = n;
 167             return this;
 168         }
 169 
 170         public Builder require(String d, boolean reexport) {
 171          //   System.err.format("%s depend %s reexports %s%n", name, d, reexport);
 172             requires.put(d, reexport);
 173             return this;
 174         }
 175 
 176         public Builder packages(Set<String> pkgs) {
 177             packages.addAll(pkgs);
 178             return this;
 179         }
 180 
 181         public Builder export(String p, Set<String> ms) {
 182             Objects.requireNonNull(p);
 183             Objects.requireNonNull(ms);
 184             exports.put(p, new HashSet<>(ms));
 185             return this;
 186         }
 187         public Builder classes(ClassFileReader.ModuleClassReader reader) {
 188             this.reader = reader;
 189             return this;
 190         }
 191 
 192         public Module build() {
 193             Module m = new Module(reader, name, requires, exports, packages);
 194             return m;
 195         }
 196     }
 197 }