/* * 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.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.w3c.dom.Element; import com.shavenpuppy.jglib.IResource; import com.shavenpuppy.jglib.Resource; import com.shavenpuppy.jglib.Resources; import com.shavenpuppy.jglib.XMLResourceWriter; import com.shavenpuppy.jglib.util.XMLUtil; /** * $Id: Feature.java,v 1.24 2011/10/14 15:16:41 cix_foo Exp $ * Base class for game resources * @author $Author: cix_foo $ * @version $Revision: 1.24 $ */ public abstract class Feature extends Resource { private static final long serialVersionUID = 1L; /** All game features */ private static final List<Feature> FEATURES = new ArrayList<Feature>(); /* * Resource data */ /** Autocreate this feature at application init */ private boolean autoCreated; /** Inherit data from another feature */ private String inherit; /* * Transient data */ private transient Feature inheritFeature; /** * C'tor */ public Feature() { super(); } /** * C'tor * @param name */ public Feature(String name) { super(name); } /** * Autocreate all auto-created features */ public static void autoCreate() throws Exception { for (Feature feature : FEATURES) { if (feature.autoCreated) { feature.create(); } } for (IResource resource : Resources.list()) { if (resource.isCreated()) { resource.archive(); } } System.gc(); } @Override public void archive() { clearStrings(); } protected final void setAutoCreated() { autoCreated = true; } @Override public void load(Element element, Loader loader) throws Exception { autoCreated = XMLUtil.getBoolean(element, "autoCreated", autoCreated); // Default processing XMLUtil.grabXMLAttributes(loader, this, Feature.class, element); } private void clearStrings() { Class<?> clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); ArrayList<Field> sortedStringFields = new ArrayList<Field>(fields.length / 2 + 1); ArrayList<Field> sortedResourceFields = new ArrayList<Field>(fields.length / 2 + 1); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; // Ignore a few well-known fields String fieldName = f.getName().toLowerCase(); if (fieldName.equals("name") || fieldName.equals("class") || fieldName.equals("autoCreated") || fieldName.equals("inherit")) { continue; } f.setAccessible(true); // ordinary string fields: // If the field is final or static or transient then ignore it int modifiers = f.getModifiers(); // Ignore final and static fields if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { continue; } if (f.getAnnotation(Data.class) != null) { // It's "data" - don't touch continue; } else if (!Modifier.isTransient(modifiers) && f.getType() == String.class) { sortedStringFields.add(f); } else if (Modifier.isTransient(modifiers) && IResource.class.isAssignableFrom(f.getType())) { sortedResourceFields.add(f); } } // Sort in descending order of field name length class LengthSorter implements Comparator<Field> { @Override public int compare(Field fa, Field fb) { int fal = fa.getName().length(); int fbl = fb.getName().length(); if (fal > fbl) { return -1; } else if (fal < fbl) { return 1; } else { return 0; } } } LengthSorter sorter = new LengthSorter(); Collections.sort(sortedStringFields, sorter); Collections.sort(sortedResourceFields, sorter); for (Field stringField : sortedStringFields) { // Search for a field beginning with this field's name for (Iterator<Field> j = sortedResourceFields.iterator(); j.hasNext(); ) { Field resourceField = j.next(); if (resourceField.getName().startsWith(stringField.getName())) { resourceField.setAccessible(true); String resourceName; try { resourceName = (String) stringField.get(this); if (resourceName != null && Resources.exists(resourceName)) { IResource r = Resources.peek(resourceName); if (resourceField.getType().isAssignableFrom(r.getClass())) { stringField.set(this, null); //System.out.println("Zapped "+this.getName()+"."+stringField); } } else { // Don't attempt to get this field again j.remove(); } } catch (Exception e) { assert false : "Should never happen: "+e; } break; } } } // Get next class up... clazz = clazz.getSuperclass(); } } /** * Do default creation. This scans through the class heirarchy, looking for non-transient non-final non-static String fields, * and then trying to find a corresponding transient, non-final, non-static field that starts with the name of the first * field. The original String field is meant to be a Resource which can be stashed in the transient partner field. */ private void defaultCreation() throws Exception { // First deal with inheritance, a special case if (inherit != null && inheritFeature == null) { inheritFeature = (Feature) Resources.get(inherit); if (inheritFeature == null) { throw new Exception(this+" (a "+getClass().getName()+") cannot inherit "+inherit+" as it doesn't exist"); } if (!inheritFeature.getClass().isAssignableFrom(this.getClass())) { throw new Exception(this+" (a "+getClass().getName()+") cannot inherit "+inherit+" (a "+inheritFeature.getClass().getName()+")"); } Class<?> clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; // Ignore a few well-known fields String fieldName = f.getName().toLowerCase(); if (fieldName.equals("name") || fieldName.equals("class") || fieldName.equals("autoCreated") || fieldName.equals("inherit")) { continue; } f.setAccessible(true); // If the field is final or static or transient then ignore it int modifiers = f.getModifiers(); if (Modifier.isTransient(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { continue; } Object inheritedValue = f.get(inheritFeature); if (inheritedValue != null) { Object currentValue = f.get(this); if (currentValue != null) { // Might be a primitive type if (f.getType().isPrimitive()) { // Yes it is. In which case ignore "default" values like false and 0. if (currentValue.equals(Boolean.FALSE) || (currentValue instanceof Number && ((Number) currentValue).doubleValue() == 0.0)) { f.set(this, inheritedValue); } } } else { f.set(this, inheritedValue); } } } // Get next class up... clazz = clazz.getSuperclass(); } } Class<?> clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); ArrayList<Field> sortedStringFields = new ArrayList<Field>(fields.length / 2 + 1); ArrayList<Field> sortedResourceFields = new ArrayList<Field>(fields.length / 2 + 1); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; // Ignore a few well-known fields String fieldName = f.getName().toLowerCase(); if (fieldName.equals("name") || fieldName.equals("class") || fieldName.equals("autoCreated") || fieldName.equals("inherit")) { continue; } f.setAccessible(true); // ordinary string fields: // If the field is final or static or transient then ignore it int modifiers = f.getModifiers(); // Ignore final and static fields if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { continue; } if (!Modifier.isTransient(modifiers) && f.getType() == String.class) { sortedStringFields.add(f); } else if (Modifier.isTransient(modifiers) && IResource.class.isAssignableFrom(f.getType())) { sortedResourceFields.add(f); } } // Sort in descending order of field name length class LengthSorter implements Comparator<Field> { @Override public int compare(Field fa, Field fb) { int fal = fa.getName().length(); int fbl = fb.getName().length(); if (fal > fbl) { return -1; } else if (fal < fbl) { return 1; } else { return 0; } } } LengthSorter sorter = new LengthSorter(); Collections.sort(sortedStringFields, sorter); Collections.sort(sortedResourceFields, sorter); for (Field stringField : sortedStringFields) { // Search for a field beginning with this field's name for (Iterator<Field> j = sortedResourceFields.iterator(); j.hasNext(); ) { Field resourceField = j.next(); if (resourceField.getName().startsWith(stringField.getName())) { if (resourceField.get(this) != null) { // It's already assigned something continue; } resourceField.setAccessible(true); String resourceName = (String) stringField.get(this); if (resourceName != null && Resources.exists(resourceName)) { IResource r = Resources.get(resourceName); if (resourceField.getType().isAssignableFrom(r.getClass())) { resourceField.set(this, r); } else { throw new Exception(resourceField.getName()+" cannot be assigned a "+r.getClass().getName()); } } else { // Don't attempt to get this field again j.remove(); } break; } } } // Get next class up... clazz = clazz.getSuperclass(); } // Now create all non-transient Resource fields clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; int rfmodifiers = f.getModifiers(); if (Modifier.isTransient(rfmodifiers) || Modifier.isStatic(rfmodifiers)) { continue; } f.setAccessible(true); Object resource = f.get(this); if (resource != null && IResource.class.isAssignableFrom(resource.getClass())) { ((IResource)resource).create(); } } // Get next class up... clazz = clazz.getSuperclass(); } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.Resource#doCreate() */ @Override protected void doCreate() { try { defaultCreation(); } catch (Exception t) { throw new RuntimeException("Failed to create "+this+" due to "+t, t); } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.Resource#doDestroy() */ @Override protected void doDestroy() { defaultDestroy(); } /** * Default destruction: null out any transient Resource fields. */ private void defaultDestroy() { Class<?> clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; // Ignore a few well-known fields if (f.getName().equals("name") || f.getName().equals("class") || f.getName().equals("autoCreated")) { continue; } f.setAccessible(true); // If the field is final or static or not transient then ignore it int modifiers = f.getModifiers(); if (!Modifier.isTransient(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { continue; } if (IResource.class.isAssignableFrom(f.getType())) { try { IResource r = (IResource) f.get(this); // If the resource is locked, don't null it either if (r != null && !r.isLocked()) { f.set(this, null); } } catch (IllegalAccessException e) { e.printStackTrace(System.err); } } } // Get next class up... clazz = clazz.getSuperclass(); } // Now zap any non-transient resource fields clazz = this.getClass(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; int rfmodifiers = f.getModifiers(); if (Modifier.isTransient(rfmodifiers) || Modifier.isStatic(rfmodifiers)) { continue; } if (!IResource.class.isAssignableFrom(f.getType())) { continue; } f.setAccessible(true); try { if (f.get(this) == null) { continue; } IResource resource = (IResource) f.get(this); if (resource != null) { resource.destroy(); } } catch (IllegalAccessException e) { e.printStackTrace(System.err); } } // Get next class up... clazz = clazz.getSuperclass(); } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.Resource#doToXML(com.shavenpuppy.jglib.XMLResourceWriter) */ @Override protected void doToXML(XMLResourceWriter writer) throws IOException { if (autoCreated && shouldWriteAttribute("autoCreated")) { writer.writeAttribute("autoCreated", autoCreated); } Class<?> clazz = getClass(); List<Field> normalFields = new LinkedList<Field>(); List<Field> objectFields = new LinkedList<Field>(); while (Feature.class.isAssignableFrom(clazz)) { Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i ++) { Field f = fields[i]; // Ignore a few well-known fields if (f.getName().equals("name") || f.getName().equals("class") || f.getName().equals("autoCreated")) { continue; } f.setAccessible(true); // If the field is final or static or transient then ignore it int modifiers = f.getModifiers(); if (Modifier.isTransient(modifiers) || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) { continue; } if (Resource.class.isAssignableFrom(f.getType())) { objectFields.add(f); } else { normalFields.add(f); } } // Get next class up... clazz = clazz.getSuperclass(); } // Now write it all out. But first let's construct a vanilla Feature // and look at the default values. Feature tester; try { tester = getClass().newInstance(); } catch (InstantiationException e1) { throw new IOException(e1.getMessage()); } catch (IllegalAccessException e1) { throw new IOException(e1.getMessage()); } for (Field field : normalFields) { String name = field.getName(); if (shouldWriteAttribute(name)) { try { Object value = field.get(this); Object defaultValue = field.get(tester); if (value != null && !value.equals(defaultValue)) { writer.writeAttribute(name, value); } } catch (IllegalArgumentException e) { throw new IOException(e.getMessage()); } catch (IllegalAccessException e) { throw new IOException(e.getMessage()); } } } // Let user write some attributes now doWriteAttributes(writer); for (Field field : objectFields) { String name = field.getName(); if (shouldWriteChild(name)) { try { Resource value = (Resource) field.get(this); if (value != null) { writer.writeTag(name); value.toXML(writer); writer.closeTag(); } } catch (IllegalArgumentException e) { throw new IOException(e.getMessage()); } catch (IllegalAccessException e) { throw new IOException(e.getMessage()); } } } // Let user write some objects doWriteChildren(writer); } /** * Write any attributes out here * @param writer * @throws IOException */ protected void doWriteAttributes(XMLResourceWriter writer) throws IOException { } /** * Write any child tags or text etc out here * @param writer * @throws IOException */ protected void doWriteChildren(XMLResourceWriter writer) throws IOException { } /** * Override to selectively write attributes out automatically * @param attribute * @return true if this attribute should be automatically written (default true) */ protected boolean shouldWriteAttribute(String attribute) { return true; } /** * Override to selectively write children out automatically * @param child * @return true if this child should be automatically written (default true) */ protected boolean shouldWriteChild(String child) { return true; } /* (non-Javadoc) * @see com.shavenpuppy.jglib.Resource#deregister() */ @Override public final void deregister() { if (FEATURES.remove(this)) { doDeregister(); } } /* (non-Javadoc) * @see com.shavenpuppy.jglib.Resource#register() */ @Override public final void register() { if (FEATURES.contains(this)) { return; } FEATURES.add(this); doRegister(); } /** * Override for extra registration */ protected void doRegister() { } /** * Override for extra deregistration */ protected void doDeregister() { } }