1 /* 2 * Copyright (c) 2010, 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 package com.sun.classanalyzer; 24 25 import com.sun.classanalyzer.AnnotatedDependency.OptionalDependency; 26 import com.sun.classanalyzer.Module.ModuleVisitor; 27 import com.sun.classanalyzer.ModuleInfo.Dependence; 28 import com.sun.classanalyzer.ModuleInfo.PackageInfo; 29 import static com.sun.classanalyzer.ModuleInfo.Dependence.Modifier.*; 30 import java.io.File; 31 import java.io.IOException; 32 import java.util.*; 33 34 /** 35 * Module builder that creates modules as defined in the given 36 * module configuration files. The run() method assigns 37 * all classes and resources according to the module definitions. 38 * Additional dependency information can be specified e.g. 39 * Class.forName, JNI_FindClass, and service providers. 40 * 41 * @see DependencyConfig 42 * @author mchung 43 */ 44 public class ModuleBuilder { 45 46 private final List<String> depConfigs = new ArrayList<String>(); 47 private final Map<Module, ModuleInfo> moduleinfos = 48 new LinkedHashMap<Module, ModuleInfo>(); 49 private final String version; 50 private final boolean mergeModules; 51 52 public ModuleBuilder(List<String> configs, String version) 53 throws IOException { 54 this(configs, null, true, version); 55 } 56 57 public ModuleBuilder(List<String> configs, 58 List<String> depconfigs, 59 boolean merge, 60 String version) 61 throws IOException { 62 if (configs != null) { 63 // create modules based on the input config files 64 for (String file : configs) { 65 for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) { 66 newModule(mconfig); 67 } 68 } 69 } 70 if (depconfigs != null) { 71 this.depConfigs.addAll(depconfigs); 72 } 73 this.mergeModules = merge; 74 this.version = version; 75 } 76 77 /** 78 * Returns a module of a given name with no main entry point. 79 */ 80 public Module newModule(String name) throws IOException { 81 return newModule(new ModuleConfig(name, null)); 82 } 83 84 /** 85 * Returns a module of a given ModuleConfig. 86 */ 87 public Module newModule(ModuleConfig mconfig) { 88 return Module.addModule(mconfig); 89 } 90 91 /** 92 * Loads modules from the .classlist and .resources files in 93 * the given classListDir. 94 * 95 */ 96 public Set<Module> loadModulesFrom(File classlistDir) throws IOException { 97 ClassListReader reader = new ClassListReader(this); 98 return reader.loadModulesFrom(classlistDir); 99 } 100 101 /** 102 * This method assigns the classes and resource files 103 * to modules and generates the package information and 104 * the module information. 105 * 106 * This method can be overridden in a subclass implementation. 107 */ 108 public void run() throws IOException { 109 // assign classes and resource files to the modules and 110 // group fine-grained modules per configuration files 111 buildModules(); 112 113 // generate package information 114 buildPackageInfos(); 115 116 // analyze cross-module dependencies and generate ModuleInfo 117 buildModuleInfos(); 118 } 119 120 /** 121 * Returns the resulting top-level, non-empty modules. 122 */ 123 public Set<Module> getModules() { 124 return moduleinfos.keySet(); 125 } 126 127 /** 128 * Builds modules from the existing list of classes and resource 129 * files according to the module configuration files. 130 * 131 */ 132 protected void buildModules() throws IOException { 133 // Add additional dependencies after classes are added to the modules 134 DependencyConfig.parse(depConfigs); 135 136 // process the roots and dependencies to get the classes for each module 137 Collection<Module> modules = Module.getAllModules(); 138 for (Module m : modules) { 139 m.processRootsAndReferences(); 140 } 141 142 if (mergeModules) { 143 // group fine-grained modules 144 Module.buildModuleMembers(); 145 } 146 } 147 148 /** 149 * Build ModuleInfo for the top level modules. 150 */ 151 protected void buildModuleInfos() { 152 // backedges (i.e. reverse dependences) 153 Map<Module, Set<Module>> backedges = new TreeMap<Module, Set<Module>>(); 154 155 // analyze the module's dependences and create ModuleInfo 156 for (Module m : Module.getAllModules()) { 157 if (m.isTopLevel()) { 158 ModuleInfo mi = buildModuleInfo(m); 159 m.setModuleInfo(mi); 160 moduleinfos.put(m, mi); 161 // keep track of the backedges 162 for (Dependence d : mi.requires()) { 163 // only add the top level modules 164 Module dep = d.getModule(); 165 Set<Module> set = backedges.get(dep); 166 if (set == null) { 167 set = new TreeSet<Module>(); 168 backedges.put(dep, set); 169 } 170 set.add(m); 171 } 172 } 173 } 174 175 // fixup permits after all ModuleInfo are created in two passes: 176 // 1. permits the requesting module if it requires local dependence 177 // 2. if permits set is non-empty, permits 178 // all of its requesting modules 179 for (ModuleInfo mi : moduleinfos.values()) { 180 for (Dependence d : mi.requires()) { 181 if (d.isLocal()) { 182 Module dm = d.getModule(); 183 moduleinfos.get(dm).addPermit(mi.getModule()); 184 } 185 } 186 } 187 188 for (Map.Entry<Module, Set<Module>> e : backedges.entrySet()) { 189 Module dm = e.getKey(); 190 ModuleInfo dmi = moduleinfos.get(dm); 191 if (dmi == null) { 192 throw new RuntimeException(dm + " null moduleinfo"); 193 } 194 if (dmi.permits().size() > 0) { 195 for (Module m : e.getValue()) { 196 dmi.addPermit(m); 197 } 198 } 199 } 200 } 201 202 // module to packages 203 private final Map<Module, Set<PackageInfo>> packagesForModule = 204 new TreeMap<Module, Set<PackageInfo>>(); 205 // package name to PackageInfo set 206 private final Map<String, Set<PackageInfo>> packages = 207 new TreeMap<String, Set<PackageInfo>>(); 208 // module with split packages 209 private final Map<Module, Set<PackageInfo>> modulesWithSplitPackage = 210 new TreeMap<Module, Set<PackageInfo>>(); 211 212 /** 213 * Builds PackageInfo for each top level module. 214 */ 215 protected void buildPackageInfos() { 216 for (Module m : Module.getAllModules()) { 217 if (m.isTopLevel()) { 218 Set<PackageInfo> pkgs = getPackageInfos(m); 219 packagesForModule.put(m, pkgs); 220 } 221 } 222 223 for (Map.Entry<Module, Set<PackageInfo>> e : packagesForModule.entrySet()) { 224 Module m = e.getKey(); 225 for (PackageInfo p : e.getValue()) { 226 Set<PackageInfo> set = packages.get(p.pkgName); 227 if (set == null) { 228 set = new TreeSet<PackageInfo>(); 229 packages.put(p.pkgName, set); 230 } 231 set.add(p); 232 } 233 } 234 235 for (Map.Entry<String, Set<PackageInfo>> e : packages.entrySet()) { 236 String pkg = e.getKey(); 237 if (e.getValue().size() > 1) { 238 for (PackageInfo pi : e.getValue()) { 239 Set<PackageInfo> set = modulesWithSplitPackage.get(pi.module); 240 if (set == null) { 241 set = new TreeSet<PackageInfo>(); 242 modulesWithSplitPackage.put(pi.module, set); 243 } 244 set.add(pi); 245 } 246 } 247 } 248 } 249 250 public Map<String, Set<Module>> getSplitPackages() { 251 Map<String, Set<Module>> result = new LinkedHashMap<String, Set<Module>>(); 252 for (Map.Entry<String, Set<PackageInfo>> e : packages.entrySet()) { 253 String pkg = e.getKey(); 254 if (e.getValue().size() > 1) { 255 for (PackageInfo pi : e.getValue()) { 256 Set<Module> set = result.get(pkg); 257 if (set == null) { 258 set = new TreeSet<Module>(); 259 result.put(pkg, set); 260 } 261 set.add(pi.module); 262 } 263 } 264 } 265 return result; 266 } 267 268 private Set<PackageInfo> getPackageInfos(final Module m) { 269 Map<String, PackageInfo> packages = new TreeMap<String, PackageInfo>(); 270 Module.Visitor<Void, Map<String, PackageInfo>> visitor = 271 new Module.Visitor<Void, Map<String, PackageInfo>>() { 272 273 @Override 274 public Void visitClass(Klass k, Map<String, PackageInfo> packages) { 275 // update package statistics 276 String pkg = k.getPackageName(); 277 PackageInfo pkginfo = packages.get(pkg); 278 if (pkginfo == null) { 279 pkginfo = new PackageInfo(m, pkg); 280 packages.put(pkg, pkginfo); 281 } 282 283 if (k.exists()) { 284 // only count the class that is parsed 285 pkginfo.add(k.getFileSize()); 286 } 287 return null; 288 } 289 290 @Override 291 public Void visitResource(ResourceFile r, Map<String, PackageInfo> packages) { 292 // nop 293 return null; 294 } 295 }; 296 297 m.visit(visitor, packages); 298 return new TreeSet<PackageInfo>(packages.values()); 299 } 300 301 private ModuleInfo buildModuleInfo(Module m) { 302 Map<Module, Dependence> requires = new LinkedHashMap<Module, Dependence>(); 303 Set<Module> permits = new TreeSet<Module>(); 304 305 // add static dependences 306 for (Klass from : m.classes()) { 307 for (Klass to : from.getReferencedClasses()) { 308 if (m.isModuleDependence(to)) { 309 // is this dependence overridden as optional? 310 boolean optional = OptionalDependency.isOptional(from, to); 311 addDependence(requires, new Dependence(from, to, optional)); 312 } 313 } 314 } 315 316 // add dependency due to the main class 317 Klass k = m.mainClass(); 318 if (k != null && m.isModuleDependence(k)) { 319 addDependence(requires, new Dependence(k.getModule())); 320 } 321 322 // add requires and permits specified in the config files 323 processModuleConfigs(m, requires, permits); 324 325 // add dependencies due to the AnnotatedDependency 326 for (Dependence d : AnnotatedDependency.getDependencies(m)) { 327 addDependence(requires, d); 328 } 329 330 // Add LOCAL to the dependence and permits will be added 331 // in the separate phase 332 Set<PackageInfo> splitPkgs = modulesWithSplitPackage.get(m); 333 if (splitPkgs != null) { 334 for (PackageInfo sp : splitPkgs) { 335 Set<PackageInfo> pis = packages.get(sp.pkgName); 336 for (PackageInfo pi : pis) { 337 // is the package splitted with its dependence? 338 if (requires.containsKey(pi.module)) { 339 // If so, the dependence has to be LOCAL 340 requires.get(pi.module).addModifier(LOCAL); 341 } 342 } 343 } 344 } 345 346 // use the module's exporter in the dependence 347 Set<Dependence> depset = new TreeSet<Dependence>(); 348 for (Dependence d : requires.values()) { 349 Dependence dep = d; 350 if (!d.isLocal()) { 351 Module exp = d.getModule().exporter(m); 352 if (exp == null) { 353 throw new RuntimeException(d.getModule() + " null exporter"); 354 } 355 if (d.getModule() != exp && exp != m) { 356 dep = new Dependence(exp, d.modifiers()); 357 } 358 } 359 // ## not to include optional dependences in jdk.boot 360 // ## should move this to jdk.base 361 if (m instanceof PlatformModuleBuilder.BootModule && d.isOptional()) { 362 continue; 363 } 364 365 depset.add(dep); 366 } 367 ModuleInfo mi = new ModuleInfo(m, version, packagesForModule.get(m), depset, permits); 368 return mi; 369 } 370 371 private void addDependence(Map<Module, Dependence> requires, Dependence d) { 372 Module dm = d.getModule(); 373 Dependence dep = requires.get(dm); 374 if (dep == null || dep.equals(d)) { 375 requires.put(dm, d); 376 } else { 377 if (dep.getModule() != d.getModule()) { 378 throw new RuntimeException("Unexpected dependence " + dep + " != " + d); 379 } 380 381 // update the modifiers 382 dep.update(d); 383 requires.put(dm, dep); 384 } 385 } 386 387 private void processModuleConfigs(final Module module, 388 final Map<Module, Dependence> requires, 389 final Set<Module> permits) { 390 ModuleVisitor<Void> v = new ModuleVisitor<Void>() { 391 392 public void preVisit(Module p, Void dummy) { 393 } 394 395 public void visited(Module p, Module m, Void dummy) { 396 for (Dependence d : m.config().requires()) { 397 addDependence(requires, d); 398 } 399 for (String name : m.config().permits()) { 400 Module pm = Module.findModule(name); 401 if (pm != null) { 402 permits.add(pm.group()); 403 } else { 404 throw new RuntimeException("module " + name 405 + " specified in the permits rule for " + m.name() 406 + " doesn't exist"); 407 } 408 } 409 } 410 411 public void postVisit(Module p, Void dummy) { 412 } 413 }; 414 415 Set<Module> visited = new TreeSet<Module>(); 416 // first add requires and permits for the module 417 v.visited(module, module, null); 418 // then visit their members 419 module.visitMembers(visited, v, null); 420 } 421 }