alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.modules;

import java.beans.Beans;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.MalformedURLException;
import java.util.*;
import java.util.jar.Manifest;
import java.util.jar.Attributes;

import org.openide.ErrorManager;
import org.openide.TopManager;
import org.openide.util.NbBundle;
import org.openide.util.SharedClassObject;

/** A description of a module that is constructed from the module's manifest file.
* This provides a convenient way to parse a manifest file.
* @author Jaroslav Tulach, Ian Formanek, Jesse Glick
* @deprecated No longer used; see {@link org.openide.modules.ModuleInfo} instead.
*/
public final class ModuleDescription extends Object {
    
    static {
        if (ErrorManager.getDefault().isNotifiable(ErrorManager.INFORMATIONAL)) {
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, new Throwable("WARNING: use of deprecated class org.openide.modules.ModuleDescription")); // NOI18N
        }
    }

    // [PENDING] use ErrorManager instead
    private static final boolean VERBOSE = Boolean.getBoolean ("org.openide.modules.ModuleDescription.VERBOSE"); // NOI18N

    // -----------------------------------------------------------------------------
    // Global tags

    /** Global tag for whole module. Identifies the JAR as a module and gives its code name. */
    public static final Attributes.Name TAG_MAGIC = new Attributes.Name ("OpenIDE-Module"); // NOI18N
    /** Display name of module. May be localized, e.g. OpenIDE-Module-Name_cs. */
    public static final Attributes.Name TAG_NAME = new Attributes.Name ("OpenIDE-Module-Name"); // NOI18N
    /** Short description of module. May be localized, e.g. OpenIDE-Module-Short-Description_cs. */
    public static final Attributes.Name TAG_SHORT_DESCRIPTION = new Attributes.Name ("OpenIDE-Module-Short-Description"); // NOI18N
    /** Long description of module.
     * May be localized, e.g. OpenIDE-Module-Long-Description_cs.
     * @since 1.1.6
     */
    public static final Attributes.Name TAG_LONG_DESCRIPTION = new Attributes.Name ("OpenIDE-Module-Long-Description"); // NOI18N
    /** Category of module for display purposes. May be localized, e.g. OpenIDE-Module-Display-Category_cs. */
    public static final Attributes.Name TAG_CATEGORY = new Attributes.Name ("OpenIDE-Module-Display-Category"); // NOI18N

    private static final Comparator codeNameComparator = new Comparator () {
                public int compare (Object o1, Object o2) {
                    ModuleDescription md1 = (ModuleDescription) o1;
                    ModuleDescription md2 = (ModuleDescription) o2;
                    return md1.getCodeName ().compareTo (md2.getCodeName ());
                }
            };

    // -----------------------------------------------------------------------------
    // Versioning tags

    /** Specification version tag for whole module. Identifies the specification version of the module. */
    public static final Attributes.Name TAG_SPEC_VERSION = new Attributes.Name ("OpenIDE-Module-Specification-Version"); // NOI18N
    /** Implementation version tag for whole module. Identifies the implementation version of the module. */
    public static final Attributes.Name TAG_IMPL_VERSION = new Attributes.Name ("OpenIDE-Module-Implementation-Version"); // NOI18N

    // -----------------------------------------------------------------------------
    // Dependency tags

    /** Module dependency tag for whole module. Identifies the modules on which this module depends. */
    public static final Attributes.Name TAG_MODULE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Module-Dependencies"); // NOI18N
    /** Package dependency tag for whole module. Identifies the package versions on which this module depends. */
    public static final Attributes.Name TAG_PACKAGE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Package-Dependencies"); // NOI18N
    /** Java dependency tag for whole module. Identifies the Java version on which this module depends. */
    public static final Attributes.Name TAG_JAVA_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-Java-Dependencies"); // NOI18N
    /** IDE dependency tag for whole module. Identifies the version of the core IDE on which this module depends. */
    public static final Attributes.Name TAG_IDE_DEPENDENCIES = new Attributes.Name ("OpenIDE-Module-IDE-Dependencies"); // NOI18N

    // -----------------------------------------------------------------------------
    // ModuleInstall tags

    /** Name of (optional) main class. Must be public with a no-argument public constructor, and
    * implement {@link org.openide.modules.ModuleInstall}.
    */
    public static final Attributes.Name TAG_MAIN = new Attributes.Name ("OpenIDE-Module-Install"); // NOI18N

    /** Name of (optional) XML filesystem layer.
     * This is given in the global attributes of a module.
     * Should be given as a resource path such as:
     * 
org/netbeans/modules/foo/resources/layer.xml
* The path will be treated as localizable, so for example * the above path may actually load: *
org/netbeans/modules/foo/resources/layer_ja.xml
*/ public static final Attributes.Name TAG_LAYER = new Attributes.Name ("OpenIDE-Module-Layer"); // NOI18N // ----------------------------------------------------------------------------- // Module content tags /** Name of an (optional) JavaHelp-style help set. * May be localized. *

For example, the value org.netbeans.module.Index will look for one of the following, according to locale: *

    * /org/netbeans/module/Index_cs.html
    * /org/netbeans/module/Index.html
    * 
*/ public static final Attributes.Name TAG_DESCRIPTION = new Attributes.Name ("OpenIDE-Module-Description"); // NOI18N // [PENDING] icon tag /** Tag for a section. Identifies that the entry is specially treated somehow. */ public static final Attributes.Name TAG_SECTION_CLASS = new Attributes.Name ("OpenIDE-Module-Class"); // NOI18N /** "Action" module section. */ // NOI18N public static final String SECTION_ACTION = "Action"; // NOI18N /** "Option" module section. */ // NOI18N public static final String SECTION_OPTION = "Option"; // NOI18N /** "Loader" module section. */ // NOI18N public static final String SECTION_LOADER = "Loader"; // NOI18N /** Option to install this loader before another. */ public static final Attributes.Name TAG_INSTALL_BEFORE = new Attributes.Name ("Install-Before"); // NOI18N /** Option to install this loader after another. */ public static final Attributes.Name TAG_INSTALL_AFTER = new Attributes.Name ("Install-After"); // NOI18N /** "Filesystem" module section. */ // NOI18N public static final String SECTION_FILESYSTEM = "Filesystem"; // NOI18N /** Display name of a file system type. Used e.g. in popup menus to add a new instance to the Repository. */ public static final Attributes.Name TAG_FILESYSTEM_NAME = new Attributes.Name ("Display-Name"); // NOI18N /** Help resource for a file system. */ public static final Attributes.Name TAG_FILESYSTEM_HELP = new Attributes.Name ("Help"); // NOI18N /** "Service" module section. */ // NOI18N public static final String SECTION_SERVICE = "Service"; // NOI18N /** Whether this service should be the default for its category. */ public static final Attributes.Name TAG_SERVICE_DEFAULT = new Attributes.Name ("Default"); // NOI18N /** "Debugger" module section. */ // NOI18N public static final String SECTION_DEBUGGER = "Debugger"; // NOI18N /** "Node" module section. */ // NOI18N public static final String SECTION_NODE = "Node"; // NOI18N /** Option to distingiush between different types of nodes. *

Currently the possible types are Environment, Roots, * and Session. If the attribute * is missing, Environment is assumed. */ public static final Attributes.Name TAG_NODE_TYPE = new Attributes.Name ("Type"); // NOI18N /** "ClipboardConvertor" module section. */ // NOI18N public static final String SECTION_CLIPBOARD_CONVERTOR = "ClipboardConvertor"; // NOI18N // ----------------------------------------------------------------------------- // private variables /** classloader to use when creating module installables */ ClassLoader cl; /** (display) name of module */ private String moduleName; // code name of module private String codeName; /** short description */ private String shortDescription; /** long description */ private String longDescription; /** display category */ private String displayCategory; /** main class instance class */ private String mainClass; /** true if main class was actually a *.ser and not *.class */ private boolean mainClassSer; /** main class instance */ private ModuleInstall main; /** layer resource path */ private String layerResource; /** URL to filesystem layer */ private URL layer; /** specification vesion */ private String specVersion; /** implementation version */ private String implVersion; // Dependencies of various sorts. Elements are of type Dependency. private Set dependenciesSet; /** resource for description */ private String description; /** list of sections in the module */ private ManifestSection[] sections; /** set of instance classes which do not come from the classloader cl */ final Set badClasses = new HashSet (); // Set // ----------------------------------------------------------------------------- // Constructor /** Create new description from a provided manifest file. * It is assumed that the JAR file is already known to the current Repository class loader. * @param name name of the JAR file * @param man the manifest file within that JAR * @exception IllegalModuleException if there is an error reading the description * @deprecated Better to explicitly provide a classloader. */ public ModuleDescription (String name, Manifest man) throws IllegalModuleException { this (name, man, TopManager.getDefault ().currentClassLoader ()); } /** Create new description from a provided manifest file and classloader. * @param name name of the JAR file * @param man the manifest file within that JAR * @param cl classloader which should be able to find objects mentioned in the manifest; * if null, must be set later with {@link #setClassLoader} * @exception IllegalModuleException if there is an error reading the description */ public ModuleDescription (String name, Manifest man, ClassLoader cl) throws IllegalModuleException { setClassLoader (cl); createDescription (name, man); if (VERBOSE) { System.err.println ("Making module description for " + this); Enumeration en = dependencies (); while (en.hasMoreElements ()) System.err.println ("\t=>" + (Dependency) en.nextElement ()); } } /** Create new description from a fixed JAR file on disk (for testing). * This constructor may be conveniently used to test parsing of a module JAR, * as well as testing cross-dependencies and so on. * Both the Repository and the JAR itself will be searched for classes. * @param jar the JAR file * @deprecated Only for testing. * @throws IllegalModuleException if there is an error reading the description * @throws IOException if the JAR file could not be opened or read */ public ModuleDescription (File jar) throws IllegalModuleException, IOException { this (jar.getPath (), new Manifest (new FileInputStream (jar)), new URLClassLoader (new URL[] { jar.getCanonicalFile ().toURL () }, TopManager.getDefault ().currentClassLoader ())); } /** Create new description from a string (for testing). * This constructor may be conveniently used to test parsing of a module JAR, * as well as testing cross-dependencies and so on. * The Repository will be searched for classes. * @param text the full text of the manifest file * @deprecated Only for testing. * @throws IllegalModuleException if there is an error reading the description * @throws IOException should not be thrown */ public ModuleDescription (String text) throws IllegalModuleException, IOException { this ("/no/path/to/testManifest.mf", new Manifest (new ByteArrayInputStream (text.getBytes ()))); // NOI18N } // ----------------------------------------------------------------------------- // Public interface /** Get display name of the module. * @return the name * @see #TAG_NAME */ public String getName () { return moduleName; } /** Get the module's short description (like a tool tip). * @return the short description, or null if unspecified * @see #TAG_SHORT_DESCRIPTION */ public String getShortDescription () { return shortDescription; } /** Get the module's long description (paragraph-length summary). * @return the long description, or null if unspecified * @see #TAG_LONG_DESCRIPTION * @since 1.1.6 */ public String getLongDescription () { return longDescription; } /** Get the module's display category. * @return the display category, or null if unspecified * @see #TAG_CATEGORY */ public String getDisplayCategory () { return displayCategory; } /** Get code name of the module. * @return the code name (should not normally change between releases except to indicate incompatible changes) * @see #TAG_MAGIC */ public String getCodeName () { return codeName; } /** Get code name base of the module. * E.g. for the code name foo/3, this would give foo. * @return the code name base (should not change between releases) * @see #getCodeName */ public String getCodeNameBase () { int slash = codeName.indexOf ("/"); // NOI18N if (slash == -1) return codeName; else return codeName.substring (0, slash); } /** Get the major release number of the module code name. * E.g. for the code name foo/3, this would give 3. * @return the release number (should change between releases to indicate incompatible changes), or -1 if unspecified * @see #getCodeName */ public int getCodeNameRelease () { int slash = codeName.indexOf ("/"); // NOI18N if (slash == -1) return -1; else return Integer.parseInt (codeName.substring (slash + 1)); } /** Get the classloader used to load this module. * @return its classloader */ public ClassLoader getClassLoader () { return cl; } /** Set the classloader used to load this module. * May be null, in which case resources associated with this module are cleared; * before attempting to load anything again, the loader must be set back to something. * @param cl the classloader */ public synchronized void setClassLoader (ClassLoader cl) { if (this.cl != null && cl == null) { // Clear all cached objects. main = null; layer = null; badClasses.clear (); for (int i = 0; i < sections.length; i++) { sections[i].release (); } } this.cl = cl; if (VERBOSE) showLoader (); } /** Get a set of classes that were not loaded by the requested classloader. * @return a set of {@link Class} * @see #getClassLoader */ public Set getBadClasses () { return Collections.unmodifiableSet (badClasses); } /** Get the main hook object of the module (to run hooks from). * If the module did not specify a main class, a dummy will be returned instead. * * @return main object of the module * @see #TAG_MAIN */ public synchronized ModuleInstall getModule () { try { if (mainClass != null) { /* System.err.println("ModuleDescription.getModule; cl=" + cl); if (cl != null && cl instanceof java.net.URLClassLoader) { java.net.URL[] urls = ((java.net.URLClassLoader) cl).getURLs (); for (int i = 0; i < urls.length; i++) System.err.println("\t" + urls[i]); } */ if (mainClassSer) { main = (ModuleInstall) Beans.instantiate (cl, mainClass); } else { main = (ModuleInstall) SharedClassObject.findObject (Class.forName (mainClass, true, cl), true); } if (! mainClassSer && main.getClass ().getClassLoader () != cl) badClasses.add (main.getClass ()); } // do not try anymore mainClass = null; } catch (Exception ex) { if (ex instanceof ClassNotFoundException) showLoader (); TopManager.getDefault ().notifyException (ex); } if (main == null) { main = MODULE_NONE; } return main; } void showLoader () { System.err.println ("Offending classloader: " + cl); // NOI18N showLoader0 (cl); } private static void showLoader0 (ClassLoader cl) { if (cl != null) { showLoader0 (cl.getParent ()); if (cl instanceof URLClassLoader) { URL[] urls = ((URLClassLoader) cl).getURLs (); for (int i = 0; i < urls.length; i++) System.err.println ("\t" + urls[i]); // NOI18N } } } /** Get the filesystem layer. * If non-null, will be a pointer to an * XML filesystem which should be added by the module * as a project layer. * @return a URL to the layer, or null if none was specified * @deprecated {@link #getLayerResource} actually used now */ public synchronized URL getLayer () { if (layerResource != null) { // Try at most once to load it, otherwise it will be null. try { int idx = layerResource.lastIndexOf ('.'); // [PENDING] supply proper localized exception: if (idx == -1) throw new MalformedURLException (); String base = layerResource.substring (0, idx).replace ('/', '.'); String ext = layerResource.substring (idx + 1); layer = NbBundle.getLocalizedFile (base, ext, Locale.getDefault (), cl); } catch (Exception e) { TopManager.getDefault ().notifyException (e); } layerResource = null; } return layer; } /** Get the resource path to the filesystem layer. * @return the resource path, or null */ public String getLayerResource () { return layerResource; } /** Get a URL to a page describing the module. * @return the URL of a JavaHelp HelpSet file, or * null if the module did not specify a description * @see #TAG_DESCRIPTION * @exception IllegalStateException if the tag is specified but the HelpSet file was not found * @deprecated Currently disused in favor of {@link #getDescriptionResource} */ public URL getDescription () { if (description == null) return null; ErrorManager em = ErrorManager.getDefault (); /* try { URL u = new URL ("nbresloc:/" + description.replace ('.', '/') + ".hs"); // NOI18N try { u.openConnection ().connect (); return u; } catch (IOException ex) { // Well, maybe we need the specified classloader; try that. */ if (cl == null) throw (IllegalStateException) em.annotate (new IllegalStateException (), new NullPointerException ()); try { return NbBundle.getLocalizedFile (description, "hs", Locale.getDefault (), cl); // NOI18N } catch (MissingResourceException mre) { IllegalStateException ise = new IllegalStateException (); //em.annotate (ise, ex); throw (IllegalStateException) em.annotate (ise, mre); } /* } } catch (MalformedURLException mfue) { throw (IllegalStateException) em.annotate (new IllegalStateException (), mfue); } */ } /** Get JavaHelp helpset as a resource path. * This returns a resource path to the base-locale helpset. * @return the path to the helpset, or null if no helpset was specified */ public String getDescriptionResource () { if (description == null) { return null; } else { return description.replace ('.', '/') + ".hs"; // NOI18N } } /** Iterates over all entries found in the module. It sends all the entries * to the provided iterator's callback methods. * * @param it iterator over all sections */ public synchronized void forEachSection (ManifestSection.Iterator it) { int s = sections.length; List al = new LinkedList (); for (int i = 0; i < s; i++) { try { sections[i].invokeIterator (it); al.add (sections[i]); } catch (Exception ex) { TopManager.getDefault().notifyException(ex); // extract the section } } if (sections.length > al.size ()) sections = (ManifestSection[]) al.toArray (new ManifestSection[al.size ()]); } /** Get all dependencies. * @return an enumeration of {@link ModuleDescription.Dependency}s * @see #getDependencies */ public Enumeration dependencies () { return Collections.enumeration (dependenciesSet); } /** Get a list of all dependencies. * @return the dependencies * @see #dependencies */ public Dependency[] getDependencies () { return (Dependency[]) dependenciesSet.toArray (new Dependency[dependenciesSet.size()]); } /** Get the specification version of this module. * @return the spec version, or null * @see #TAG_SPEC_VERSION */ public String getSpecVersion () { return specVersion; } /** Get the implementation version of this module. * @return the impl version, or null * @see #TAG_IMPL_VERSION */ public String getImplVersion () { return implVersion; } /** Check whether this description satisfies all of its dependencies, and if not say why. * @param otherModules other modules which this module might require * @return null if satisfied, else text explaining why it was not * @see ModuleDescription.Dependency#checkForMiss */ public String reasonWhyUnsatisfied (ModuleDescription[] otherModules) throws IllegalModuleException { if (VERBOSE) System.err.println ("Checking all dependencies for " + this); String reason = null; Iterator it = dependenciesSet.iterator (); while (it.hasNext ()) { Dependency dep = (Dependency) it.next (); String miss = dep.checkForMiss (otherModules); if (miss != null) { // String text = getStringFormatted ("MSG_Why_Dep_Failed", getName (), getCodeName (), dep.toString (), miss); // NOI18N if (reason == null) reason = getStringFormatted ("MSG_Why_Dep_Failed", getName (), getCodeName () ) + miss; // NOI18N //reason = text; else reason += "\n" + miss; // NOI18N } } return reason; } /** Check whether this description depends on another module. * This does not check whether the dependency is fully satisfied (i.e. the versions match); * it only checks whether there is some sort of dependency or not. * Also note that implicit dependencies within sections, e.g. the implicit dependency one module * may have on another based on loader pool installation or whatnot, is not considered at all. * Modules are considered to depend on themselves. * @param other the other module to compare to * @return true if this module states that it depends on the other in its dependency list, false if it makes no such statement * @see #TAG_MODULE_DEPENDENCIES */ public boolean dependsOnModule (ModuleDescription other) { if (this == other || codeName.equals (other.codeName)) return true; Iterator it = dependenciesSet.iterator (); while (it.hasNext ()) { Dependency dep = (Dependency) it.next(); if (dep.getType () == Dependency.TYPE_MODULE && dep.getName ().equals (other.getCodeName ())) return true; } return false; } public String toString () { return getStringFormatted ("DBG_Module_ToString", getCodeName (), getName ()); // NOI18N } /** Resolve the proper ordering of a set of modules. * Checks the dependencies among the modules and attempts to order them * according to a topological sort based on cross-dependencies. * Where the ordering is not otherwise specified, orders modules alphabetically based on code name. * @param modules a set of ModuleDescriptions to be installed * @return a list of the same ModuleDescriptions in the order in which they should be installed * @throws IllegalModuleException if the ordering cannot be resolved (for example, due to a cyclic dependency) * @see #dependsOnModule */ public static List resolveOrdering (Set modules) throws IllegalModuleException { if (VERBOSE) { System.err.println ("Incoming module list: "); Iterator it0 = modules.iterator (); while (it0.hasNext ()) System.err.println ("\t" + (ModuleDescription) it0.next ()); } // Not a Knuth-quality topological sort here by any means! Oh well. // Predetermine all cross-dependencies (for speed). // This is a map from MD to set of MD's it depends on (not incl. itself). Map crossdeps = new HashMap (); // Map> Iterator it1 = modules.iterator (); while (it1.hasNext ()) { ModuleDescription md = (ModuleDescription) it1.next (); Set thisdeps = new HashSet (); // Set Iterator it2 = modules.iterator (); while (it2.hasNext ()) { ModuleDescription other = (ModuleDescription) it2.next (); if (md == other) continue; if (md.dependsOnModule (other)) thisdeps.add (other); } crossdeps.put (md, thisdeps); } // Order the modules alphabetically into a new set. SortedSet sorted = new TreeSet (codeNameComparator); // SortedSet sorted.addAll (modules); if (sorted.size () != modules.size ()) throwOverlapException (modules, sorted); // The result list. List result = new ArrayList (); // List // Iteratively look for modules with no remaining dependencies. // We will remove items from sorted as we go and add them to result. pullin_em_out: while (sorted.size () > 0) { // System.err.println ("Sorted set size at " + sorted.size ()); Iterator it3 = sorted.iterator (); while (it3.hasNext ()) { ModuleDescription test = (ModuleDescription) it3.next (); Set remainingDeps = (Set) crossdeps.get (test); if (remainingDeps.size () == 0) { // System.err.println ("Removing " + test.getName () + " (leaf count at " + leafCount + ")"); result.add (test); it3.remove (); // Kill deps on this from other modules. Iterator it4 = sorted.iterator (); while (it4.hasNext ()) { ModuleDescription othermod = (ModuleDescription) it4.next (); if (othermod == test) throw new IllegalModuleException ("Should not happen."); // NOI18N Set otherdeps = (Set) crossdeps.get (othermod); otherdeps.remove (test); } continue pullin_em_out; } } // OK, there were none pulled out on this round => cyclic dependency. // Display a list of all modules that had cyclic dependencies, sep'd by commas. Iterator it5 = sorted.iterator (); boolean first = true; StringBuffer buf = new StringBuffer (); while (it5.hasNext ()) { if (first) first = false; else buf.append ("; "); // NOI18N ModuleDescription bad = (ModuleDescription) it5.next (); buf.append (bad.getName ()); buf.append (" (=> "); // NOI18N boolean firstagain = true; Iterator it6 = ((Set) crossdeps.get (bad)).iterator (); while (it6.hasNext ()) { if (firstagain) firstagain = false; else buf.append (", "); // NOI18N buf.append (((ModuleDescription) it6.next ()).getName ()); } buf.append (")"); // NOI18N } throw new IllegalModuleException (getStringFormatted ("EXC_Cyclic", buf.toString ())); // NOI18N } if (VERBOSE) { System.err.println ("Outgoing module list: "); Iterator hookah2 = result.iterator (); while (hookah2.hasNext ()) System.err.println ("\t" + (ModuleDescription) hookah2.next ()); } return result; } /** Throw IllegalModuleException indicating a module code name overlap. For convenience. * @param incoming raw set of modules with duplicates * @param checked sorted set of modules with duplicates removed * @throws IllegalModuleException always */ private static void throwOverlapException (Set incoming, SortedSet checked) throws IllegalModuleException { // By popular request, this message should be more helpful now: StringBuffer overlaps = new StringBuffer (); Map occurrences = new HashMap (); // Map Iterator it = incoming.iterator (); while (it.hasNext ()) { ModuleDescription md = (ModuleDescription) it.next (); String cn = md.getCodeName (); Integer count = (Integer) occurrences.get (cn); if (count == null) occurrences.put (cn, new Integer (1)); else occurrences.put (cn, new Integer (count.intValue () + 1)); } boolean firstOverlap = true; Iterator it2 = occurrences.keySet ().iterator (); while (it2.hasNext ()) { String codeName = (String) it2.next (); Integer count2 = (Integer) occurrences.get (codeName); if (count2.intValue () > 1) { if (firstOverlap) firstOverlap = false; else overlaps.append (' '); overlaps.append (codeName); overlaps.append ('('); overlaps.append (count2.toString ()); overlaps.append (')'); } } throw new IllegalModuleException (getStringFormatted ("EXC_Overlapping_Code_Names", // NOI18N String.valueOf (checked.size ()), String.valueOf (incoming.size () - checked.size ()), ModuleDescription.TAG_MAGIC.toString (), overlaps.toString ())); } /** Actually generate a list of newly-installable modules in this IDE (but do not install them). * Checks all of their dependencies, and orders them properly. * If any are missing dependencies, they are removed from the set * (after notifying the user), and the remainder are reexamined in case * other modules are now missing a dependency. They are ordered in the normal fashion. * If there are exceptions in any calculations (not just missed dependencies), * these are propagated without any attempt at further error recovery. * @param restored a set of ModuleDescriptions for already-installed modules which should have already been restored * @param installed a set of ModuleDescriptions for modules which are intended for installation and may be returned * @return a (possibly empty) list of ModuleDescriptions for modules which may be installed, in the order in which they should be installed; will be a subset of installed * @throws IllegalModuleException if any problem is encountered other than missed dependencies */ public static List resolveOrderingForRealInstall (Set restored, Set installed) throws IllegalModuleException { if (VERBOSE) System.err.println ("rOFRI called."); { // Check that there are not overlaps anywhere. SortedSet doubleCheck = new TreeSet (codeNameComparator); doubleCheck.addAll (restored); doubleCheck.addAll (installed); int actual = doubleCheck.size (); int presumed = restored.size () + installed.size (); if (actual != presumed) { Set combined = new HashSet (); combined.addAll (restored); combined.addAll (installed); throwOverlapException (combined, doubleCheck); } } // List of messages for modules which are *not* to be installed. List missed = new ArrayList (); // List // Set of modules which will actually be installed. Set actual = new HashSet (); // Set actual.addAll (installed); int misscount; // this time around do { misscount = 0; Iterator it = actual.iterator (); while (it.hasNext ()) { ModuleDescription test = (ModuleDescription) it.next (); Set whatCanIStillUse = new HashSet (); // Set whatCanIStillUse.addAll (restored); whatCanIStillUse.addAll (actual); String miss = test.reasonWhyUnsatisfied ((ModuleDescription[]) whatCanIStillUse.toArray (new ModuleDescription[whatCanIStillUse.size()])); if (miss != null) { misscount++; it.remove (); missed.add (miss); } } } while (misscount > 0); /* if (missed.size () > 0) { TopManager.getDefault ().notify (new NotifyDescriptor.Message (new Object[] { getStringFormatted ("MSG_Some_Missed", "" + missed.size (), "" + actual.size (), "" + (actual.size () + missed.size ())), (String[]) missed.toArray (new String[missed.size ()]) })); } */ return resolveOrdering (actual); } /** Check whether specification versions are compatible. * True if older version is less-than-or-equal-to newer version acc. to a Dewey-decimal lexicographic compare. *

This algorithm should hopefully match that used by the Java Versioning specification * (which unfortunately does not make its algorithm public). * @param older the presumed older version * @param newer the presumed newer version * @throws IllegalModuleException in case of a number format error * @see Package */ public static boolean compatibleWith (String older, String newer) throws IllegalModuleException { if (older == null || newer == null) return false; StringTokenizer oldTok = new StringTokenizer (older, "."); // NOI18N StringTokenizer newTok = new StringTokenizer (newer, "."); // NOI18N while (oldTok.hasMoreTokens () || newTok.hasMoreTokens ()) { if (! newTok.hasMoreTokens ()) // E.g. 1.2.3 vs. 1.2: return false; if (! oldTok.hasMoreTokens ()) // E.g. 1.2 vs. 1.2.3: return true; String oldElt=oldTok.nextToken (); String newElt=newTok.nextToken (); int oldNum = 0, newNum = 0; try { oldNum = Integer.parseInt (oldElt); newNum = Integer.parseInt (newElt); } catch (NumberFormatException e) { // [PENDING] I18N IllegalModuleException ime = new IllegalModuleException (older + "<" + newer + "?"); // NOI18N ErrorManager.getDefault ().annotate (ime, e); throw ime; } if (oldNum < 0 || newNum < 0) return false; if (oldNum < newNum) // E.g. 1.2 vs. 1.3: return true; if (oldNum > newNum) // E.g. 1.3 vs. 1.2: return false; // Continue... } // Equal: return true; } // ----------------------------------------------------------------------------- // Private methods /** Check whether a possible code name is valid. */ static void checkCodeName (String codeName, boolean slashOK) throws IllegalModuleException { checkCodeName ("", codeName, slashOK); // NOI18N } /** Check whether a possible code name is valid. * @param moduleName name of module that contains this code name or "" if not known * @param codeName code name of the module */ static void checkCodeName (String moduleName, String codeName, boolean slashOK) throws IllegalModuleException { String base; int slash = codeName.indexOf ("/"); // NOI18N int release; if (slash == -1) { base = codeName; release = -1; } else { if (! slashOK) { throw new IllegalModuleException (getStringFormatted ("EXC_slash_not_permitted", codeName, moduleName)); // NOI18N } base = codeName.substring (0, slash); try { release = Integer.parseInt (codeName.substring (slash + 1)); } catch (NumberFormatException e) { throw new IllegalModuleException (getStringFormatted ("EXC_Non_Numeric_Release", codeName, moduleName)); // NOI18N } } for (int ch = 0; ch < base.length (); ch++) { char c = base.charAt (ch); if (! (Character.isJavaIdentifierPart (c) || c == '.')) throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Char_In_Code_Name", codeName, moduleName)); // NOI18N } } /** Check whether a possible specification version is valid. */ static void checkSpec (String spec) throws IllegalModuleException { StringTokenizer tok = new StringTokenizer (spec, ".", true); // NOI18N if (tok.countTokens () % 2 == 0) throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec", spec)); for (int i = 0; tok.hasMoreTokens (); i++) { String piece = tok.nextToken (); if (i % 2 == 0) { try { int x = Integer.parseInt (piece); if (x < 0) throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec", spec)); } catch (NumberFormatException nfe) { throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec", spec)); } } else { if (! ".".equals (piece)) // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Spec", spec)); } } } /** Creates description of the module grabbed from the jar archive manifest. * @return module description * @exception IllegalModuleException if there is error reading the description */ private void createDescription (String name, Manifest man) throws IllegalModuleException { Attributes attr = man.getMainAttributes (); // ----------------------------------------------------------------------------- // Global tags : required: TAG_MAGIC codeName = attr.getValue (TAG_MAGIC); if (codeName == null) { // not module throw new IllegalModuleException (getStringFormatted ("EXC_Not_A_Module", TAG_MAGIC.toString (), name)); // NOI18N } checkCodeName (name, codeName, true); moduleName = (String) NbBundle.getLocalizedValue (attr, TAG_NAME); if (moduleName == null) { String str = name; int from = str.lastIndexOf('/'); int till = str.lastIndexOf('.'); moduleName = str.substring((from == -1) ? 0 : from + 1, (till == -1) ? str.length() : till); } shortDescription = (String) NbBundle.getLocalizedValue (attr, TAG_SHORT_DESCRIPTION); longDescription = (String) NbBundle.getLocalizedValue (attr, TAG_LONG_DESCRIPTION); displayCategory = (String) NbBundle.getLocalizedValue (attr, TAG_CATEGORY); // ----------------------------------------------------------------------------- // Versioning tags specVersion = attr.getValue (TAG_SPEC_VERSION); if (specVersion != null) checkSpec (specVersion); implVersion = attr.getValue (TAG_IMPL_VERSION); // ----------------------------------------------------------------------------- // Dependency tags dependenciesSet = new HashSet (); parseDependencies (Dependency.TYPE_MODULE, attr.getValue (TAG_MODULE_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_PACKAGE, attr.getValue (TAG_PACKAGE_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_JAVA, attr.getValue (TAG_JAVA_DEPENDENCIES), dependenciesSet); parseDependencies (Dependency.TYPE_IDE, attr.getValue (TAG_IDE_DEPENDENCIES), dependenciesSet); // ----------------------------------------------------------------------------- // ModuleInstall tags mainClass = attr.getValue (TAG_MAIN); if (mainClass != null) { mainClassSer = mainClass.endsWith (".ser"); // NOI18N mainClass = createPackageName (mainClass); } layerResource = attr.getValue (TAG_LAYER); // [PENDING] check syntax: no initial slash, ... // ----------------------------------------------------------------------------- // other tags description = attr.getValue (TAG_DESCRIPTION); // iterator of Attributes Iterator en = man.getEntries ().entrySet ().iterator (); ArrayList v = new ArrayList (); while (en.hasNext ()) { Map.Entry entry = (Map.Entry)en.next (); ManifestSection s = ManifestSection.createSection ((String)entry.getKey (), (Attributes) entry.getValue (), this); if (s != null) { // if the entry describes section class v.add (s); } } sections = new ManifestSection[v.size ()]; v.toArray (sections); } /** Parse dependencies from tags. * @param type like Dependency.type * @param body actual text of tag body; if null, does nothing * @param deps set of dependencies to add to * @throws IllegalModuleException if in bad format */ private void parseDependencies (int type, String body, Set deps) throws IllegalModuleException { if (body == null) return; // First split on commas. StringTokenizer tok = new StringTokenizer (body, ","); // NOI18N if (! tok.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_No_Deps_Given", body)); // NOI18N while (tok.hasMoreTokens ()) { String onedep = tok.nextToken (); StringTokenizer tok2 = new StringTokenizer (onedep, " \t\n\r"); // NOI18N if (! tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_No_Name_In_Dep", onedep)); // NOI18N String name = tok2.nextToken (); int comparison; String version; if (tok2.hasMoreTokens ()) { String compthing = tok2.nextToken (); if (compthing.equals (">")) // NOI18N comparison = Dependency.COMPARE_SPEC; else if (compthing.equals ("=")) // NOI18N comparison = Dependency.COMPARE_IMPL; else throw new IllegalModuleException (getStringFormatted ("EXC_Unrec_Comp_Str", compthing)); // NOI18N if (! tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_Comp_Str_Without_Vers", onedep)); // NOI18N version = tok2.nextToken (); if (tok2.hasMoreTokens ()) throw new IllegalModuleException (getStringFormatted ("EXC_Garbage", onedep)); // NOI18N } else { comparison = Dependency.COMPARE_ANY; version = null; } deps.add (new Dependency (type, name, comparison, version, this)); } } /** Convert a class file name to a resource name suitable for Beans.instantiate. * @param name resource name of class file * @return class name without the .class/.ser extension, and using dots as package separator * @throws IllegalModuleException if the name did not have a valid extension, or originally contained dots outside the extension, etc. */ static String createPackageName(String name) throws IllegalModuleException { String clExt = ".class"; // NOI18N if (!name.endsWith(clExt)) { // try different extension clExt = ".ser"; // NOI18N } if (name.endsWith(clExt)) { String bareName = name.substring(0, name.length() - clExt.length()); if (bareName.length () == 0) // ".class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.charAt (0) == '/') // "/foo/bar.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.charAt (bareName.length () - 1) == '/') // "foo/bar/.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N if (bareName.indexOf ('.') != -1) // "foo.bar.class" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N return bareName.replace('/', '.'); } else { // "foo/bar" or "foo.bar" // NOI18N throw new IllegalModuleException (getStringFormatted ("EXC_Bad_Class_File_Name", name)); // NOI18N } } static String getStringFormatted (String key, Object[] args) { return NbBundle.getMessage (ModuleDescription.class, key, args); } static String getStringFormatted (String key, String arg1) { return NbBundle.getMessage (ModuleDescription.class, key, arg1); } static String getStringFormatted (String key, String arg1, String arg2) { return NbBundle.getMessage (ModuleDescription.class, key, arg1, arg2); } static String getStringFormatted (String key, String arg1, String arg2, String arg3) { return NbBundle.getMessage (ModuleDescription.class, key, arg1, arg2, arg3); } static String getStringFormatted (String key, String arg1, String arg2, String arg3, String arg4) { return getStringFormatted (key, new Object[] { arg1, arg2, arg3, arg4 }); } /** A type of dependency that the module can have on its environment. * @see ModuleDescription */ public static final class Dependency extends Object { /** Dependency on another module. */ public final static int TYPE_MODULE = 1; /** Dependency on a package. */ public final static int TYPE_PACKAGE = 2; /** Dependency on Java. */ public final static int TYPE_JAVA = 3; /** Dependency on the IDE. */ public final static int TYPE_IDE = 4; // Type of dependency. private int type; // Name of dependency. private String name; /** Comparison by specification version. * The actual version must equal or exceed the requested * version according to Dewey decimal numbering. * @see ModuleDescription#compatibleWith */ public final static int COMPARE_SPEC = 1; /** Comparison by implementation version. * The actual and requested versions must match exactly as strings. */ public final static int COMPARE_IMPL = 2; /** No comparison, just require the dependency to be present. */ public final static int COMPARE_ANY = 3; // Type of comparison. private int comparison; // Requested version of dependency. private String version; private ModuleDescription desc; /** Create a new dependency object. * @param type the type of dependency (one of {@link #TYPE_MODULE}, {@link #TYPE_PACKAGE}, {@link #TYPE_JAVA}, or {@link #TYPE_IDE}) * @param name the name of the dependency, e.g. com.mycom.myothermodule/2 or Java * @param comparison the type of comparison (one of {@link #COMPARE_SPEC}, {@link #COMPARE_IMPL}, or {@link #COMPARE_ANY}) * @param version the string version requested; may be null * @param desc the associated module description (for getting the classloader) * @throws IllegalModuleException if there was something invalid about the parameters */ Dependency (int type, String name, int comparison, String version, ModuleDescription desc) throws IllegalModuleException { this.type = type; this.name = name; this.comparison = comparison; this.version = version; this.desc = desc; // Sanity. if (comparison == COMPARE_SPEC) ModuleDescription.checkSpec (version); switch (type) { case TYPE_MODULE: ModuleDescription.checkCodeName (name, true); break; case TYPE_PACKAGE: { int idx = name.indexOf ('['); if (idx != -1) { if (idx > 0) { ModuleDescription.checkCodeName (name.substring (0, idx), false); } if (name.charAt (name.length () - 1) != ']') { throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_pkg_dep_not_close_bracket", toString ())); // NOI18N } ModuleDescription.checkCodeName (name.substring (idx + 1, name.length () - 1), false); } else { ModuleDescription.checkCodeName (name, false); } } break; case TYPE_JAVA: if (! (name.equals ("Java") || name.equals ("VM"))) // NOI18N throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_Bad_Java_Dep", toString ())); // NOI18N break; case TYPE_IDE: if (! (name.equals ("IDE"))) { // NOI18N int slash = name.indexOf ("/"); // NOI18N boolean ok; if (slash == -1) { ok = false; } else { try { Integer.parseInt (name.substring (slash + 1)); ok = true; } catch (NumberFormatException e) { ok = false; } } if (! ok) throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_Bad_IDE_Dep", toString ())); // NOI18N } if (comparison == COMPARE_ANY) throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_IDE_Dep_Uncompared", toString ())); // NOI18N break; default: throw new IllegalModuleException ("unknown type"); // NOI18N } } /** Get the type. * @return the type */ public int getType () { return type; } /** Get the name. * @return the name */ public String getName () { return name; } /** Get the comparison type. * @return the comparison type */ public int getComparison () { return comparison; } /** Get the version. * @return the version (may be null) */ public String getVersion () { return version; } /** Check whether this dependency is currently satisfied by the supplied parameters. * Note that for dependencies of type {@link #TYPE_PACKAGE}, the check is against whether * that package is loaded into the classloader used by the invoking class. *

The following system properties, with sample values, are used to check dependencies * of type {@link #TYPE_IDE}: * * * * *
NameDescriptionSample
org.openide.specification.versionSpecification version1.0.12
org.openide.versionImplementation versionbuild #999
org.openide.major.version"Code name", i.e. IDE incompatible releaseIDE/2
* @param otherModules other modules which this dependency might require * @return null if satisfied, else a message explaining why it was not satisfied */ public String checkForMiss (ModuleDescription[] otherModules) throws IllegalModuleException { switch (type) { case TYPE_MODULE: for (int i = 0; i < otherModules.length; i++) { ModuleDescription other = otherModules[i]; if (name.equals (other.getCodeName ())) { if (comparison == COMPARE_ANY) { return null; } else if (comparison == COMPARE_SPEC) { if (other.getSpecVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Module_Spec_None", other.getName ()); // NOI18N else if (! ModuleDescription.compatibleWith (version, other.getSpecVersion ())) return ModuleDescription.getStringFormatted ("MSG_Module_Spec_Bad", other.getName (), other.getSpecVersion (), version); // NOI18N else return null; } else { // COMPARE_IMPL if (other.getImplVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Module_Impl_None", other.getName ()); // NOI18N else if (! other.getImplVersion ().equals (version)) return ModuleDescription.getStringFormatted ("MSG_Module_Impl_Bad", other.getName (), other.getImplVersion (), version); // NOI18N else return null; } } } return ModuleDescription.getStringFormatted ("MSG_Module_None", name); // NOI18N case TYPE_PACKAGE: if (! (desc.cl instanceof PackageAccessibleClassLoader)) { throw new IllegalModuleException (ModuleDescription.getStringFormatted ("EXC_not_package_accessible", desc.cl.getClass ().getName ())); // NOI18N } String packageName, sampleName; int idx = name.indexOf ('['); if (idx == -1) { packageName = name; sampleName = null; } else if (idx == 0) { packageName = null; sampleName = name.substring (1, name.length () - 1); } else { packageName = name.substring (0, idx); sampleName = name.substring (idx + 1, name.length () - 1); if (sampleName.indexOf ('.') == -1) { // Unqualified class name; prefix it automatically. sampleName = packageName + '.' + sampleName; } } if (sampleName != null) { try { desc.cl.loadClass (sampleName); } catch (ClassNotFoundException cnfe) { if (packageName == null) { // This was all we were relying on, so it is an error. return ModuleDescription.getStringFormatted ("MSG_pkg_sample_not_found", sampleName); // NOI18N } // Else let the regular package check take care of it; // this was only run to enforce that the package defs were loaded. } } if (packageName != null) { Package pkg = ((PackageAccessibleClassLoader) desc.cl).getPackageAccessibly (packageName); if (pkg == null) return ModuleDescription.getStringFormatted ("MSG_Package_None", packageName); // NOI18N if (comparison == COMPARE_ANY) { return null; } else if (comparison == COMPARE_SPEC) { if (pkg.getSpecificationVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Package_Spec_None", pkg.getName ()); // NOI18N else if (! ModuleDescription.compatibleWith (version, pkg.getSpecificationVersion ().trim ())) return ModuleDescription.getStringFormatted ("MSG_Package_Spec_Bad", pkg.getName (), pkg.getSpecificationVersion (), version); // NOI18N else return null; } else { // COMPARE_IMPL if (pkg.getImplementationVersion () == null) return ModuleDescription.getStringFormatted ("MSG_Package_Impl_None", pkg.getName ()); // NOI18N else if (! pkg.getImplementationVersion ().trim ().equals (version)) return ModuleDescription.getStringFormatted ("MSG_Package_Impl_Bad", pkg.getName (), pkg.getImplementationVersion (), version); // NOI18N else return null; } } else { // Satisfied sample class. return null; } case TYPE_JAVA: // Assume all versions are set, and that COMPARE_ANY was not used. if (name.equals ("Java")) { // NOI18N if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, System.getProperty ("java.specification.version")) ? null : ModuleDescription.getStringFormatted ("MSG_Java_Spec", version); // NOI18N } else { // COMPARE_IMPL return System.getProperty ("java.version").equals (version) ? null : ModuleDescription.getStringFormatted ("MSG_Java_Impl", version); // NOI18N } } else { // VM if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, System.getProperty ("java.vm.specification.version")) ? null : ModuleDescription.getStringFormatted ("MSG_VM_Spec", version); // NOI18N } else { // COMPARE_IMPL return System.getProperty ("java.vm.version").equals (version) ? null : ModuleDescription.getStringFormatted ("MSG_VM_Impl", version); // NOI18N } } case TYPE_IDE: String IDEName = System.getProperty ("org.openide.major.version", "IDE"); String IDESpecVersion = System.getProperty ("org.openide.specification.version", "0.0"); String IDEImplVersion = System.getProperty ("org.openide.version", ""); if (! IDEName.equals (name)) return ModuleDescription.getStringFormatted ("MSG_IDE_Name", name, IDEName); // NOI18N if (comparison == COMPARE_SPEC) { return ModuleDescription.compatibleWith (version, IDESpecVersion) ? null : ModuleDescription.getStringFormatted ("MSG_IDE_Spec", version, IDESpecVersion); // NOI18N } else if (comparison == COMPARE_IMPL) { return version.equals (IDEImplVersion) ? null : ModuleDescription.getStringFormatted ("MSG_IDE_Impl", version, IDEImplVersion); // NOI18N } else { // COMPARE_ANY return null; } } throw new IllegalModuleException ("should never happen"); // NOI18N } public boolean equals (Object o) { if (o == null || ! (o instanceof Dependency)) return false; Dependency oo = (Dependency) o; return type == oo.type && name.equals (oo.name) && comparison == oo.comparison && ((version == null && oo.version == null) || (version != null && oo.version != null && version.equals (oo.version))); } public int hashCode () { // Jesse's super-scientific technique: return (type * 57) ^ name.hashCode () ^ (comparison * 231) ^ (version == null ? 111 : version.hashCode ()); } public String toString () { // Too late at night to bother with ChoiceFormat. return ModuleDescription.getStringFormatted ("DBG_Dependency_ToString", // NOI18N (type == TYPE_MODULE ? "Module" : // NOI18N type == TYPE_PACKAGE ? "Package" : // NOI18N type == TYPE_JAVA ? "Java" : // NOI18N type == TYPE_IDE ? "IDE" : "???"), // NOI18N name, comparison == COMPARE_ANY ? "" : // NOI18N ModuleDescription.getStringFormatted ("DBG_Dependency_Comparison", // NOI18N (comparison == COMPARE_SPEC ? ">" : // NOI18N comparison == COMPARE_IMPL ? "=" : "???"), // NOI18N version)); } } /** Interface to permit a couple of methods in ClassLoader to be made public. */ public static interface PackageAccessibleClassLoader { /** @see ClassLoader#getPackage */ public Package getPackageAccessibly (String name); /** @see ClassLoader#getPackages */ public Package[] getPackagesAccessibly (); } /** Empty instance for used if the module is not present in the jar file. * Note that default ModuleInstall instance does nothing, so that is fine. */ private final static ModuleInstall MODULE_NONE = new ModuleInstall (); }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.