package org.concord.otrunk;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.concord.framework.otrunk.OTChangeEvent;
import org.concord.framework.otrunk.OTChangeListener;
import org.concord.framework.otrunk.OTID;
import org.concord.framework.otrunk.OTObject;
import org.concord.framework.otrunk.OTObjectCollection;
import org.concord.framework.otrunk.OTObjectInterface;
import org.concord.framework.otrunk.OTObjectList;
import org.concord.framework.otrunk.OTObjectMap;
import org.concord.framework.otrunk.OTObjectService;
import org.concord.framework.otrunk.OTResourceList;
import org.concord.framework.otrunk.OTResourceMap;
import org.concord.framework.otrunk.otcore.OTClass;
import org.concord.framework.otrunk.otcore.OTClassProperty;
import org.concord.framework.otrunk.otcore.OTType;
import org.concord.otrunk.datamodel.BlobResource;
import org.concord.otrunk.datamodel.OTDataCollection;
import org.concord.otrunk.datamodel.OTDataList;
import org.concord.otrunk.datamodel.OTDataMap;
import org.concord.otrunk.datamodel.OTDataObject;
import org.concord.otrunk.overlay.CompositeDataObject;
import org.concord.otrunk.view.OTConfig;
public class OTObjectInternal implements OTObjectInterface
{
public final static boolean traceListeners = OTConfig.getBooleanProp(
OTConfig.TRACE_LISTENERS_PROP, false);
public final static String LISTENER_THROWABLE_MESSAGE =
"Throwable thrown by an OTObjecListener, other listeners might not be notified";
protected OTObjectServiceImpl objectService;
/**
* This is null unless there are actually listeners added this saves some
* memory.
*/
ArrayList<WeakReference<OTChangeListener>> changeListeners = null;
/**
* This is for debugging purposes it contains a mapping from
* the weak reference object to the toString of the listener it
* referenced. This way when the listener is gc'd we can printout
* its "label" (toString value).
*/
Map<WeakReference<OTChangeListener>, String> changeListenerLabels;
/**
* This can be used by a user of an object to turn off the listening
*
*/
protected boolean doNotifyListeners = true;
protected OTObject changeEventSource;
protected OTDataObject dataObject;
protected OTClass otClass;
private String changeEventSourceInstanceID;
private int hashCode;
private HashMap<String, Object> referencedObjects;
public OTObjectInternal()
{
}
public OTObjectInternal(OTDataObject dataObject, OTObjectServiceImpl objectService, OTClass otClass)
{
setup(dataObject, objectService, otClass);
}
public void setup(OTObjectInternal objInternal)
{
setup(objInternal.dataObject, objInternal.objectService, objInternal.otClass);
}
public void setup(OTDataObject dataObject, OTObjectServiceImpl objectService, OTClass otClass)
{
this.objectService = objectService;
this.dataObject = dataObject;
String str = getOTClassName() + "@" + getGlobalId().hashCode();
hashCode = str.hashCode();
if(otClass == null){
throw new IllegalStateException("otClass cannot be null");
}
this.otClass = otClass;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#getOTObjectService()
*/
public OTObjectService getOTObjectService()
{
return objectService;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#setEventSource(org.concord.framework.otrunk.OTObject)
*/
public void setEventSource(OTObject src)
{
changeEventSource = src;
changeEventSourceInstanceID = Integer.toHexString(System.identityHashCode(changeEventSource));
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#setDoNotifyListeners(boolean)
*/
public void setDoNotifyChangeListeners(boolean doNotify)
{
doNotifyListeners = doNotify;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#notifyOTChange(java.lang.String, java.lang.String, java.lang.Object)
*/
public void notifyOTChange(String property, String operation,
Object value, Object previousValue)
{
if(!doNotifyListeners || changeListeners == null){
return;
}
ArrayList<WeakReference<OTChangeListener>> toBeRemoved = null;
OTChangeEvent changeEvent = new OTChangeEvent(changeEventSource);
changeEvent.setProperty(property);
changeEvent.setOperation(operation);
changeEvent.setValue(value);
changeEvent.setPreviousValue(previousValue);
try {
for(int i=0;i<changeListeners.size(); i++){
WeakReference<OTChangeListener> ref = null;
try {
ref = changeListeners.get(i);
} catch (java.lang.IndexOutOfBoundsException e){
// listener was removed since entering this loop
return;
}
if (ref == null){
return;
}
OTChangeListener listener = ref.get();
if(traceListeners && !(listener instanceof InternalListener)){
System.out.println("sending stateChanged " + changeEvent.getDescription() +
" to " + listener);
}
if(listener != null) {
listener.stateChanged(changeEvent);
} else {
// the listener was gc'd so lets mark it to be removed
if(toBeRemoved == null) {
toBeRemoved = new ArrayList<WeakReference<OTChangeListener>>();
}
if(traceListeners){
System.out.println("otChangeListener garbage collected:" +
changeListenerLabels.get(ref));
}
toBeRemoved.add(ref);
}
}
// The Errors and runtime exceptions are printed out here because the code which caused
// this change event might not expect an exception and might be silently catching exceptions
// for its own reasons.
} catch (Error err) {
System.err.println(LISTENER_THROWABLE_MESSAGE);
err.printStackTrace();
throw(err);
} catch (RuntimeException exp) {
System.err.println(LISTENER_THROWABLE_MESSAGE);
exp.printStackTrace();
throw(exp);
}
if(toBeRemoved != null) {
for(int i=0; i<toBeRemoved.size(); i++) {
changeListeners.remove(toBeRemoved.get(i));
}
}
// if(changeListeners.size() == 0){
// changeListeners = null;
// }
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#addOTChangeListener(org.concord.framework.otrunk.OTChangeListener)
*/
public void addOTChangeListener(OTChangeListener changeListener)
{
if(changeListener == null){
throw new IllegalArgumentException("changeListener cannot be null");
}
if(changeListeners == null){
changeListeners = new ArrayList<WeakReference<OTChangeListener>>();
}
// Check if the changeListener has already been added
for(int i=0; i<changeListeners.size(); i++) {
WeakReference<OTChangeListener> ref = changeListeners.get(i);
if(changeListener == ref.get()) {
// this changeListener has already been added
return;
}
}
WeakReference<OTChangeListener> listenerRef = new WeakReference<OTChangeListener>(changeListener);
changeListeners.add(listenerRef);
// debugging instrumentation
// ignore instances of the tracelistener
if(traceListeners &&
!(changeListener instanceof InternalListener)){
System.out.println("addOTChangeListener(obj:" + changeEventSource + ",");
System.out.println(" listener:" + changeListener+")");
if(changeListenerLabels == null){
changeListenerLabels = new HashMap<WeakReference<OTChangeListener>, String>();
// the ("" + obj) is used in case the changeListener is null
changeListenerLabels.put(listenerRef, "" + changeListener);
}
}
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#removeOTChangeListener(org.concord.framework.otrunk.OTChangeListener)
*/
public void removeOTChangeListener(OTChangeListener changeListener)
{
if(traceListeners){
System.out.println("removeOTChangeListener(obj:" + changeEventSource + ",");
System.out.println(" listener:" + changeListener);
}
if(changeListeners == null){
System.err.println("Warning: trying to remove a listener that hasn't been added: " +
changeListener);
return;
}
// param OTChangeListener listener
for(int i=0; i<changeListeners.size(); i++) {
WeakReference<OTChangeListener> ref = changeListeners.get(i);
if(changeListener == ref.get()) {
changeListeners.remove(i);
// if we don't break right away then the "i" variable will skip over the
// the next value of the list.
break;
}
}
// if(changeListeners.size() == 0) {
// changeListeners = null;
// }
}
public OTObject getOTObject(OTID childID) throws Exception
{
return objectService.getOTObject(childID);
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectInternal#setResource(java.lang.String, java.lang.Object)
*/
public boolean setResource(String name, Object value)
{
Object dataValue = value;
if(value instanceof OTObject) {
OTObject child = (OTObject)value;
OTID childId = child.getGlobalId();
saveReference(name, child);
dataValue = childId;
} else if(value instanceof byte[]) {
dataValue = new BlobResource((byte[])value);
} else if(value instanceof URL){
dataValue = new BlobResource((URL)value);
}
// if the value is a BlobResource itself then it will pass right though to here
// Check to see if it is equal before we go further
Object oldValue = getResourceValue(name);
if(oldValue != null && oldValue.equals(dataValue)){
return false;
}
// setResource should only return true if the dataObject was
// actually changed with this call
if(setResourceInternal(name, dataValue)){
if(oldValue instanceof OTID){
// Handle the case where someone set a object reference to null
if(value == null){
saveReference(name, null);
}
try {
oldValue = getOTObject((OTID) oldValue);
} catch (Exception e) {
e.printStackTrace();
}
}
notifyOTChange(name, OTChangeEvent.OP_SET, value, oldValue);
}
return true;
}
public OTID getGlobalId()
{
return dataObject.getGlobalId();
}
public boolean isResourceSet(String resourceName)
{
OTClassProperty property = otClass().getProperty(resourceName);
if(property == null){
throw new IllegalStateException("Property: " + resourceName + " doesn't exist on OTClass: " + otClass().getName());
}
return otIsSet(property);
}
public Object getResourceChecked(String resourceName, Class<?> returnType)
{
try {
return getResource(resourceName, returnType);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Object getResource(String resourceName, Class<?> returnType)
throws Exception
{
OTClassProperty classProperty = otClass().getProperty(resourceName);
return getResource(classProperty, returnType);
}
public Object getResource(String resourceName)
throws Exception
{
return getResource(resourceName, null);
}
public Object getResourceChecked(OTClassProperty property, Class<?> returnType)
{
try {
return getResource(property, returnType);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Object getResource(OTClassProperty property, Class<?> returnType)
throws Exception
{
Object value = getResourceInternal(property, returnType);
// If the resource value is an OTObject save a reference to it
// if this is is an overridden property then don't save it
if(!property.isOverriddenProperty() && (value instanceof OTObject || value instanceof OTObjectCollection)){
String propertyName = property.getName();
saveReference(propertyName, value);
}
return value;
}
public Object getResourceInternal(OTClassProperty property, Class<?> returnType)
throws Exception
{
String resourceName = property.getName();
OTType type = property.getType();
if(returnType == null){
returnType = type.getInstanceClass();
}
// If this class is the one handling the overlays then this call
// would be substituted by one that goes through all of the overlayed data objects.
Object resourceValue = null;
Object overriddenValue = null;
if(property.isOverriddenProperty()){
if(dataObject instanceof CompositeDataObject) {
Object nonActiveDeltaResource =
((CompositeDataObject)dataObject).getNonActiveDeltaResource(resourceName);
resourceValue = nonActiveDeltaResource;
overriddenValue = resourceValue;
} else {
System.err.println("Warning: this object isn't from an Overlay");
resourceValue = getResourceValue(resourceName);
}
} else {
resourceValue = getResourceValue(resourceName);
}
// we can't rely on the returnType here because it could be an
// interface that isn't in the ot package
if(resourceValue instanceof OTID){
OTObject object;
try {
if(resourceValue == null) {
return null;
}
OTID objId = (OTID)resourceValue;
object = getOTObject(objId);
if(object != null){
if(!returnType.isAssignableFrom(object.getClass())){
System.err.println("Error: Type Mismatch");
System.err.println(" value: " + object);
System.err.println(" parentObject: " + otClass.getName());
System.err.println(" resourceName: " + resourceName);
System.err.println(" expected type is: " + returnType);
return null;
}
}
return object;
} catch (Exception e)
{
e.printStackTrace();
}
return null;
} else if(OTResourceMap.class.isAssignableFrom(returnType)) {
try {
OTDataMap list = getResourceCollection(resourceName, OTDataMap.class,
overriddenValue);
return new OTResourceMapImpl(resourceName, list, this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} else if(OTObjectMap.class.isAssignableFrom(returnType)) {
try {
OTDataMap list = getResourceCollection(resourceName, OTDataMap.class,
overriddenValue);
return new OTObjectMapImpl(resourceName, list, this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} else if(OTResourceList.class.isAssignableFrom(returnType)) {
try {
OTDataList list = getResourceCollection(resourceName, OTDataList.class,
overriddenValue);
return new OTResourceListImpl(resourceName, list, this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} else if(OTObjectList.class.isAssignableFrom(returnType)) {
try {
OTDataList list = getResourceCollection(resourceName, OTDataList.class,
overriddenValue);
return new OTObjectListImpl(resourceName, list, this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} else if(resourceValue instanceof BlobResource) {
BlobResource blob = (BlobResource)resourceValue;
if(returnType == byte[].class){
return blob.getBytes();
} else if(returnType == URL.class){
return blob.getBlobURL();
} else {
return blob;
}
} else if(resourceValue == null &&
(returnType == String.class || returnType.isPrimitive() ||
returnType == Boolean.class || returnType == Short.class ||
returnType == Character.class || returnType == Integer.class ||
returnType == Float.class || returnType == Double.class ||
returnType == Long.class || Enum.class.isAssignableFrom(returnType))) {
Object defaultValue = property.getDefault();
if(defaultValue == null &&
returnType != String.class &&
!Enum.class.isAssignableFrom(returnType)){
throw new RuntimeException("No default value set for \"" + resourceName + "\" " +
"in class: " + otClass.getName());
}
return defaultValue;
}
if(resourceValue == null) return null;
if(!returnType.isInstance(resourceValue) &&
!returnType.isPrimitive()){
System.err.println("invalid resource value for: " + resourceName);
System.err.println(" object type: " + otClass.getName());
System.err.println(" resourceValue is: " + resourceValue.getClass());
System.err.println(" expected type is: " + returnType);
return null;
}
return resourceValue;
}
public Object getResourceValue(String resourceName)
{
return dataObject.getResource(resourceName);
}
@SuppressWarnings("unchecked")
public <T extends OTDataCollection> T getResourceCollection(String resourceName,
Class<T> collectionClass, Object overriddenValue)
{
if(overriddenValue != null){
return (T)overriddenValue;
} else {
return dataObject.getResourceCollection(resourceName, collectionClass);
}
}
public OTResourceMap getResourceMap(String resourceName)
{
OTDataMap map = dataObject.getResourceCollection(
resourceName, OTDataMap.class);
return new OTResourceMapImpl(resourceName, map, this);
}
public OTObjectMap getObjectMap(String resourceName)
{
OTDataMap map = dataObject.getResourceCollection(
resourceName, OTDataMap.class);
return new OTObjectMapImpl(resourceName, map, this);
}
public OTResourceList getResourceList(String resourceName)
{
OTDataList list = dataObject.getResourceCollection(
resourceName, OTDataList.class);
return new OTResourceListImpl(resourceName, list, this);
}
public OTObjectList getObjectList(String resourceName, Object overriddenValue)
{
OTDataList list =
getResourceCollection(resourceName, OTDataList.class, overriddenValue);
return new OTObjectListImpl(resourceName, list, this);
}
public boolean setResourceInternal(String name, Object value)
{
return dataObject.setResource(name, value);
}
public String getOTClassName()
{
return OTrunkImpl.getClassName(dataObject);
}
public String internalToString()
{
String otObjectIDStr = "";
if(changeEventSourceInstanceID != null){
otObjectIDStr = "@" + changeEventSourceInstanceID;
}
return getOTClassName() + "#" + getGlobalId() + otObjectIDStr;
}
public int internalHashCode()
{
return hashCode;
}
public boolean internalEquals(Object other)
{
if(!(other instanceof OTObject)){
return false;
}
if(changeEventSource == other) {
return true;
}
if(((OTObject)other).getGlobalId().equals(getGlobalId())) {
System.err.println("compared two ot objects with the same ID but different instances");
return true;
}
return false;
}
protected void saveReference(String key, Object value)
{
if(referencedObjects == null){
referencedObjects = new HashMap<String, Object>();
}
referencedObjects.put(key, value);
}
@Override
protected void finalize()
throws Throwable
{
if(OTConfig.isTrace()){
System.out.println("finalizing object: " + internalToString());
}
if(traceListeners){
if(changeListeners != null){
// Check for the case where there is just the TraceListener
if(changeListeners.size() == 1){
WeakReference<OTChangeListener> ref = changeListeners.get(0);
OTChangeListener listener = ref.get();
if(listener instanceof InternalListener){
// don't print anything here
return;
}
}
System.out.println("listeners on finalized object: " + internalToString());
for(int i=0; i<changeListeners.size(); i++){
WeakReference<OTChangeListener> ref = changeListeners.get(i);
OTChangeListener listener = ref.get();
if(listener instanceof InternalListener){
// skip the trace listener
continue;
}
System.out.println(" " + listener);
}
}
}
}
public OTClass otClass()
{
return otClass;
}
public String getName()
{
return (String) getResourceValue("name");
}
public void setName(String name)
{
setResource("name", name);
}
public void init()
{
// do nothing on init.
}
public String getLocalId()
{
throw new UnsupportedOperationException("should not be called");
}
public Object otGet(OTClassProperty property)
{
try {
return getResource(property, null);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public boolean otIsSet(OTClassProperty property)
{
if(property.isOnlyInOverlayProperty()){
if(dataObject instanceof CompositeDataObject) {
return ((CompositeDataObject)dataObject).hasOverrideInTopOverlay(property.getName());
} else {
System.err.println("Warning: this object isn't from an Overlay");
}
}
Object resourceValue = dataObject.getResource(property.getName());
return resourceValue != null;
}
public void otSet(OTClassProperty property, Object newValue)
{
// FIXME should probably do some type checking here
setResource(property.getName(), newValue);
}
public void otUnSet(OTClassProperty property)
{
/*
boolean isOnlyInOverlayProperty();
public boolean isOverridenProperty();
public OTClassProperty getOnlyInOverlayProperty();
public OTClassProperty getOverridenProperty();
*/
if(property.isOnlyInOverlayProperty()){
if(dataObject instanceof CompositeDataObject) {
((CompositeDataObject)dataObject).removeOverrideInTopOverlay(property.getName());
return;
} else {
System.err.println("Warning: this object isn't from an Overlay");
}
}
setResource(property.getName(), null);
}
public String otExternalId()
{
return objectService.getExternalID(changeEventSource);
}
public ArrayList<WeakReference<OTChangeListener>> getOTChangeListeners()
{
return changeListeners;
}
public OTObjectMap getAnnotations() {
return getObjectMap("annotations");
}
}