Java tutorial
// // @(#)VariantManager.java 1.00 7/2002 // // Copyright 2002 Zachary DelProposto. All rights reserved. // Use is subject to license terms. // // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program 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 for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Or from http://www.gnu.org/ // package dip.world.variant; import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.jnlp.ServiceManager; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.io.FileUtils; import dip.gui.dialog.ErrorDialog; import dip.misc.Log; import dip.world.variant.data.MapGraphic; import dip.world.variant.data.SymbolPack; import dip.world.variant.data.Variant; import dip.world.variant.parser.XMLSymbolParser; import dip.world.variant.parser.XMLVariantParser; /** * Finds Variant-packs, which are of the format: * <ol> * <li>*Variant.zip</li> * <li>*Variants.zip</li> * <li>*Variant.jar</li> * <li>*Variants.jar</li> * </ol> * Faciliates loading of variant resources. Within the above file, the variants.xml * file is parsed to determine the required information. * <p> * Also finds all SymbolPacks, which end in: * <ol> * <li>*Symbols.zip</li> * <li>*Symbols.jar</li> * </ol> * <p> * * TODO: deconflict code may work better if we include the preceding "/" before * the .jar name.<br> * <br> */ public class VariantManager { /** Version Constant representing the most recent version of a Variant or SymbolPack */ public static final float VERSION_NEWEST = -1000.0f; /** Version Constant representing the most oldest version of a Variant or SymbolPack */ public static final float VERSION_OLDEST = -2000.0f; // variant constants private static final List<String> VARIANT_EXTENSIONS = new ArrayList<String>() { /** * */ private static final long serialVersionUID = 1L; { add("Variant.zip"); add("Variants.zip"); add("Variant.jar"); add("Variants.jar"); } }; private static final String VARIANT_FILE_NAME = "variants.xml"; // symbol constants private static final List<String> SYMBOL_EXTENSIONS = new ArrayList<String>() { /** * */ private static final long serialVersionUID = 1L; { add("Symbols.zip"); add("Symbols.jar"); } }; private static final String SYMBOL_FILE_NAME = "symbols.xml"; // class variables private static VariantManager vm = null; // instance variables private final boolean isInWebstart; private HashMap<String, MapRec> variantMap = null; // map of lowercased Variant names to MapRec objects (which contain VRecs) private HashMap<String, MapRec> symbolMap = null; // lowercase symbol names to MapRec objects (which contain SPRecs) // cached variables to enhance performance of getResource() methods private transient List<Variant> variants = new ArrayList<Variant>(); // The sorted Variant list private transient List<SymbolPack> symbolPacks = new ArrayList<SymbolPack>(); // The sorted SymbolPack list private transient URLClassLoader currentUCL = null; // The current class loader private transient URL currentPackageURL = null; // The current class loader URL /** * Initiaize the VariantManager. * <p> * An exception is thrown if no File paths are specified. A "." may be used * to specify th ecurrent directory. * <p> * Loaded XML may be validated if the isValidating flag is set to true. * */ public static synchronized void init(final List<File> searchPaths, boolean isValidating) throws javax.xml.parsers.ParserConfigurationException, NoVariantsException { long ttime = System.currentTimeMillis(); long vptime = ttime; Log.println("VariantManager.init()"); if (searchPaths == null || searchPaths.isEmpty()) { throw new IllegalArgumentException(); } if (vm != null) { // perform cleanup vm.variantMap.clear(); vm.variants = new ArrayList<Variant>(); vm.currentUCL = null; vm.currentPackageURL = null; vm.symbolPacks = new ArrayList<SymbolPack>(); vm.symbolMap.clear(); } vm = new VariantManager(); // find plugins, create plugin loader final List<URL> pluginURLs = vm.searchForFiles(searchPaths, VARIANT_EXTENSIONS); // setup document builder DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { // this may improve performance, and really only apply to Xerces dbf.setAttribute("http://apache.org/xml/features/dom/defer-node-expansion", Boolean.FALSE); dbf.setAttribute("http://apache.org/xml/properties/input-buffer-size", new Integer(4096)); dbf.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", Boolean.FALSE); } catch (Exception e) { Log.println("VM: Could not set XML feature.", e); } dbf.setValidating(isValidating); dbf.setCoalescing(false); dbf.setIgnoringComments(true); // setup variant parser XMLVariantParser variantParser = new XMLVariantParser(dbf); // for each plugin, attempt to find the "variants.xml" file inside. // if it does not exist, we will not load the file. If it does, we will parse it, // and associate the variant with the URL in a hashtable. for (final URL pluginURL : pluginURLs) { URLClassLoader urlCL = new URLClassLoader(new URL[] { pluginURL }); URL variantXMLURL = urlCL.findResource(VARIANT_FILE_NAME); if (variantXMLURL != null) { String pluginName = getFile(pluginURL); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; try { is = new BufferedInputStream(variantXMLURL.openStream()); variantParser.parse(is, pluginURL); final List<Variant> variants = variantParser.getVariants(); // add variants; variants with same name (but older versions) are // replaced with same-name newer versioned variants for (final Variant variant : variants) { addVariant(variant, pluginName, pluginURL); } } catch (IOException e) { // display error dialog ErrorDialog.displayFileIO(null, e, pluginURL.toString()); } catch (org.xml.sax.SAXException e) { // display error dialog ErrorDialog.displayGeneral(null, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } } } // if we are in webstart, search for variants within webstart jars Enumeration<URL> enum2 = null; ClassLoader cl = null; if (vm.isInWebstart) { cl = vm.getClass().getClassLoader(); try { enum2 = cl.getResources(VARIANT_FILE_NAME); } catch (IOException e) { enum2 = null; } if (enum2 != null) { while (enum2.hasMoreElements()) { URL variantURL = enum2.nextElement(); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; String pluginName = getWSPluginName(variantURL); try { is = new BufferedInputStream(variantURL.openStream()); variantParser.parse(is, variantURL); final List<Variant> variants = variantParser.getVariants(); // add variants; variants with same name (but older versions) are // replaced with same-name newer versioned variants for (final Variant variant : variants) { addVariant(variant, pluginName, variantURL); } } catch (IOException e) { // display error dialog ErrorDialog.displayFileIO(null, e, variantURL.toString()); } catch (org.xml.sax.SAXException e) { // display error dialog ErrorDialog.displayGeneral(null, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } } } // if(enum2 != null) } // check: did we find *any* variants? Throw an exception. if (vm.variantMap.isEmpty()) { StringBuffer msg = new StringBuffer(256); msg.append("No variants found on path: "); for (final File searchPath : searchPaths) { msg.append(searchPath); msg.append("; "); } throw new NoVariantsException(msg.toString()); } Log.printTimed(vptime, "VariantManager: variant parsing time: "); ///////////////// SYMBOLS ///////////////////////// // now, parse symbol packs XMLSymbolParser symbolParser = new XMLSymbolParser(dbf); // find plugins, create plugin loader final List<URL> pluginURLs2 = vm.searchForFiles(searchPaths, SYMBOL_EXTENSIONS); // for each plugin, attempt to find the "variants.xml" file inside. // if it does not exist, we will not load the file. If it does, we will parse it, // and associate the variant with the URL in a hashtable. for (final URL pluginURL : pluginURLs2) { URLClassLoader urlCL = new URLClassLoader(new URL[] { pluginURL }); URL symbolXMLURL = urlCL.findResource(SYMBOL_FILE_NAME); if (symbolXMLURL != null) { String pluginName = getFile(pluginURL); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; try { is = new BufferedInputStream(symbolXMLURL.openStream()); symbolParser.parse(is, pluginURL); addSymbolPack(symbolParser.getSymbolPack(), pluginName, pluginURL); } catch (IOException e) { // display error dialog ErrorDialog.displayFileIO(null, e, pluginURL.toString()); } catch (org.xml.sax.SAXException e) { // display error dialog ErrorDialog.displayGeneral(null, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } } } // if we are in webstart, search for variants within webstart jars enum2 = null; cl = null; if (vm.isInWebstart) { cl = vm.getClass().getClassLoader(); try { enum2 = cl.getResources(SYMBOL_FILE_NAME); } catch (IOException e) { enum2 = null; } if (enum2 != null) { while (enum2.hasMoreElements()) { URL symbolURL = enum2.nextElement(); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; String pluginName = getWSPluginName(symbolURL); try { is = new BufferedInputStream(symbolURL.openStream()); symbolParser.parse(is, symbolURL); addSymbolPack(symbolParser.getSymbolPack(), pluginName, symbolURL); } catch (IOException e) { // display error dialog ErrorDialog.displayFileIO(null, e, symbolURL.toString()); } catch (org.xml.sax.SAXException e) { // display error dialog ErrorDialog.displayGeneral(null, e); } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } } } } // if(enum2 != null) } // if(isInWebStart) // check: did we find *any* symbol packs? Throw an exception. if (vm.symbolMap.isEmpty()) { StringBuffer msg = new StringBuffer(256); msg.append("No SymbolPacks found on path: "); for (final File searchPath : searchPaths) { msg.append(searchPath); msg.append("; "); } throw new NoVariantsException(msg.toString()); } Log.printTimed(ttime, "VariantManager: total parsing time: "); }// init() /** * Returns the known Variants. If multiple versions of a Variant * exist, only the latest version is returned. The list is * sorted in alphabetic order. */ public static synchronized List<Variant> getVariants() { checkVM(); if (vm.variants.size() != vm.variantMap.size()) { // note that we need to avoid putting duplicates // into the array. // final List<Variant> list = new ArrayList<Variant>(); // list of variants final Set<MapRec> set = new HashSet<MapRec>(); // a set of MapRecs set.addAll(vm.variantMap.values()); // fill variant list with variants. for (final MapRec mr : set) { MapRecObj mro = mr.get(VERSION_NEWEST); assert (mro != null); if (mro instanceof VRec) { list.add(((VRec) mro).getVariant()); } } Collections.sort(list); vm.variants = new ArrayList<Variant>(); } return vm.variants; }// getVariants() /** * Returns the known SymbolPacks. If multiple versions of a SymbolPack * exist, only the latest version is returned. The list is * sorted in alphabetic order. */ public static synchronized List<SymbolPack> getSymbolPacks() { checkVM(); if (vm.symbolPacks.size() != vm.symbolMap.size()) { // avoid putting duplicates into the array. final ArrayList<SymbolPack> list = new ArrayList<SymbolPack>(); // list of SymbolPacks final Set<MapRec> set = new HashSet<MapRec>(); // a set of MapRecs set.addAll(vm.symbolMap.values()); // fill variant list with variants. for (final MapRec mr : set) { MapRecObj mro = mr.get(VERSION_NEWEST); assert (mro != null); if (mro instanceof SPRec) { list.add(((SPRec) mro).getSymbolPack()); } } Collections.sort(list); vm.symbolPacks = new ArrayList<SymbolPack>(list); } return vm.symbolPacks; }// getSymbolPacks() /** * Finds Variant with the given name, or null if no Variant is found. * Attempts to find the version specified. Versions must be > 0.0f or * the version constants VERSION_NEWEST or VERSION_OLDEST. * <p> * Note: Name is <b>not</b> case-sensitive. * */ public static synchronized Variant getVariant(String name, float version) { checkVM(); MapRec mr = vm.variantMap.get(name.toLowerCase()); if (mr != null) { return ((VRec) mr.get(version)).getVariant(); } return null; }// getVariant() /** * Finds SymbolPack with the given name, or null if no SymbolPack is found. * Attempts to find the version specified. Versions must be > 0.0f or * the version constants VERSION_NEWEST or VERSION_OLDEST. * <p> * Note: Name is <b>not</b> case-sensitive. * */ public static synchronized SymbolPack getSymbolPack(String name, float version) { checkVM(); if (name == null) { return null; } MapRec mr = vm.symbolMap.get(name.toLowerCase()); if (mr != null) { return ((SPRec) mr.get(version)).getSymbolPack(); } return null; }// getSymbolPack() /** * Obtains a SymbolPack via the following criteria: * <ol> * <li>If matching SymbolPack name and Version found, that is returned; otherwise</li> * <li>Returns SymbolPack of same name but of the newest available version; otherwise</li> * <li>Returns the newest available SymbolPack preferred by the MapGraphic (if set); otherwise</li> * <li>Returns the first SymbolPack in the list of SymbolPacks.</li> * </ol> * <p> * Thus it is assured that a SymbolPack will always be obtained. */ public static synchronized SymbolPack getSymbolPack(MapGraphic mg, String symbolPackName, final float symbolPackVersion) { if (mg == null) { throw new IllegalArgumentException(); } // safety: // if version is invalid (< 0.0f), convert to VERSION_NEWEST // automatically. Log this method, though float spVersion = symbolPackVersion; if (spVersion <= 0.0f) { Log.println( "WARNING: VariantManager.getSymbolPack() called with symbolPackVersion of <= 0.0f. Check parameters."); spVersion = VERSION_NEWEST; } SymbolPack sp = getSymbolPack(symbolPackName, spVersion); if (sp == null) { sp = getSymbolPack(symbolPackName, VERSION_NEWEST); if (sp == null && mg.getPreferredSymbolPackName() != null) { sp = getSymbolPack(mg.getPreferredSymbolPackName(), VERSION_NEWEST); } if (sp == null) { sp = getSymbolPacks().get(0); } } return sp; }// getSymbolPack() /** * Returns true if the desired version was found. Version must * be a positive floating point value, or, a defined constant. * Returns false if the version is not available or the variant * is not found. */ public static boolean hasVariantVersion(final String name, final float version) { return (getVariant(name, version) != null); }// hasVariantVersion() /** * Returns true if the desired version was found. Version must * be a positive floating point value, or, a defined constant. * Returns false if the version is not available or the SymbolPack * is not found. */ public static boolean hasSymbolPackVersion(final String name, final float version) { return (getSymbolPack(name, version) != null); }// hasVariantVersion() /** * Returns the versions of a variant that are available. * If the variant is not found, a zero-length array is returned. */ public synchronized static float[] getVariantVersions(final String name) { checkVM(); MapRec mr = vm.variantMap.get(name.toLowerCase()); if (mr != null) { return (mr.getVersions()); } return new float[0]; }// getVariantVersions() /** * Returns the versions of a SymbolPack that are available. * If the SymbolPack is not found, a zero-length array is returned. */ public synchronized static float[] getSymbolPackVersions(String name) { checkVM(); MapRec mr = vm.symbolMap.get(name.toLowerCase()); if (mr != null) { return (mr.getVersions()); } return new float[0]; }// getSymbolPackVersions() /** Ensures version is positive OR VERSION_NEWEST or VERSION_OLDEST */ private static void checkVersionConstant(float version) { if (version <= 0.0f && (version != VERSION_NEWEST && version != VERSION_OLDEST)) { throw new IllegalArgumentException("invalid version or version constant: " + version); } }// checkVersionConstant() /** * Gets a specific resource for a Variant or a SymbolPack, given a URL to * the package and a reference URI. Threadsafe. * <p> * Typically, getResource(Variant, URI) or getResource(SymbolPack, URI) is * preferred to this method. * */ public static synchronized URL getResource(URL packURL, URI uri) { // ensure we have been initialized... checkVM(); // if we are in webstart, assume that this is a webstart jar. if (vm.isInWebstart) { URL url = getWSResource(packURL, uri); // if cannot get it, fall through. if (url != null) { return url; } } // if URI has a defined scheme, convert to a URL (if possible) and return it. if (uri.getScheme() != null) { try { return uri.toURL(); } catch (MalformedURLException e) { return null; } } // resolve & load. URLClassLoader classLoader = getClassLoader(packURL); return classLoader.findResource(uri.toString()); }// getResource() /** * Gets a specific resource by properly resolving the URI * to this Variant. Null arguments are illegal. Returns * null if the resource cannot be resolved. Threadsafe. */ public static URL getResource(Variant variant, URI uri) { if (variant == null) { throw new IllegalArgumentException(); } return getResource(getVRec(variant), uri); }// getResource() /** * Gets a specific resource by properly resolving the URI * to this SymbolPack. Null arguments are illegal. Returns * null if the resource cannot be resolved. Threadsafe. */ public static URL getResource(SymbolPack symbolPack, URI uri) { if (symbolPack == null) { throw new IllegalArgumentException(); } return getResource(getSPRec(symbolPack), uri); }// getResource() /** * Gets the URL to the Variant package (plugin). This is typically * only needed in special circumstances. Returns null if null variant * input OR variant not found. * <p> * Note that this will always return a URL with a JAR prefix. * e.g.: <code>jar:http:/the.location/ajar.zip!/</code> * or <code>jar:file:/c:/plugins/ajar.zip!/</code> */ public static URL getVariantPackageJarURL(Variant variant) { if (variant != null) { VRec vr = getVRec(variant); if (vr != null) { assert (vr.getURL() != null); URL url = vr.getURL(); String txtUrl = url.toString(); if (txtUrl.startsWith("jar:")) { return url; } else { StringBuffer sb = new StringBuffer(txtUrl.length() + 8); sb.append("jar:"); sb.append(txtUrl); sb.append("!/"); try { return new URL(sb.toString()); } catch (MalformedURLException e) { Log.println("Could not convert ", url, " to a JAR url."); Log.println("Exception: ", e); } } } } return null; }// getVariantPackageURL() /** * Internal getResource() implementation */ private static synchronized URL getResource(MapRecObj mro, URI uri) { // ensure we have been initialized... checkVM(); assert (mro != null); if (uri == null) { throw new IllegalArgumentException("null URI"); } // if we are in webstart, assume that this is a webstart jar. if (vm.isInWebstart) { URL url = getWSResource(mro, uri); // if cannot get it, fall through. if (url != null) { return url; } } // if URI has a defined scheme, convert to a URL (if possible) and return it. if (uri.getScheme() != null) { try { return uri.toURL(); } catch (MalformedURLException e) { return null; } } // find the URL if (mro.getURL() != null) { return getClassLoader(mro.getURL()).findResource(uri.toString()); } return null; }// getResource() /** Ensures that we have initialized the VariantManager */ private static void checkVM() { if (vm == null) { throw new IllegalArgumentException("not initialized"); } }// checkVM() /** Singleton */ private VariantManager() { variantMap = new HashMap<String, MapRec>(53); symbolMap = new HashMap<String, MapRec>(17); isInWebstart = isInWebstart(); }// VariantManager() /** * Searches the given paths for files ending with the given extension(s). * Returns URLs. * */ private List<URL> searchForFiles(final List<File> searchPaths, final List<String> extensions) { final List<URL> urlList = new ArrayList<URL>(); for (final File searchPath : searchPaths) { final Collection<File> list = FileUtils.listFiles(searchPath, null, false); // internal error if list == null; means that // searchPaths[] is not a directory! if (list != null) { for (final File file : list) { String fileName = file.getPath(); if (checkFileName(fileName, extensions)) { try { urlList.add(file.toURL()); } catch (java.net.MalformedURLException e) { // do nothing; we just won't add it } } } } } return urlList; }// searchForFiles() /** Returns the URLClassLoader for a given URL, or creates a new one.... */ private static URLClassLoader getClassLoader(URL packageURL) { // WARNING: this method is not (itself) threadsafe if (packageURL == null) { throw new IllegalArgumentException(); } // see if a classloader for this url already exists (cache of 1) if (packageURL.equals(vm.currentPackageURL)) { return vm.currentUCL; } vm.currentUCL = new URLClassLoader(new URL[] { packageURL }); vm.currentPackageURL = packageURL; return vm.currentUCL; }// getClassLoader() /** Returns the "file" part of the URL; e.g.: x/y/z.jar, returns z.jar */ private static String getFile(URL url) { String s = url.toString(); return s.substring(s.lastIndexOf("/") + 1, s.length()); }// getFile() /** Get the webstart plugin name */ private static String getWSPluginName(URL url) { final String s = url.toString(); final int idxExclam = s.indexOf('!'); if (idxExclam >= 0) { return s.substring(s.lastIndexOf("/", idxExclam) + 1, idxExclam); } else { return s; } }// getWSPluginName() /** Checks if the fileName ends with an allowed extension; if so, returns true. */ private boolean checkFileName(String fileName, final List<String> extensions) { for (final String extension : extensions) { if (fileName.endsWith(extension)) { return true; } } return false; }// checkFileName() /** See if we are running under Java Webstart */ private boolean isInWebstart() { // this is not the most optimal code. try { ServiceManager.lookup("javax.jnlp.BasicService"); return true; } catch (Throwable e) { return false; } }// isInWebstart() /** * Get a resource for a variant. This uses the variantName to * deconflict, if multiple resources exist with the same name. * <p> * Conflict occur when plugins are loaded under the same ClassLoader, * because variant plugin namespace is not unique. * <p> * This primarily applies to Webstart resources */ private static URL getWSResource(MapRecObj mro, URI uri) { assert (vm.isInWebstart); if (uri == null) { return null; } ClassLoader cl = vm.getClass().getClassLoader(); if (mro != null) { Enumeration<URL> enum2 = null; try { enum2 = cl.getResources(uri.toString()); } catch (IOException e) { return null; } while (enum2.hasMoreElements()) { URL url = enum2.nextElement(); // deconflict. Note that this is not, and cannot be, foolproof; // due to name-mangling by webstart. For example, if two plugins // called "test" and "Supertest" exist, test may find the data // file within Supertest because indexOf(test, SuperTest) >= 0 // // however, if we can get the mangled name and set it as the // 'pluginName', we can be foolproof. // String lcPath = url.getPath(); String search = mro.getPluginName() + "!"; if (lcPath.indexOf(search) >= 0) { return url; } } } return null; }// getWSResource() /** * Get a resource for a variant. This uses the variantName to * deconflict, if multiple resources exist with the same name. * <p> * Conflict occur when plugins are loaded under the same ClassLoader, * because variant plugin namespace is not unique. * <p> * This primarily applies to Webstart resources */ private static URL getWSResource(URL packURL, URI uri) { /* NOTE: this method is used by getResource(URL, URI), which is chiefly used by VariantManager and associated parsers; a VariantRecord has not yet been created. So we cannot use that; the internal logic here is slightly different. */ assert (vm.isInWebstart); ClassLoader cl = vm.getClass().getClassLoader(); String deconflictName = getWSPluginName(packURL); Enumeration<URL> enum2 = null; try { enum2 = cl.getResources(uri.toString()); } catch (IOException e) { return null; } while (enum2.hasMoreElements()) { URL url = enum2.nextElement(); // deconflict. Note that this is not, and cannot be, foolproof; // due to name-mangling by webstart. For example, if two plugins // called "test" and "Supertest" exist, test may find the data // file within Supertest because indexOf(test, SuperTest) >= 0 // // however, if we can get the mangled name and set it as the // 'pluginName', we can be foolproof. // String lcPath = url.getPath(); if (lcPath.indexOf(deconflictName) >= 0) { return url; } } return null; }// getWSResource() /** * Adds a Variant. If the variant already exists with the same * name, checks the version. If the same version already exists, * an exception is thrown. If not, the new version is also added. * If we are in Web Start, however, no exception is thrown. * <p> * All names and aliases are mapped to the MapRec, not the VRec. * When mapping an alias, if it corresponds to a DIFFERENT * MapRec, an exception is thrown (this represents a non-unique * alias). * <p> * NOTE: names and aliases are always mapped in all lower case. */ private static void addVariant(Variant v, String pluginName, URL pluginURL) throws IOException { if (v == null || pluginName == null || pluginURL == null) { throw new IllegalArgumentException(); } VRec vr = new VRec(); vr.setPluginName(pluginName); vr.setURL(pluginURL); vr.setVariant(v); final String vName = v.getName().toLowerCase(); // see if we are mapped to a MapRec already. // MapRec mapRec = vm.variantMap.get(vName); if (mapRec == null) { // not yet mapped! let's map it. mapRec = new MapRec(vr); vm.variantMap.put(vName, mapRec); } else { // we are mapped. See if this version has been added. // If not, we'll add it. if (!mapRec.add(vr) && !vm.isInWebstart) { final VRec vrec2 = (VRec) mapRec.get(v.getVersion()); final Variant v2 = vrec2.getVariant(); // 2 variants with identical versions! we are confused! // try to provide as much helpful info as possible. throw new IOException("Two variants with identical version numbers have been found.\n" + "Conflicting version: " + v.getVersion() + "\n" + "Variant 1: name=" + v.getName() + "; pluginName = " + vr.getPluginName() + "; pluginURL = " + vr.getURL() + "\n" + "Variant 2: name=" + v2.getName() + "; pluginName = " + vrec2.getPluginName() + "; pluginURL = " + vrec2.getURL() + "\n"); } } // map the aliases and/or check that aliases refer to the // same MapRec (this prevents two different Variants with the same // alias from causing a subtle error) // final String[] aliases = v.getAliases(); for (int idx = 0; idx < aliases.length; idx++) { // not if it's "" though... if (!"".equals(aliases[idx])) { final String alias = aliases[idx].toLowerCase(); MapRec testMapRec = vm.variantMap.get(alias); if (testMapRec == null) { // add alias vm.variantMap.put(alias, mapRec); } else if (testMapRec != mapRec) { // ERROR! incorrect alias map final Variant v2 = ((VRec) testMapRec.get(VERSION_OLDEST)).getVariant(); throw new IOException("Two variants have a conflicting (non-unique) alias.\n" + "Variant 1: name=" + v.getName() + "; version=" + v.getVersion() + "; pluginName = " + vr.getPluginName() + "; pluginURL = " + vr.getURL() + "\n" + "Variant 2: name=" + v2.getName() + "; (must check all variants with this name)\n"); } // else {} : we are already mapped correctly. Nothing to change. } } }// addVariant() /** * Adds a SymbolPack. If the SymbolPack already exists with the same * name, checks the version. If the same version already exists, * an exception is thrown. If not, the new version is also added. * <p> * SymbolPacks do not support aliases. * <p> * Names are always mapped in all lower case. */ private static void addSymbolPack(SymbolPack sp, String pluginName, URL pluginURL) throws IOException { if (sp == null || pluginName == null || pluginURL == null) { throw new IllegalArgumentException(); } SPRec spRec = new SPRec(); spRec.setPluginName(pluginName); spRec.setURL(pluginURL); spRec.setSymbolPack(sp); final String spName = sp.getName().toLowerCase(); // see if we are mapped to a MapRec already. // MapRec mapRec = vm.symbolMap.get(spName); if (mapRec == null) { // not yet mapped! let's map it. mapRec = new MapRec(spRec); vm.symbolMap.put(spName, mapRec); } else { // we are mapped. See if this version has been added. if (!mapRec.add(spRec) && !vm.isInWebstart) { SPRec spRec2 = (SPRec) mapRec.get(sp.getVersion()); final SymbolPack sp2 = spRec2.getSymbolPack(); if (sp2.getVersion() == sp.getVersion()) { // 2 SymbolPacks with identical versions! we are confused! // try to provide as much helpful info as possible. throw new IOException("Two SymbolPcaks with identical version numbers have been found.\n" + "Conflicting version: " + sp.getVersion() + "\n" + "SymbolPack 1: name=" + sp.getName() + "; pluginName = " + spRec.getPluginName() + "; pluginURL = " + spRec.getURL() + "\n" + "SymbolPack 2: name=" + sp2.getName() + "; pluginName = " + spRec2.getPluginName() + "; pluginURL = " + spRec2.getURL() + "\n"); } } // we haven't been added (not a dupe); add mapRec.add(spRec); } }// addSymbolPack() /** Gets the VRec associated with a Variant (via name and version) */ private static VRec getVRec(Variant v) { MapRec mapRec = vm.variantMap.get(v.getName().toLowerCase()); return (VRec) mapRec.get(v.getVersion()); }// getVRec() /** Gets the SPRec associated with a SymbolPack (via name and version) */ private static SPRec getSPRec(SymbolPack sp) { MapRec mapRec = vm.symbolMap.get(sp.getName().toLowerCase()); return (SPRec) mapRec.get(sp.getVersion()); }// getSPRec() /** The value which is stored within the name mapping */ private static class MapRec { private final List<MapRecObj> list = new ArrayList<MapRecObj>(2); // this constructor prevents us from having an empty list. public MapRec(MapRecObj obj) { if (obj == null) { throw new IllegalArgumentException(); } list.add(obj); }// MapRec() /** * Adds the MapRecObj to this MapRec, but only if it is of * a unique version. If it is not, returns false. Otherwise, * the MapRecObj is added and returns true. */ public boolean add(MapRecObj obj) { for (final MapRecObj temp : list) { if (temp.getVersion() == obj.getVersion()) { return false; } } list.add(obj); return true; }// add() /** Get all available versions */ public float[] getVersions() { final float[] versions = new float[list.size()]; for (int i = 0; i < list.size(); i++) { MapRecObj mro = list.get(i); versions[i] = mro.getVersion(); } return versions; }// getVersions() /** * Get the desired version. Supports version constants. * Returns null if version not found (shouldn't occur if * version constants used, and at least one element exists) */ public MapRecObj get(final float version) { checkVersionConstant(version); final int size = list.size(); // typical-case if (size == 1 && (version == VERSION_OLDEST || version == VERSION_NEWEST)) { return list.get(0); } MapRecObj selected = null; for (int i = 0; i < size; i++) { MapRecObj mro = list.get(i); selected = (selected == null) ? mro : selected; if ((version == VERSION_OLDEST && mro.getVersion() < selected.getVersion()) || (version == VERSION_NEWEST && mro.getVersion() > selected.getVersion())) { selected = mro; } else if (mro.getVersion() == version) { return mro; } } return selected; }// get() }// inner class VMRec /** MapRec stores a list of ObjRecs */ private static abstract class MapRecObj { private URL fileURL; private String pluginName; public String getPluginName() { return pluginName; } public void setPluginName(String value) { pluginName = value; } public URL getURL() { return fileURL; } public void setURL(URL value) { fileURL = value; } public abstract float getVersion(); }// inner class ObjRec /** An ObjRec for Variant objects */ private static class VRec extends MapRecObj { private Variant variant; public Variant getVariant() { return variant; } public void setVariant(Variant value) { variant = value; } @Override public float getVersion() { return variant.getVersion(); } }// inner class VRec /** An ObjRec for SymbolPack objects */ private static class SPRec extends MapRecObj { private SymbolPack symbolPack; public SymbolPack getSymbolPack() { return symbolPack; } public void setSymbolPack(SymbolPack value) { symbolPack = value; } @Override public float getVersion() { return symbolPack.getVersion(); } }// inner class SPRec }// class VariantManager