// // @(#)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 dip.world.variant.data.*; import dip.world.variant.parser.*; //import dip.gui.dialog.ErrorDialog; import dip.misc.Log; import java.util.*; import java.net.URLClassLoader; import java.net.URL; import java.net.URI; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.io.*; import org.xml.sax.*; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; //import javax.jnlp.*; import java.util.*; /** * 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 String VARIANT_EXTENSIONS[] = { "Variant.zip", "Variants.zip", "Variant.jar", "Variants.jar" }; private static final String VARIANT_FILE_NAME = "variants.xml"; // symbol constants private static final String SYMBOL_EXTENSIONS[] = { "Symbols.zip", "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 variantMap = null; // map of lowercased Variant names to MapRec objects (which contain VRecs) private HashMap symbolMap = null; // lowercase symbol names to MapRec objects (which contain SPRecs) // cached variables to enhance performance of getResource() methods private transient Variant[] variants = new Variant[0]; // The sorted Variant list private transient SymbolPack[] symbolPacks = new SymbolPack[0]; // 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(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.length == 0) { throw new IllegalArgumentException(); } if(vm != null) { // perform cleanup vm.variantMap.clear(); vm.variants = new Variant[0]; vm.currentUCL = null; vm.currentPackageURL = null; vm.symbolPacks = new SymbolPack[0]; vm.symbolMap.clear(); } vm = new VariantManager(); // find plugins, create plugin loader 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(int i=0; i<pluginURLs.length; i++) { URLClassLoader urlCL = new URLClassLoader(new URL[]{ pluginURLs[i] }); URL variantXMLURL = urlCL.findResource(VARIANT_FILE_NAME); if(variantXMLURL != null) { String pluginName = getFile( pluginURLs[i] ); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; try { is = new BufferedInputStream(variantXMLURL.openStream()); variantParser.parse(is, pluginURLs[i]); Variant[] variants = variantParser.getVariants(); // add variants; variants with same name (but older versions) are // replaced with same-name newer versioned variants for(int vi=0; vi<variants.length; vi++) { addVariant(variants[vi], pluginName, pluginURLs[i]); } } catch(IOException e) { e.printStackTrace(); // display error dialog //ErrorDialog.displayFileIO(null, e, pluginURLs[i].toString()); } catch(org.xml.sax.SAXException e) { e.printStackTrace(); // 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 enum1 = null; ClassLoader cl = null; if(vm.isInWebstart) { cl = vm.getClass().getClassLoader(); try { enum1 = cl.getResources(VARIANT_FILE_NAME); } catch(IOException e) { enum1 = null; } if(enum1 != null) { while(enum1.hasMoreElements()) { URL variantURL = (URL) enum1.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); Variant[] variants = variantParser.getVariants(); // add variants; variants with same name (but older versions) are // replaced with same-name newer versioned variants for(int vi=0; vi<variants.length; vi++) { addVariant(variants[vi], pluginName, variantURL); } } catch(IOException e) { e.printStackTrace(); // display error dialog //ErrorDialog.displayFileIO(null, e, variantURL.toString()); } catch(org.xml.sax.SAXException e) { e.printStackTrace(); // display error dialog //ErrorDialog.displayGeneral(null, e); } finally { if(is != null) { try { is.close(); } catch (IOException e) {} } } } }// if(enum != 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(int i=0; i<searchPaths.length; i++) { msg.append(searchPaths[i]); 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 pluginURLs = 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(int i=0; i<pluginURLs.length; i++) { URLClassLoader urlCL = new URLClassLoader(new URL[]{ pluginURLs[i] }); URL symbolXMLURL = urlCL.findResource(SYMBOL_FILE_NAME); if(symbolXMLURL != null) { String pluginName = getFile( pluginURLs[i] ); // parse variant description file, and create hash entry of variant object -> URL InputStream is = null; try { is = new BufferedInputStream(symbolXMLURL.openStream()); symbolParser.parse(is, pluginURLs[i]); addSymbolPack(symbolParser.getSymbolPack(), pluginName, pluginURLs[i]); } catch(IOException e) { e.printStackTrace(); // display error dialog //ErrorDialog.displayFileIO(null, e, pluginURLs[i].toString()); } catch(org.xml.sax.SAXException e) { e.printStackTrace(); // 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 enum1 = null; cl = null; if(vm.isInWebstart) { cl = vm.getClass().getClassLoader(); try { enum1 = cl.getResources(SYMBOL_FILE_NAME); } catch(IOException e) { enum1 = null; } if(enum1 != null) { while(enum1.hasMoreElements()) { URL symbolURL = (URL) enum1.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) { e.printStackTrace(); // display error dialog //ErrorDialog.displayFileIO(null, e, symbolURL.toString()); } catch(org.xml.sax.SAXException e) { e.printStackTrace(); // display error dialog //ErrorDialog.displayGeneral(null, e); } finally { if(is != null) { try { is.close(); } catch (IOException e) {} } } } }// if(enum != 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(int i=0; i<searchPaths.length; i++) { msg.append(searchPaths[i]); 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 Variant[] getVariants() { checkVM(); if(vm.variants.length != vm.variantMap.size()) { // note that we need to avoid putting duplicates // into the array. // ArrayList list = new ArrayList(); // list of variants Set set = new HashSet(); // a set of MapRecs set.addAll(vm.variantMap.values()); // fill variant list with variants. Iterator iter = set.iterator(); while(iter.hasNext()) { MapRec mr = (MapRec) iter.next(); MapRecObj mro = mr.get(VERSION_NEWEST); assert(mro != null); list.add( ((VRec) mro).getVariant() ); } Collections.sort(list); vm.variants = (Variant[]) list.toArray(new Variant[list.size()]); } 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 SymbolPack[] getSymbolPacks() { checkVM(); if(vm.symbolPacks.length != vm.symbolMap.size()) { // avoid putting duplicates into the array. ArrayList list = new ArrayList(); // list of SymbolPacks Set set = new HashSet(); // a set of MapRecs set.addAll(vm.symbolMap.values()); // fill variant list with variants. Iterator iter = set.iterator(); while(iter.hasNext()) { MapRec mr = (MapRec) iter.next(); MapRecObj mro = mr.get(VERSION_NEWEST); assert(mro != null); list.add( ((SPRec) mro).getSymbolPack() ); } Collections.sort(list); vm.symbolPacks = (SymbolPack[]) list.toArray(new SymbolPack[list.size()]); } 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 = (MapRec) vm.variantMap.get(name.toLowerCase()); if(mr != null) { return (Variant) ((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 = (MapRec) vm.symbolMap.get(name.toLowerCase()); if(mr != null) { return (SymbolPack) ((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()[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 = (MapRec) 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 = (MapRec) 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(53); symbolMap = new HashMap(17); isInWebstart = false; }// VariantManager() /** * Searches the given paths for files ending with the given extension(s). * Returns URLs. * */ private URL[] searchForFiles(final File[] searchPaths, final String[] extensions) { List urlList = new LinkedList(); for(int spIdx=0; spIdx<searchPaths.length; spIdx++) { File[] list = searchPaths[spIdx].listFiles(); // internal error if list == null; means that // searchPaths[] is not a directory! if(list != null) { for(int i=0; i<list.length; i++) { if(list[i].isFile()) { String fileName = list[i].getPath(); if(checkFileName(fileName, extensions)) { try { urlList.add(list[i].toURL()); } catch(java.net.MalformedURLException e) { // do nothing; we just won't add it } } } } } } return (URL[]) urlList.toArray(new URL[urlList.size()]); }// 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, String[] extensions) { for(int i=0; i<extensions.length; i++) { if(fileName.endsWith(extensions[i])) { 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 enum1 = null; try { enum1 = cl.getResources(uri.toString()); } catch(IOException e) { return null; } while(enum1.hasMoreElements()) { URL url = (URL) enum1.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 enum1 = null; try { enum1 = cl.getResources(uri.toString()); } catch(IOException e) { return null; } while(enum1.hasMoreElements()) { URL url = (URL) enum1.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 = (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 = (MapRec) 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 = (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 = (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 = (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 ArrayList list = new ArrayList(2); // this constructor prevents us from having an empty list. public MapRec(MapRecObj obj) { if(obj == null) { throw new IllegalArgumentException(); } list.add(obj); }// MapRec() public int size() { return list.size(); } /** * 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(int i=0; i<list.size(); i++) { MapRecObj temp = (MapRecObj) list.get(i); 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 = (MapRecObj) 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 (MapRecObj) list.get(0); } MapRecObj selected = null; for(int i=0; i<size; i++) { MapRecObj mro = (MapRecObj) 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; } 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; } public float getVersion() { return symbolPack.getVersion(); } }// inner class SPRec }// class VariantManager