/* * Copyright (c) 2003-onwards Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.shavenpuppy.jglib.resources; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.shavenpuppy.jglib.IResource; import com.shavenpuppy.jglib.Resource; import com.shavenpuppy.jglib.Resources; import com.shavenpuppy.jglib.util.XMLUtil; /** * Resource converter. Reads in resources XML file and writes out a serialized * version. Usage: ResourceConverter <input file> <output file> * * @author foo */ public class ResourceConverter implements Resource.Loader { private static final boolean DEBUG = false; /** For artificially generating names */ private static int uniqueNameCounter; static final Class<?>[] namedSig = new Class[] {String.class}; static final Class<?>[] unnamedSig = new Class[] {}; private Map<String, Class<? extends IResource>> typeMap = new HashMap<String, Class<? extends IResource>>(); // A stack of temporary mappings Stack<Map<String, Class<? extends IResource>>> stack = new Stack<Map<String, Class<? extends IResource>>>(); /** Overwrite mode */ private boolean overwrite; /** Track included files */ private final Set<String> included = new HashSet<String>(); private ResourceLoadedListener loadedListener; /** Manufactured resource names */ private final Stack<String> resourcePath = new Stack<String>(); /** Classloader */ private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); /** * Constructor */ public ResourceConverter() { } public ResourceConverter(ResourceLoadedListener loadedListener, ClassLoader classLoader) { this.loadedListener = loadedListener; this.classLoader = classLoader; } /** * Constructor for ResourceConverter. */ public ResourceConverter(String resourceName) throws Exception { include(resourceName); } @SuppressWarnings("unused") public static void main(String[] args) { try { if (args.length != 2) { System.err.println("Usage: ResourceConverter <classpath-xml-resource> <destpath>"); System.exit(-1); } new ResourceConverter(args[0]); // warning suppressed Resources.save(new FileOutputStream(args[1])); } catch (Exception e) { e.printStackTrace(); } } /** * Reset the included files, so you can load them in again. */ public void resetIncludes() { included.clear(); } private void log(String msg) { if (DEBUG) { System.err.println(msg); } } /** * Include a resources file. If the file has already been included it is not * included again. * * @param resourceName the resource name which is the XML file */ public void include(String resourceName) throws Exception { log("Including resources from:" + resourceName); // Path starts here try { resourcePath.push(resourceName); // First attempt: search classpath InputStream is; is = classLoader.getResourceAsStream(resourceName); if (is == null) { ClassLoader l = Thread.currentThread().getContextClassLoader(); is = l.getResourceAsStream(resourceName); } if (is == null) { // Second attempt: search working directory is = new BufferedInputStream(new FileInputStream(resourceName)); } try { include(is); } catch (Exception e) { throw new Exception("Failed to include '"+resourceName+"'", e); } } finally { // Pop path resourcePath.pop(); } } /** * Include resources directly from a snippet */ public void load(String input) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new ByteArrayInputStream(input.getBytes())); Element root = document.getDocumentElement(); load(root); } /** * Include resources from the specified input stream * * @param is An XML input stream */ @Override public void include(InputStream is) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(is); Element root = document.getDocumentElement(); String name = XMLUtil.getString(root, "name", null); if (name != null) { if (included.contains(name)) { return; } included.add(name); resourcePath.push(name); } // Having determined the mappings from this file we can then continue // by loading all the tags we find. NodeList childNodeList = root.getChildNodes(); for (int i = 0; i < childNodeList.getLength(); i++) { Node node = childNodeList.item(i); if (node.getNodeType() != Node.ELEMENT_NODE) { continue; } Element featureElement = (Element) node; load(featureElement); } if (name != null) { resourcePath.pop(); } } /** * Conditional loading. */ private void ifDef(Element element, boolean match) throws Exception { String key = element.getAttribute("key"); String value = element.getAttribute("value"); log("Checking condition: " + key + "=" + value + "..."); if (System.getProperty(key, "<undefined>").equals(value) == match) { log("true"); NodeList children = element.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child instanceof Element) { load((Element) child); } } } else { log("false"); } } public ClassLoader getClassLoader() { return classLoader; } /** * @see com.shavenpuppy.jglib.Resource.Loader#load(org.w3c.dom.Element) */ @Override public IResource load(Element featureElement) throws Exception { if (featureElement.getNodeName().equals("include")) { String name = featureElement.getAttribute("resource"); if (name == null) { throw new Exception("Missing 'resource' attribute in include tag"); } log("Including " + name); include(name); return null; } else if (featureElement.getNodeName().equals("map")) { String tag = featureElement.getAttribute("tag"); if (tag == null || "".equals(tag)) { throw new Exception("Missing 'tag' attribute"); } else if (tag.equals("map")) { throw new Exception("Cannot reassign 'map' tag"); } String className = featureElement.getAttribute("class"); if (className == null || "".equals(className)) { throw new Exception("Missing 'class' attribute"); } Class<? extends Resource> clazz = (Class<? extends Resource>) Class.forName(className, true, getClassLoader()); if (!Resource.class.isAssignableFrom(clazz)) { throw new Exception(clazz + " is not a Resource."); } Class<? extends IResource> oldClazz = typeMap.get(tag); typeMap.put(tag, clazz); Resources.registerTag(clazz, tag); // useful if (oldClazz == null) { log("Mapping <" + tag + "> to " + className); } else if (oldClazz != clazz) { log("Re-mapping <" + tag + "> to " + className+" : old mapping was "+oldClazz.getName()); } return null; } else if (featureElement.getNodeName().equals("instance")) { String className = featureElement.getAttribute("class"); if (className == null || "".equals(className)) { throw new Exception("Missing 'class' attribute"); } Class<? extends Resource> clazz = (Class<? extends Resource>) Class.forName(className, true, getClassLoader()); if (!Resource.class.isAssignableFrom(clazz)) { throw new Exception(clazz + " is not a Resource."); } return loadInstance(true, featureElement, clazz); } else if (featureElement.getNodeName().equals("ifdef")) { ifDef(featureElement, true); return null; } else if (featureElement.getNodeName().equals("ifndef")) { ifDef(featureElement, false); return null; } else if (featureElement.getNodeName().equals("property")) { XMLUtil.putVar(XMLUtil.getString(featureElement, "key"), XMLUtil.getString(featureElement, "value")); return null; } Class<? extends IResource> featureClass = typeMap.get(featureElement.getTagName()); if (featureClass == null) { featureClass = Resources.getMapping(featureElement.getTagName()); if (featureClass == null) { throw new Exception("No mapping specified for " + featureElement.getTagName()); } } return loadInstance(false, featureElement, featureClass); } /** * Load an instance of the class specified */ private IResource loadInstance(boolean isInstance, Element featureElement, Class<? extends IResource> featureClass) throws Exception { // Check that we're creating something appropriate... assert Resource.class.isAssignableFrom(featureClass) : featureClass.getName() + " is not a Resource"; Constructor<? extends IResource> namedCtor = null, unnamedCtor = null; boolean canBeUnNamed = false, canBeNamed = false, named = false; try { namedCtor = featureClass.getConstructor(namedSig); canBeNamed = true; } catch (NoSuchMethodException e) { } try { unnamedCtor = featureClass.getConstructor(unnamedSig); canBeUnNamed = true; } catch (NoSuchMethodException e) { } if (!canBeNamed && !canBeUnNamed) { throw new Exception("No suitable constructor found for " + featureElement.getTagName()); } // Construct a new instance try { IResource newFeature = null; String name = null; name = featureElement.getAttribute("name"); named = !"".equals(name); boolean exists = false, wasCreated = false; // If in overwrite mode, check to see if a named resource already // exists if (overwrite && named) { exists = Resources.exists(name); if (exists) { // It already exists, so we'll re-use it. If it is already // created it // must first be destroyed: newFeature = Resources.peek(name); // Check the resource is not locked if (newFeature.isLocked()) { // It's locked so don't load return newFeature; } else { log("Reloading " + newFeature); } wasCreated = newFeature.isCreated(); if (wasCreated) { newFeature.destroy(); } newFeature.deregister(); } } if (newFeature == null) { if (canBeNamed && named && namedCtor != null) { newFeature = namedCtor.newInstance(new Object[] {name}); } else if (canBeUnNamed && !named && unnamedCtor != null) { newFeature = unnamedCtor.newInstance(new Object[] {}); } else if (canBeNamed && !named) { throw new Exception("Resource has no 'name' attribute (a " + featureElement.getTagName() + "/" + featureClass.getName() + ")"); } else if (canBeUnNamed && named) { throw new Exception("Resource cannot be named (a " + featureClass.getName() + ")"); } else { throw new Exception("Something strange happened."); } } if (!named) { // Construct a name if we ain't got one StringBuilder sb = new StringBuilder(64); // for (String s : resourcePath) { // sb.append(s); // sb.append('.'); // } sb.append(newFeature.getClass().getName()); sb.append(uniqueNameCounter++); newFeature.setName(sb.toString()); } // Load it from the element node try { newFeature.load(featureElement, this); } catch (Exception e) { System.err.println("Failed to load " + newFeature + " due to " + e); throw e; } if (!newFeature.isSubResource()) { if (exists) { newFeature.register(); if (wasCreated) { newFeature.create(); } if (loadedListener != null) { loadedListener.resourceLoaded(newFeature); // TODO: // Should be // reloaded()? } } else { // And stash it in the Resources if (!overwrite && Resources.exists(name)) { throw new Exception("Resource " + newFeature + " has already been defined."); } Resources.put(newFeature); if (loadedListener != null) { loadedListener.resourceLoaded(newFeature); } log("Resource " + newFeature + ": loaded"); } } return newFeature; } catch (InstantiationException e) { e.printStackTrace(System.err); throw new Exception("Resource " + featureElement.getAttribute("name") + " [" + featureElement.getAttribute("class") + "] failed to instantiate.", e.getCause()); } catch (Exception e) { log("Exception loading resource from tag " + featureElement.getNodeName()); NamedNodeMap atts = featureElement.getAttributes(); for (int i = 0; i < atts.getLength(); i++) { log("\tAttribute " + atts.item(i).getNodeName() + "=" + featureElement.getAttribute(atts.item(i).getNodeName())); } throw e; } catch (AssertionError e) { log("Exception loading resource from tag " + featureElement.getNodeName()); NamedNodeMap atts = featureElement.getAttributes(); for (int i = 0; i < atts.getLength(); i++) { log("\tAttribute " + atts.item(i).getNodeName() + "=" + featureElement.getAttribute(atts.item(i).getNodeName())); } throw e; } } @Override public void setLoadedListener(ResourceLoadedListener loadedListener) { this.loadedListener = loadedListener; } @Override public ResourceLoadedListener getLoadedListener() { return loadedListener; } /** * Sets overwrite mode. Existing resources are overwritten by newer ones * without throwing an exception. */ @Override public void setOverwrite(boolean overwrite) { this.overwrite = overwrite; } @Override public boolean isOverwrite() { return overwrite; } /** * @see com.shavenpuppy.jglib.Resource.Loader#pushMap(Map) */ @Override public void pushMap(Map<String, Class<? extends IResource>> map) { stack.push(typeMap); typeMap = new HashMap<String, Class<? extends IResource>>(typeMap); typeMap.putAll(map); // Permanently remember tags for (Map.Entry<String, Class<? extends IResource>> entry : typeMap.entrySet()) { String tag = entry.getKey(); Class<? extends IResource> clazz = entry.getValue(); Resources.registerTag(clazz, tag); } } /** * @see com.shavenpuppy.jglib.Resource.Loader#popMap() */ @Override public Map<String, Class<? extends IResource>> popMap() { if (stack.size() == 0) { return null; } Map<String, Class<? extends IResource>> existingMap = typeMap; typeMap = stack.pop(); return existingMap; } }