o.n.bootstrap/src/org/netbeans/StandardModule.java
author jtulach@netbeans.org
Wed Sep 26 20:30:15 2007 +0000 (11 months ago)
changeset 56815 7f8dd08beedf
parent 54308da23eb1de38e
permissions -rw-r--r--
I love free software very very very very much
        1 /*
        2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
        3  *
        4  * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
        5  *
        6  * The contents of this file are subject to the terms of either the GNU
        7  * General Public License Version 2 only ("GPL") or the Common
        8  * Development and Distribution License("CDDL") (collectively, the
        9  * "License"). You may not use this file except in compliance with the
       10  * License. You can obtain a copy of the License at
       11  * http://www.netbeans.org/cddl-gplv2.html
       12  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
       13  * specific language governing permissions and limitations under the
       14  * License.  When distributing the software, include this License Header
       15  * Notice in each file and include the License file at
       16  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
       17  * particular file as subject to the "Classpath" exception as provided
       18  * by Sun in the GPL Version 2 section of the License file that
       19  * accompanied this code. If applicable, add the following below the
       20  * License Header, with the fields enclosed by brackets [] replaced by
       21  * your own identifying information:
       22  * "Portions Copyrighted [year] [name of copyright owner]"
       23  *
       24  * Contributor(s):
       25  *
       26  * The Original Software is NetBeans. The Initial Developer of the Original
       27  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
       28  * Microsystems, Inc. All Rights Reserved.
       29  *
       30  * If you wish your version of this file to be governed by only the CDDL
       31  * or only the GPL Version 2, indicate your decision by adding
       32  * "[Contributor] elects to include this software in this distribution
       33  * under the [CDDL or GPL Version 2] license." If you do not indicate a
       34  * single choice of license, a recipient has the option to distribute
       35  * your version of this file under either the CDDL, the GPL Version 2 or
       36  * to extend the choice of license to its licensees as provided above.
       37  * However, if you add GPL Version 2 code and therefore, elected the GPL
       38  * Version 2 license, then the option applies only if the new code is
       39  * made subject to such option by the copyright holder.
       40  */
       41 
       42 package org.netbeans;
       43 
       44 // THIS CLASS OUGHT NOT USE NbBundle NOR org.openide CLASSES
       45 // OUTSIDE OF openide-util.jar! UI AND FILESYSTEM/DATASYSTEM
       46 // INTERACTIONS SHOULD GO ELSEWHERE.
       47 // (NbBundle.getLocalizedValue is OK here.)
       48 
       49 import java.io.File;
       50 import java.io.IOException;
       51 import java.io.InputStream;
       52 import java.security.AllPermission;
       53 import java.security.CodeSource;
       54 import java.security.PermissionCollection;
       55 import java.security.Permissions;
       56 import java.util.ArrayList;
       57 import java.util.Collections;
       58 import java.util.HashMap;
       59 import java.util.HashSet;
       60 import java.util.List;
       61 import java.util.Locale;
       62 import java.util.Map;
       63 import java.util.MissingResourceException;
       64 import java.util.Properties;
       65 import java.util.ResourceBundle;
       66 import java.util.Set;
       67 import java.util.StringTokenizer;
       68 import java.util.jar.Attributes;
       69 import java.util.jar.JarFile;
       70 import java.util.jar.Manifest;
       71 import java.util.logging.Level;
       72 import java.util.zip.ZipEntry;
       73 import org.netbeans.Module.PackageExport;
       74 import org.openide.modules.Dependency;
       75 import org.openide.util.Exceptions;
       76 import org.openide.util.NbBundle;
       77 
       78 /** Object representing one module, possibly installed.
       79  * Responsible for opening of module JAR file; reading
       80  * manifest; parsing basic information such as dependencies;
       81  * and creating a classloader for use by the installer.
       82  * Methods not defined in ModuleInfo must be called from within
       83  * the module manager's read mutex as a rule.
       84  * @author Jesse Glick
       85  */
       86 final class StandardModule extends Module {
       87     
       88     /** JAR file holding the module */
       89     private final File jar;
       90     /** if reloadable, temporary JAR file actually loaded from */
       91     private File physicalJar = null;
       92     private Manifest manifest;
       93     
       94     /** Map from extension JARs to sets of JAR that load them via Class-Path.
       95      * Used only for debugging purposes, so that a warning is printed if two
       96      * different modules try to load the same extension (which would cause them
       97      * to both load their own private copy, which may not be intended).
       98      */
       99     private static final Map<File,Set<File>> extensionOwners = new HashMap<File,Set<File>>();
      100     /** Simple registry of JAR files used as modules.
      101      * Used only for debugging purposes, so that we can be sure
      102      * that no one is using Class-Path to refer to other modules.
      103      */
      104     private static final Set<File> moduleJARs = new HashSet<File>();
      105 
      106     /** Set of locale-variants JARs for this module (or null).
      107      * Added explicitly to classloader, and can be used by execution engine.
      108      */
      109     private Set<File> localeVariants = null;
      110     /** Set of extension JARs that this module loads via Class-Path (or null).
      111      * Can be used e.g. by execution engine. (#9617)
      112      */
      113     private Set<File> plainExtensions = null;
      114     /** Set of localized extension JARs derived from plainExtensions (or null).
      115      * Used to add these to the classloader. (#9348)
      116      * Can be used e.g. by execution engine.
      117      */
      118     private Set<File> localeExtensions = null;
      119     /** Patches added at the front of the classloader (or null).
      120      * Files are assumed to be JARs; directories are themselves.
      121      */
      122     private Set<File> patches = null;
      123     
      124     /** localized properties, only non-null if requested from disabled module */
      125     private Properties localizedProps;
      126     
      127     /** Use ModuleManager.create as a factory. */
      128     public StandardModule(ModuleManager mgr, Events ev, File jar, Object history, boolean reloadable, boolean autoload, boolean eager) throws IOException {
      129         super(mgr, ev, history, reloadable, autoload, eager);
      130         this.jar = jar;
      131         loadManifest();
      132         parseManifest();
      133         findExtensionsAndVariants(manifest);
      134         // Check if some other module already listed this one in Class-Path.
      135         // For the chronologically reverse case, see findExtensionsAndVariants().
      136         Set<File> bogoOwners = extensionOwners.get(jar);
      137         if (bogoOwners != null) {
      138             Util.err.warning("module " + jar + " was incorrectly placed in the Class-Path of other JARs " + bogoOwners + "; please use OpenIDE-Module-Module-Dependencies instead");
      139         }
      140         moduleJARs.add(jar);
      141     }
      142 
      143     public @Override Manifest getManifest() {
      144         if (manifest == null) {
      145             try {
      146                 loadManifest();
      147             } catch (IOException x) {
      148                 Util.err.log(Level.WARNING, "While loading manifest for " + this, x);
      149                 manifest = new Manifest();
      150             }
      151         }
      152         return manifest;
      153     }
      154 
      155     public @Override void releaseManifest() {
      156         manifest = null;
      157     }
      158     
      159     /** Get a localized attribute.
      160      * First, if OpenIDE-Module-Localizing-Bundle was given, the specified
      161      * bundle file (in all locale JARs as well as base JAR) is searched for
      162      * a key of the specified name.
      163      * Otherwise, the manifest's main attributes are searched for an attribute
      164      * with the specified name, possibly with a locale suffix.
      165      * If the attribute name contains a slash, and there is a manifest section
      166      * named according to the part before the last slash, then this section's attributes
      167      * are searched instead of the main attributes, and for the attribute listed
      168      * after the slash. Currently this would only be useful for localized filesystem
      169      * names. E.g. you may request the attribute org/foo/MyFileSystem.class/Display-Name.
      170      * In the future certain attributes known to be dangerous could be
      171      * explicitly suppressed from this list; should only be used for
      172      * documented localizable attributes such as OpenIDE-Module-Name etc.
      173      */
      174     public Object getLocalizedAttribute(String attr) {
      175         String locb = getManifest().getMainAttributes().getValue("OpenIDE-Module-Localizing-Bundle"); // NOI18N
      176         boolean usingLoader = false;
      177         if (locb != null) {
      178             if (classloader != null) {
      179                 if (locb.endsWith(".properties")) { // NOI18N
      180                     usingLoader = true;
      181                     String basename = locb.substring(0, locb.length() - 11).replace('/', '.');
      182                     try {
      183                         ResourceBundle bundle = NbBundle.getBundle(basename, Locale.getDefault(), classloader);
      184                         try {
      185                             return bundle.getString(attr);
      186                         } catch (MissingResourceException mre) {
      187                             // Fine, ignore.
      188                         }
      189                     } catch (MissingResourceException mre) {
      190                         Util.err.log(Level.WARNING, null, mre);
      191                     }
      192                 } else {
      193                     Util.err.warning("cannot efficiently load non-*.properties OpenIDE-Module-Localizing-Bundle: " + locb);
      194                 }
      195             }
      196             if (!usingLoader) {
      197                 if (localizedProps == null) {
      198                     Util.err.log(Level.FINE, "Trying to get localized attr {0} from disabled module {1}", new Object[] {attr, getCodeNameBase()});
      199                     try {
      200                         // check if the jar file still exists (see issue 82480)
      201                         if (jar != null && jar.isFile ()) {
      202                             JarFile jarFile = new JarFile(jar, false);
      203                             try {
      204                                 loadLocalizedProps(jarFile, getManifest());
      205                             } finally {
      206                                 jarFile.close();
      207                             }
      208                         } else {
      209                             Util.err.log(Level.FINE, "Cannot get localized attr {0} from module {1} (missing or deleted JAR file: {2})", new Object[] {attr, getCodeNameBase(), jar});
      210                         }
      211                     } catch (IOException ioe) {
      212                         Util.err.log(Level.WARNING, jar.getAbsolutePath(), ioe);
      213                     }
      214                 }
      215                 if (localizedProps != null) {
      216                     String val = localizedProps.getProperty(attr);
      217                     if (val != null) {
      218                         return val;
      219                     }
      220                 }
      221             }
      222         }
      223         // Try in the manifest now.
      224         int idx = attr.lastIndexOf('/'); // NOI18N
      225         if (idx == -1) {
      226             // Simple main attribute.
      227             return NbBundle.getLocalizedValue(getManifest().getMainAttributes(), new Attributes.Name(attr));
      228         } else {
      229             // Attribute of a manifest section.
      230             String section = attr.substring(0, idx);
      231             String realAttr = attr.substring(idx + 1);
      232             Attributes attrs = getManifest().getAttributes(section);
      233             if (attrs != null) {
      234                 return NbBundle.getLocalizedValue(attrs, new Attributes.Name(realAttr));
      235             } else {
      236                 return null;
      237             }
      238         }
      239     }
      240     
      241     public boolean owns(Class clazz) {
      242         ClassLoader cl = clazz.getClassLoader();
      243         if (cl instanceof Util.ModuleProvider) {
      244             return ((Util.ModuleProvider) cl).getModule() == this;
      245         }
      246         return false;
      247         
      248     }
      249     
      250     public boolean isFixed() {
      251         return false;
      252     }
      253     
      254     /** Get the JAR this module is packaged in.
      255      * May be null for modules installed specially, e.g.
      256      * automatically from the classpath.
      257      * @see #isFixed
      258      */
      259     public File getJarFile() {
      260         return jar;
      261     }
      262     
      263     /** Create a temporary test JAR if necessary.
      264      * This is primarily necessary to work around a Java bug,
      265      * #4405789, which is marked as fixed so might be obsolete.
      266      */
      267     private void ensurePhysicalJar() throws IOException {
      268         if (reloadable && physicalJar == null) {
      269             physicalJar = Util.makeTempJar(jar);
      270         }
      271     }
      272     private void destroyPhysicalJar() {
      273         if (physicalJar != null) {
      274             if (physicalJar.isFile()) {
      275                 if (! physicalJar.delete()) {
      276                     Util.err.warning("temporary JAR " + physicalJar + " not currently deletable.");
      277                 } else {
      278                     Util.err.fine("deleted: " + physicalJar);
      279                 }
      280             }
      281             physicalJar = null;
      282         } else {
      283             Util.err.fine("no physicalJar to delete for " + this);
      284         }
      285     }
      286     
      287     /** Open the JAR, load its manifest, and do related things. */
      288     private void loadManifest() throws IOException {
      289         Util.err.fine("loading manifest of " + jar);
      290         File jarBeingOpened = null; // for annotation purposes
      291         try {
      292             if (reloadable) {
      293                 // Never try to cache reloadable JARs.
      294                 jarBeingOpened = physicalJar; // might be null
      295                 ensurePhysicalJar();
      296                 jarBeingOpened = physicalJar; // might have changed
      297                 JarFile jarFile = new JarFile(physicalJar, false);
      298                 try {
      299                     Manifest m = jarFile.getManifest();
      300                     if (m == null) throw new IOException("No manifest found in " + physicalJar); // NOI18N
      301                     manifest = m;
      302                 } finally {
      303                     jarFile.close();
      304                 }
      305             } else {
      306                 jarBeingOpened = jar;
      307                 manifest = getManager().loadManifest(jar);
      308             }
      309         } catch (IOException e) {
      310             if (jarBeingOpened != null) {
      311                 Exceptions.attachMessage(e,
      312                                          "While loading manifest from: " +
      313                                          jarBeingOpened); // NOI18N
      314             }
      315             throw e;
      316         }
      317     }
      318     
      319     /** Find any extensions loaded by the module, as well as any localized
      320      * variants of the module or its extensions.
      321      */
      322     private void findExtensionsAndVariants(Manifest m) {
      323         assert jar != null : "Cannot load extensions from classpath module " + getCodeNameBase();
      324         localeVariants = null;
      325         List<File> l = Util.findLocaleVariantsOf(jar);
      326         if (!l.isEmpty()) {
      327             localeVariants = new HashSet<File>(l);
      328         }
      329         plainExtensions = null;
      330         localeExtensions = null;
      331         String classPath = m.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
      332         if (classPath != null) {
      333             StringTokenizer tok = new StringTokenizer(classPath);
      334             while (tok.hasMoreTokens()) {
      335                 String ext = tok.nextToken();
      336                 if (new File(ext).isAbsolute() || ext.indexOf("../") != -1) { // NOI18N
      337                     Util.err.warning("Class-Path value " + ext + " from " + jar + " is illegal according to the Java Extension Mechanism: must be relative and not move up directories");
      338                 }
      339                 File extfile = new File(jar.getParentFile(), ext.replace('/', File.separatorChar));
      340                 if (! extfile.exists()) {
      341                     // Ignore unloadable extensions.
      342                     Util.err.warning("Class-Path value " + ext + " from " + jar + " cannot be found at " + extfile);
      343                     continue;
      344                 }
      345                 //No need to sync on extensionOwners - we are in write mutex
      346                     Set<File> owners = extensionOwners.get(extfile);
      347                     if (owners == null) {
      348                         owners = new HashSet<File>(2);
      349                         owners.add(jar);
      350                         extensionOwners.put(extfile, owners);
      351                     } else if (! owners.contains(jar)) {
      352                         owners.add(jar);
      353                         events.log(Events.EXTENSION_MULTIPLY_LOADED, extfile, owners);
      354                     } // else already know about it (OK or warned)
      355                 // Also check to make sure it is not a module JAR! See constructor for the reverse case.
      356                 if (moduleJARs.contains(extfile)) {
      357                     Util.err.warning("Class-Path value " + ext + " from " + jar + " illegally refers to another module; use OpenIDE-Module-Module-Dependencies instead");
      358                 }
      359                 if (plainExtensions == null) plainExtensions = new HashSet<File>();
      360                 plainExtensions.add(extfile);
      361                 l = Util.findLocaleVariantsOf(extfile);
      362                 if (!l.isEmpty()) {
      363                     if (localeExtensions == null) {
      364                         localeExtensions = new HashSet<File>();
      365                     }
      366                     localeExtensions.addAll(l);
      367                 }
      368             }
      369         }
      370         // #9273: load any modules/patches/this-code-name/*.jar files first:
      371         File patchdir = new File(new File(jar.getParentFile(), "patches"), // NOI18N
      372                                  getCodeNameBase().replace('.', '-')); // NOI18N
      373         scanForPatches(patchdir);
      374         // Use of the following system property is not supported, but is used
      375         // by e.g. XTest to influence installed modules without changing the build.
      376         // Format is -Dnetbeans.patches.org.nb.mods.foo=/path/to.file.jar:/path/to/dir
      377         String patchesClassPath = System.getProperty("netbeans.patches." + getCodeNameBase()); // NOI18N
      378         if (patchesClassPath != null) {
      379             StringTokenizer tokenizer = new StringTokenizer(patchesClassPath, File.pathSeparator);
      380             while (tokenizer.hasMoreTokens()) {
      381                 String element = tokenizer.nextToken();
      382                 File fileElement = new File(element);
      383                 if (fileElement.exists()) {
      384                     if (patches == null) {
      385                         patches = new HashSet<File>(15);
      386                     }
      387                     patches.add(fileElement);
      388                 }
      389             }
      390         }
      391         Util.err.fine("localeVariants of " + jar + ": " + localeVariants);
      392         Util.err.fine("plainExtensions of " + jar + ": " + plainExtensions);
      393         Util.err.fine("localeExtensions of " + jar + ": " + localeExtensions);
      394         Util.err.fine("patches of " + jar + ": " + patches);
      395         if (patches != null) {
      396             for (File patch : patches) {
      397                 events.log(Events.PATCH, patch);
      398             }
      399         }
      400     }
      401     
      402     /** Scans a directory for possible patch JARs. */
      403     private void scanForPatches(File patchdir) {
      404         if (!patchdir.isDirectory()) {
      405             return;
      406         }
      407         File[] jars = patchdir.listFiles(Util.jarFilter());
      408         if (jars != null) {
      409             for (File patchJar : jars) {
      410                 if (patches == null) {
      411                     patches = new HashSet<File>(5);
      412                 }
      413                 patches.add(patchJar);
      414             }
      415         } else {
      416             Util.err.warning("Could not search for patches in " + patchdir);
      417         }
      418     }
      419     
      420     /** Check if there is any need to load localized properties.
      421      * If so, try to load them. Throw an exception if they cannot
      422      * be loaded for some reason. Uses an open JAR file for the
      423      * base module at least, though may also open locale variants
      424      * as needed.
      425      * Note: due to #19698, this cache is not usually used; only if you
      426      * specifically go to look at the display properties of a disabled module.
      427      * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=12549">#12549</a>
      428      */
      429     private void loadLocalizedProps(JarFile jarFile, Manifest m) throws IOException {
      430         String locbundle = m.getMainAttributes().getValue("OpenIDE-Module-Localizing-Bundle"); // NOI18N
      431         if (locbundle != null) {
      432             // Something requested, read it in.
      433             // locbundle is a resource path.
      434             {
      435                 ZipEntry bundleFile = jarFile.getEntry(locbundle);
      436                 // May not be present in base JAR: might only be in e.g. default locale variant.
      437                 if (bundleFile != null) {
      438                     localizedProps = new Properties();
      439                     InputStream is = jarFile.getInputStream(bundleFile);
      440                     try {
      441                         localizedProps.load(is);
      442                     } finally {
      443                         is.close();
      444                     }
      445                 }
      446             }
      447             {
      448                 // Check also for localized variant JARs and load in anything from them as needed.
      449                 // Note we need to go in the reverse of the usual search order, so as to
      450                 // overwrite less specific bundles with more specific.
      451                 int idx = locbundle.lastIndexOf('.'); // NOI18N
      452                 String name, ext;
      453                 if (idx == -1) {
      454                     name = locbundle;
      455                     ext = ""; // NOI18N
      456                 } else {
      457                     name = locbundle.substring(0, idx);
      458                     ext = locbundle.substring(idx);
      459                 }
      460                 List<Util.FileWithSuffix> pairs = Util.findLocaleVariantsWithSuffixesOf(jar);
      461                 Collections.reverse(pairs);
      462                 for (Util.FileWithSuffix pair : pairs) {
      463                     File localeJar = pair.file;
      464                     String suffix = pair.suffix;
      465                     String rsrc = name + suffix + ext;
      466                     JarFile localeJarFile = new JarFile(localeJar, false);
      467                     try {
      468                         ZipEntry bundleFile = localeJarFile.getEntry(rsrc);
      469                         // Need not exist in all locale variants.
      470                         if (bundleFile != null) {
      471                             if (localizedProps == null) {
      472                                 localizedProps = new Properties();
      473                             } // else append and overwrite base-locale values
      474                             InputStream is = localeJarFile.getInputStream(bundleFile);
      475                             try {
      476                                 localizedProps.load(is);
      477                             } finally {
      478                                 is.close();
      479                             }
      480                         }
      481                     } finally {
      482                         localeJarFile.close();
      483                     }
      484                 }
      485             }
      486             if (localizedProps == null) {
      487                 // We should have loaded from at least some bundle in there...
      488                 throw new IOException("Could not find localizing bundle: " + locbundle); // NOI18N
      489             }
      490             /* Don't log; too large and annoying:
      491             if (Util.err.isLoggable(ErrorManager.INFORMATIONAL)) {
      492                 Util.err.fine("localizedProps=" + localizedProps);
      493             }
      494             */
      495         }
      496     }
      497     
      498     /** Get all JARs loaded by this module.
      499      * Includes the module itself, any locale variants of the module,
      500      * any extensions specified with Class-Path, any locale variants
      501      * of those extensions.
      502      * The list will be in classpath order (patches first).
      503      * Currently the temp JAR is provided in the case of test modules, to prevent
      504      * sporadic ZIP file exceptions when background threads (like Java parsing) tries
      505      * to open libraries found in the library path.
      506      * JARs already present in the classpath are <em>not</em> listed.
      507      * @return a <code>List&lt;File&gt;</code> of JARs
      508      */
      509     public List<File> getAllJars() {
      510         List<File> l = new ArrayList<File>();
      511         if (patches != null) l.addAll(patches);
      512         if (physicalJar != null) {
      513             l.add(physicalJar);
      514         } else if (jar != null) {
      515             l.add(jar);
      516         }
      517         if (plainExtensions != null) l.addAll (plainExtensions);
      518         if (localeVariants != null) l.addAll (localeVariants);
      519         if (localeExtensions != null) l.addAll (localeExtensions);
      520         return l;
      521     }
      522 
      523     /** Set whether this module is supposed to be reloadable.
      524      * Has no immediate effect, only impacts what happens the
      525      * next time it is enabled (after having been disabled if
      526      * necessary).
      527      * Must be called from within a write mutex.
      528      * @param r whether the module should be considered reloadable
      529      */
      530     public void setReloadable(boolean r) {
      531         getManager().assertWritable();
      532         if (reloadable != r) {
      533             reloadable = r;
      534             getManager().fireReloadable(this);
      535         }
      536     }
      537     
      538     /** Used as a flag to tell if this module was really successfully released.
      539      * Currently does not work, so if it cannot be made to work, delete it.
      540      * (Someone seems to be holding a strong reference to the classloader--who?!)
      541      */
      542     private transient boolean released;
      543     /** Count which release() call is really being checked. */
      544     private transient int releaseCount = 0;
      545 
      546     /** Reload this module. Access from ModuleManager.
      547      * If an exception is thrown, the module is considered
      548      * to be in an invalid state.
      549      */
      550     public void reload() throws IOException {
      551         // Probably unnecessary but just in case:
      552         destroyPhysicalJar();
      553         String codeNameBase1 = getCodeNameBase();
      554         localizedProps = null;
      555         loadManifest();
      556         parseManifest();
      557         findExtensionsAndVariants(manifest);
      558         String codeNameBase2 = getCodeNameBase();
      559         if (! codeNameBase1.equals(codeNameBase2)) {
      560             throw new InvalidException("Code name base changed during reload: " + codeNameBase1 + " -> " + codeNameBase2); // NOI18N
      561         }
      562     }
      563     
      564     // Access from ModuleManager:
      565     /** Turn on the classloader. Passed a list of parent modules to use.
      566      * The parents should already have had their classloaders initialized.
      567      */
      568     protected void classLoaderUp(Set<Module> parents) throws IOException {
      569         Util.err.fine("classLoaderUp on " + this + " with parents " + parents);
      570         // Find classloaders for dependent modules and parent to them.
      571         List<ClassLoader> loaders = new ArrayList<ClassLoader>(parents.size() + 1);
      572         // This should really be the base loader created by org.nb.Main for loading openide etc.:
      573         loaders.add(Module.class.getClassLoader());
      574         for (Module parent: parents) {
      575             PackageExport[] exports = parent.getPublicPackages();
      576             if (exports != null && exports.length == 0) {
      577                 // Check if there is an impl dep here.
      578                 boolean implDep = false;
      579                 for (Dependency dep : getDependenciesArray()) {
      580                     if (dep.getType() == Dependency.TYPE_MODULE &&
      581                             dep.getComparison() == Dependency.COMPARE_IMPL &&
      582                             dep.getName().equals(parent.getCodeName())) {
      583                         implDep = true;
      584                         break;
      585                     }
      586                 }
      587                 if (!implDep) {
      588                     // Nothing exported from here at all, no sense even adding it.
      589                     // Shortcut; would not harm anything to add it now, but we would
      590                     // never use it anyway.
      591                     // Cf. #27853.
      592                     continue;
      593                 }
      594             }
      595             ClassLoader l = parent.getClassLoader();
      596             if (parent.isFixed() && loaders.contains(l)) {
      597                 Util.err.fine("#24996: skipping duplicate classloader from " + parent);
      598                 continue;
      599             }
      600             loaders.add(l);
      601         }
      602         List<File> classp = new ArrayList<File>(3);
      603         if (patches != null) classp.addAll(patches);
      604 
      605         if (reloadable) {
      606             ensurePhysicalJar();
      607             // Using OPEN_DELETE does not work well with test modules under 1.4.
      608             // Random code (URL handler?) still expects the JAR to be there and
      609             // it is not.
      610             classp.add(physicalJar);
      611         } else {
      612             classp.add(jar);
      613         }
      614         // URLClassLoader would not otherwise find these, so:
      615         if (localeVariants != null) classp.addAll(localeVariants);
      616 
      617         if (localeExtensions != null) classp.addAll(localeExtensions);
      618 
      619         if (plainExtensions != null) classp.addAll(plainExtensions);
      620         
      621         // #27853:
      622         getManager().refineClassLoader(this, loaders);
      623         
      624         try {
      625             classloader = new OneModuleClassLoader(classp, loaders.toArray(new ClassLoader[loaders.size()]));
      626         } catch (IllegalArgumentException iae) {
      627             // Should not happen, but just in case.
      628             throw (IOException) new IOException(iae.toString()).initCause(iae);
      629         }
      630     }
      631     
      632     /** Turn off the classloader and release all resources. */
      633     protected void classLoaderDown() {
      634         if (classloader instanceof ProxyClassLoader) {
      635             ((ProxyClassLoader)classloader).destroy();
      636         }
      637         classloader = null;
      638         Util.err.fine("classLoaderDown on " + this + ": releaseCount=" + releaseCount + " released=" + released);
      639         released = false;
      640     }
      641     /** Should be called after turning off the classloader of one or more modules & GC'ing. */
      642     protected void cleanup() {
      643         if (isEnabled()) throw new IllegalStateException("cleanup on enabled module: " + this); // NOI18N
      644         if (classloader != null) throw new IllegalStateException("cleanup on module with classloader: " + this); // NOI18N
      645         if (! released) {
      646             Util.err.fine("Warning: not all resources associated with module " + jar + " were successfully released.");
      647             released = true;
      648         } else {
      649             Util.err.fine("All resources associated with module " + jar + " were successfully released.");
      650         }
      651         // XXX should this rather be done when the classloader is collected?
      652         destroyPhysicalJar();
      653     }
      654     
      655     /** Notify the module that it is being deleted. */
      656     public void destroy() {
      657         moduleJARs.remove(jar);
      658     }
      659     
      660     /** String representation for debugging. */
      661     public String toString() {
      662         String s = "StandardModule:" + getCodeNameBase() + " jarFile: " + jar.getAbsolutePath(); // NOI18N
      663         if (!isValid()) s += "[invalid]"; // NOI18N
      664         return s;
      665     }
      666     
      667     /** PermissionCollection with an instance of AllPermission. */
      668     private static PermissionCollection modulePermissions;
      669     /** @return initialized @see #modulePermission */
      670     private static synchronized PermissionCollection getAllPermission() {
      671         if (modulePermissions == null) {
      672             modulePermissions = new Permissions();
      673             modulePermissions.add(new AllPermission());
      674             modulePermissions.setReadOnly();
      675         }
      676         return modulePermissions;
      677     }
      678 
      679     /** Class loader to load a single module.
      680      * Auto-localizing, multi-parented, permission-granting, the works.
      681      */
      682     private class OneModuleClassLoader extends JarClassLoader implements Util.ModuleProvider {
      683         private int rc;
      684         /** Create a new loader for a module.
      685          * @param classp the List of all module jars of code directories;
      686          *      includes the module itself, its locale variants,
      687          *      variants of extensions and Class-Path items from Manifest.
      688          *      The items are JarFiles for jars and Files for directories
      689          * @param parents a set of parent classloaders (from other modules)
      690          */
      691         public OneModuleClassLoader(List<File> classp, ClassLoader[] parents) throws IllegalArgumentException {
      692             super(classp, parents, false, StandardModule.this);
      693             rc = releaseCount++;
      694         }
      695         
      696         public Module getModule() {
      697             return StandardModule.this;
      698         }
      699         
      700         /** Inheri