--- /dev/null Wed Oct 20 09:31:12 2010 +++ new/make/tools/classanalyzer/src/com/sun/classanalyzer/ModuleBuilder.java Wed Oct 20 09:31:12 2010 @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2010, 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 com.sun.classanalyzer.AnnotatedDependency.OptionalDependency; +import com.sun.classanalyzer.Module.ModuleVisitor; +import com.sun.classanalyzer.ModuleInfo.Dependence; +import com.sun.classanalyzer.ModuleInfo.PackageInfo; +import static com.sun.classanalyzer.ModuleInfo.Dependence.Modifier.*; +import java.io.File; +import java.io.IOException; +import java.util.*; + +/** + * Module builder that creates modules as defined in the given + * module configuration files. The run() method assigns + * all classes and resources according to the module definitions. + * Additional dependency information can be specified e.g. + * Class.forName, JNI_FindClass, and service providers. + * + * @see DependencyConfig + * @author mchung + */ +public class ModuleBuilder { + + private final List depConfigs = new ArrayList(); + private final Map moduleinfos = + new LinkedHashMap(); + private final String version; + private final boolean mergeModules; + + public ModuleBuilder(List configs, String version) + throws IOException { + this(configs, null, true, version); + } + + public ModuleBuilder(List configs, + List depconfigs, + boolean merge, + String version) + throws IOException { + if (configs != null) { + // create modules based on the input config files + for (String file : configs) { + for (ModuleConfig mconfig : ModuleConfig.readConfigurationFile(file)) { + newModule(mconfig); + } + } + } + if (depconfigs != null) { + this.depConfigs.addAll(depconfigs); + } + this.mergeModules = merge; + this.version = version; + } + + /** + * Returns a module of a given name with no main entry point. + */ + public Module newModule(String name) throws IOException { + return newModule(new ModuleConfig(name, null)); + } + + /** + * Returns a module of a given ModuleConfig. + */ + public Module newModule(ModuleConfig mconfig) { + return Module.addModule(mconfig); + } + + /** + * Loads modules from the .classlist and .resources files in + * the given classListDir. + * + */ + public Set loadModulesFrom(File classlistDir) throws IOException { + ClassListReader reader = new ClassListReader(this); + return reader.loadModulesFrom(classlistDir); + } + + /** + * This method assigns the classes and resource files + * to modules and generates the package information and + * the module information. + * + * This method can be overridden in a subclass implementation. + */ + public void run() throws IOException { + // assign classes and resource files to the modules and + // group fine-grained modules per configuration files + buildModules(); + + // generate package information + buildPackageInfos(); + + // analyze cross-module dependencies and generate ModuleInfo + buildModuleInfos(); + } + + /** + * Returns the resulting top-level, non-empty modules. + */ + public Set getModules() { + return moduleinfos.keySet(); + } + + /** + * Builds modules from the existing list of classes and resource + * files according to the module configuration files. + * + */ + protected void buildModules() throws IOException { + // Add additional dependencies after classes are added to the modules + DependencyConfig.parse(depConfigs); + + // process the roots and dependencies to get the classes for each module + Collection modules = Module.getAllModules(); + for (Module m : modules) { + m.processRootsAndReferences(); + } + + if (mergeModules) { + // group fine-grained modules + Module.buildModuleMembers(); + } + } + + /** + * Build ModuleInfo for the top level modules. + */ + protected void buildModuleInfos() { + // backedges (i.e. reverse dependences) + Map> backedges = new TreeMap>(); + + // analyze the module's dependences and create ModuleInfo + for (Module m : Module.getAllModules()) { + if (m.isTopLevel()) { + ModuleInfo mi = buildModuleInfo(m); + m.setModuleInfo(mi); + moduleinfos.put(m, mi); + // keep track of the backedges + for (Dependence d : mi.requires()) { + // only add the top level modules + Module dep = d.getModule(); + Set set = backedges.get(dep); + if (set == null) { + set = new TreeSet(); + backedges.put(dep, set); + } + set.add(m); + } + } + } + + // fixup permits after all ModuleInfo are created in two passes: + // 1. permits the requesting module if it requires local dependence + // 2. if permits set is non-empty, permits + // all of its requesting modules + for (ModuleInfo mi : moduleinfos.values()) { + for (Dependence d : mi.requires()) { + if (d.isLocal()) { + Module dm = d.getModule(); + moduleinfos.get(dm).addPermit(mi.getModule()); + } + } + } + + for (Map.Entry> e : backedges.entrySet()) { + Module dm = e.getKey(); + ModuleInfo dmi = moduleinfos.get(dm); + if (dmi == null) { + throw new RuntimeException(dm + " null moduleinfo"); + } + if (dmi.permits().size() > 0) { + for (Module m : e.getValue()) { + dmi.addPermit(m); + } + } + } + } + + // module to packages + private final Map> packagesForModule = + new TreeMap>(); + // package name to PackageInfo set + private final Map> packages = + new TreeMap>(); + // module with split packages + private final Map> modulesWithSplitPackage = + new TreeMap>(); + + /** + * Builds PackageInfo for each top level module. + */ + protected void buildPackageInfos() { + for (Module m : Module.getAllModules()) { + if (m.isTopLevel()) { + Set pkgs = getPackageInfos(m); + packagesForModule.put(m, pkgs); + } + } + + for (Map.Entry> e : packagesForModule.entrySet()) { + Module m = e.getKey(); + for (PackageInfo p : e.getValue()) { + Set set = packages.get(p.pkgName); + if (set == null) { + set = new TreeSet(); + packages.put(p.pkgName, set); + } + set.add(p); + } + } + + for (Map.Entry> e : packages.entrySet()) { + String pkg = e.getKey(); + if (e.getValue().size() > 1) { + for (PackageInfo pi : e.getValue()) { + Set set = modulesWithSplitPackage.get(pi.module); + if (set == null) { + set = new TreeSet(); + modulesWithSplitPackage.put(pi.module, set); + } + set.add(pi); + } + } + } + } + + public Map> getSplitPackages() { + Map> result = new LinkedHashMap>(); + for (Map.Entry> e : packages.entrySet()) { + String pkg = e.getKey(); + if (e.getValue().size() > 1) { + for (PackageInfo pi : e.getValue()) { + Set set = result.get(pkg); + if (set == null) { + set = new TreeSet(); + result.put(pkg, set); + } + set.add(pi.module); + } + } + } + return result; + } + + private Set getPackageInfos(final Module m) { + Map packages = new TreeMap(); + Module.Visitor> visitor = + new Module.Visitor>() { + + @Override + public Void visitClass(Klass k, Map packages) { + // update package statistics + String pkg = k.getPackageName(); + PackageInfo pkginfo = packages.get(pkg); + if (pkginfo == null) { + pkginfo = new PackageInfo(m, pkg); + packages.put(pkg, pkginfo); + } + + if (k.exists()) { + // only count the class that is parsed + pkginfo.add(k.getFileSize()); + } + return null; + } + + @Override + public Void visitResource(ResourceFile r, Map packages) { + // nop + return null; + } + }; + + m.visit(visitor, packages); + return new TreeSet(packages.values()); + } + + private ModuleInfo buildModuleInfo(Module m) { + Map requires = new LinkedHashMap(); + Set permits = new TreeSet(); + + // add static dependences + for (Klass from : m.classes()) { + for (Klass to : from.getReferencedClasses()) { + if (m.isModuleDependence(to)) { + // is this dependence overridden as optional? + boolean optional = OptionalDependency.isOptional(from, to); + addDependence(requires, new Dependence(from, to, optional)); + } + } + } + + // add dependency due to the main class + Klass k = m.mainClass(); + if (k != null && m.isModuleDependence(k)) { + addDependence(requires, new Dependence(k.getModule())); + } + + // add requires and permits specified in the config files + processModuleConfigs(m, requires, permits); + + // add dependencies due to the AnnotatedDependency + for (Dependence d : AnnotatedDependency.getDependencies(m)) { + addDependence(requires, d); + } + + // Add LOCAL to the dependence and permits will be added + // in the separate phase + Set splitPkgs = modulesWithSplitPackage.get(m); + if (splitPkgs != null) { + for (PackageInfo sp : splitPkgs) { + Set pis = packages.get(sp.pkgName); + for (PackageInfo pi : pis) { + // is the package splitted with its dependence? + if (requires.containsKey(pi.module)) { + // If so, the dependence has to be LOCAL + requires.get(pi.module).addModifier(LOCAL); + } + } + } + } + + // use the module's exporter in the dependence + Set depset = new TreeSet(); + for (Dependence d : requires.values()) { + Dependence dep = d; + if (!d.isLocal()) { + Module exp = d.getModule().exporter(m); + if (exp == null) { + throw new RuntimeException(d.getModule() + " null exporter"); + } + if (d.getModule() != exp && exp != m) { + dep = new Dependence(exp, d.modifiers()); + } + } + // ## not to include optional dependences in jdk.boot + // ## should move this to jdk.base + if (m instanceof PlatformModuleBuilder.BootModule && d.isOptional()) { + continue; + } + + depset.add(dep); + } + ModuleInfo mi = new ModuleInfo(m, version, packagesForModule.get(m), depset, permits); + return mi; + } + + private void addDependence(Map requires, Dependence d) { + Module dm = d.getModule(); + Dependence dep = requires.get(dm); + if (dep == null || dep.equals(d)) { + requires.put(dm, d); + } else { + if (dep.getModule() != d.getModule()) { + throw new RuntimeException("Unexpected dependence " + dep + " != " + d); + } + + // update the modifiers + dep.update(d); + requires.put(dm, dep); + } + } + + private void processModuleConfigs(final Module module, + final Map requires, + final Set permits) { + ModuleVisitor v = new ModuleVisitor() { + + public void preVisit(Module p, Void dummy) { + } + + public void visited(Module p, Module m, Void dummy) { + for (Dependence d : m.config().requires()) { + addDependence(requires, d); + } + for (String name : m.config().permits()) { + Module pm = Module.findModule(name); + if (pm != null) { + permits.add(pm.group()); + } else { + throw new RuntimeException("module " + name + + " specified in the permits rule for " + m.name() + + " doesn't exist"); + } + } + } + + public void postVisit(Module p, Void dummy) { + } + }; + + Set visited = new TreeSet(); + // first add requires and permits for the module + v.visited(module, module, null); + // then visit their members + module.visitMembers(visited, v, null); + } +}