/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Map;
import org.w3c.dom.Element;
import com.shavenpuppy.jglib.resources.ResourceLoadedListener;
/**
* An abstract resource, owned by some operating system thing.
*/
public abstract class Resource implements IResource, Cloneable {
private static final long serialVersionUID = 1L;
/*
* Resource data
*/
/** The unique name of the resource */
protected String name;
/** Locked: this resource cannot be reloaded */
private boolean locked;
/** Subresource: lifecycle & storage managed by owner resource */
private boolean subResource;
/** Creation serialization support */
private final CreationDeserialization creationDeserialization = new CreationDeserialization(this);
/*
* Transient data
*/
// Has the resource been created;
transient boolean created, creating, destroying;
/** Special twiddler that causes resources to create() when they are deserialized if they were created at serialization time */
private static class CreationDeserialization implements Serializable {
private static final long serialVersionUID = 1L;
private final Resource res;
private boolean created;
CreationDeserialization(Resource res) {
this.res = res;
}
private Object readResolve() throws ObjectStreamException {
try {
if (created) {
Resources.queue(res);
}
return this;
} catch (Exception e) {
e.printStackTrace(System.err);
throw new InvalidObjectException("Failed to deserialize resource "+res+" due to "+e);
}
}
private void writeObject(ObjectOutputStream stream) throws IOException {
created = res.isCreated();
stream.defaultWriteObject();
}
}
/**
* Special interface for development time.
*/
public interface Loader {
/**
* Get a resource from an Element.
* @param element The element to get the resource from
* @return a Resource
* @throws Exception if there's anything wrong
*/
IResource load(Element element) throws Exception;
/**
* Include further resources from the specified input stream
* @param is An XML input stream
*/
void include(InputStream is) throws Exception;
/**
* Temorarily add some mappings to the loader.
* @param map A Map of Strings (XML tag names) to Classes derived from Resource
*/
void pushMap(Map<String, Class<? extends IResource>> map);
/**
* Remove the last set of temporary mappings from the loader.
* If no temporary mappings have been added (with pushMap()),
* then nothing happens.
* @return the removed mapping, or null if there wasn't one
*/
Map<String, Class<? extends IResource>> popMap();
/**
* @param loadedListener
*/
void setLoadedListener(ResourceLoadedListener loadedListener);
ResourceLoadedListener getLoadedListener();
/**
* Sets overwrite mode. Existing resources are overwritten by newer ones without
* throwing an exception.
*/
void setOverwrite(boolean overwrite);
/**
* @return
*/
boolean isOverwrite();
}
/**
* A default public constructor, for serialization purposes.
*/
public Resource() {
}
/**
* Construct a resource with a name.
*/
public Resource(String name) {
this.name = name;
}
/**
* Create this resource. This method blocks until this resource has been created.
*/
@Override
public final void create() {
if (!created && !creating) {
creating = true;
if (Resources.getCreatingCallback() != null) {
Resources.getCreatingCallback().onCreating(this);
}
doCreate();
creating = false;
created = true;
}
}
/**
* Destroys the resource. This method blocks until this resource has been destroyed.
*/
@Override
public final void destroy() {
if (created && !destroying) {
destroying = true;
doDestroy();
destroying = false;
created = false;
}
}
/**
* Subclasses must implement this method to do the creation of external resources.
*/
protected void doCreate() {}
/**
* Subclasses must implement this method to do the destruction of external resources.
*/
protected void doDestroy() {}
/**
* Has this object been created by the allocator?
*/
@Override
public synchronized final boolean isCreated() {
return created;
}
/**
* Returns the resource's name
*/
@Override
public String toString() {
if (name == null) {
return "a "+getClass().getName();
} else {
return name;
}
}
/**
* Provides a hashcode
*/
@Override
public int hashCode() {
if (name != null) {
return name.hashCode();
} else {
return super.hashCode();
}
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Resource)) {
return false;
}
Resource r = (Resource) obj;
if (obj == this) {
return true;
}
if (r.name == null || name == null) {
// Unnamed resources aren't equal, generally
return false;
}
return r.name.equals(name);
}
@Override
public final String getName() {
return name;
}
@Override
public void load(Element element, Loader loader) throws Exception {
}
/**
* Optional operation: save the resource to an XML element.
* @param parent The parent resource, if any
* @return a new Element, or null, if the resource can't be saved to XML
*/
public Element save(Resource parent) {
return null;
}
@Override
public void register() {
}
@Override
public void deregister() {
}
@Override
public final boolean isLocked() {
return locked;
}
/**
* Lock or unlock the resource.
* @param locked
* @see #isLocked()
* @see #load(Element, Loader)
*/
public final void setLocked(boolean locked) {
this.locked = locked;
}
@Override
public final boolean isSubResource() {
return subResource;
}
/**
* Marks this resource as being a sub-resource. Sub-resources are loaded
* directly by their parent resource calling load() on them.
* @param subResource
*/
public final void setSubResource(boolean subResource) {
this.subResource = subResource;
}
/**
* Serialization support. We completely replace the serialized resource with an
* instance of SerializedResource instead.
*/
public final Object writeReplace() throws ObjectStreamException {
if (Resources.isRunMode()) {
// We're in "Run Mode"
if (name == null) {
// No name, so have to serialize directly.
return this;
} else {
// Got a name, so send a SerializedResource that just references this
return new SerializedResource(this);
}
} else {
// This is "compile time" so we serialize directly
return this;
}
}
private static final class SerializedResource implements Serializable {
private static final long serialVersionUID = 1L;
private String resourceName;
private transient Resource resource;
public SerializedResource(Resource resourceToSerialize) {
resourceName = resourceToSerialize.getName();
}
/**
* Provides a hashcode
*/
@Override
public int hashCode() {
return resourceName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof SerializedResource)) {
return false;
}
SerializedResource r = (SerializedResource) obj;
if (obj == this) {
return true;
}
if (r.resource != null && resource != null) {
return r.resource.equals(resource);
} else if (r.resourceName != null && resourceName != null) {
return r.resourceName.equals(resourceName);
} else {
return false;
}
}
/**
* @returns a Resource
*/
private Object readResolve() throws ObjectStreamException {
try {
return Resources.get(resourceName);
} catch (Exception e) {
throw new InvalidObjectException("Failed to deserialize resource "+resourceName+" due to "+e);
}
}
}
/**
* Sets the name of this resource.
* @param name
*/
@Override
public final void setName(String name) {
IResource oldResource = Resources.forget(this);
this.name = name;
if (oldResource != null) {
Resources.put(this);
}
}
/**
* Write out XML for this Resource
* @param writer The XML resource writer
* @throws IOException
*/
@Override
public final void toXML(XMLResourceWriter writer) throws IOException {
// See if there's a registered tag for this
String tag = Resources.getTag(getClass());
if (tag == null) {
// No, so write an "instance" tag
writer.writeTag("instance");
writer.writeAttribute("class", getClass().getName());
} else {
// Yes
writer.writeTag(tag);
}
if (name != null) {
writer.writeAttribute("name", name);
}
// Write fields out to XML now
doToXML(writer);
// Close the tag.
writer.closeTag();
}
/**
* Implement this method to write out the attributes and sub-resources of your
* Resource. name is already written out for you.
* @param writer
* @throws IOException
*/
protected void doToXML(XMLResourceWriter writer) throws IOException {
}
/**
* "Archive" the Resource; its definition can be erased, leaving only the "created" part
*/
@Override
public void archive() {
}
}