/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* END LICENSE */
/*
* Last modification information:
* $Revision: 1.3 $
* $Date: 2007-10-05 18:03:39 $
* $Author: scytacki $
*
* Licence Information
* Copyright 2004 The Concord Consortium
*/
package org.concord.otrunk.overlay;
import java.net.URL;
import java.util.HashMap;
import java.util.Set;
import java.util.logging.Logger;
import org.concord.framework.otrunk.OTID;
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.datamodel.OTDataObjectType;
import org.concord.otrunk.datamodel.OTDatabase;
import org.concord.otrunk.datamodel.OTObjectRevision;
import org.concord.otrunk.datamodel.OTTransientMapID;
import org.concord.otrunk.xml.XMLDataObject;
/**
* OTUserDataObject
* Class name and description
*
* Date created: Aug 24, 2004
*
* @author scott<p>
*
*/
public class CompositeDataObject
implements OTDataObject
{
private static final Logger logger =
Logger.getLogger(CompositeDataObject.class.getCanonicalName());
private OTDataObject baseObject;
private OTDataObject activeDeltaObject = null;
private OTDataObject [] middleDeltas;
private CompositeDatabase database;
// The object can represent two types of data objects
// it is either a bridge between an authored object (template object)
// and the user modifications to that object object
// or it is a wrapper around a user created object
private boolean composite;
private HashMap<String, OTDataCollection> resourceCollections =
new HashMap<String, OTDataCollection>();
public CompositeDataObject(OTDataObject baseObject,
CompositeDatabase db, OTDataObject[] middleDeltas, boolean composite)
{
this.baseObject = baseObject;
database = db;
this.middleDeltas = middleDeltas;
this.composite = composite;
if(composite){
activeDeltaObject = database.getActiveDeltaObject(baseObject);
}
}
public OTDatabase getDatabase()
{
return database;
}
public OTDataObject getBaseObject()
{
return baseObject;
}
public OTDataObject getActiveDeltaObject()
{
return activeDeltaObject;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTDataObject#getGlobalId()
*/
public OTID getGlobalId()
{
OTID dbId = database.getDatabaseId();
return new OTTransientMapID(dbId, baseObject.getGlobalId());
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTDataObject#getCurrentRevision()
*/
public OTObjectRevision getCurrentRevision()
{
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTDataObject#getResource(java.lang.String)
*/
public Object getResource(String key)
{
// If we are not a composite then do the basic thing
if(!composite){
return baseObject.getResource(key);
}
// first look in the userObject
// then look in the middle deltas
// then look in the authoringObject
Object value = null;
OTDataObject localActiveDelta = getActiveDeltaObject();
if (localActiveDelta != null) {
value = localActiveDelta.getResource(key);
if(value != null) {
return value;
} else if(localActiveDelta.containsKey(key)){
// check if the localActiveDelta contains this key in which we should
// return null, otherwise we should go on down the line
return null;
}
}
return getNonActiveDeltaResource(key);
}
public Object getNonActiveDeltaResource(String key)
{
Object value = null;
if(middleDeltas != null){
for(int i=0; i<middleDeltas.length; i++){
OTDataObject delta = middleDeltas[i];
if (delta != null) {
value = delta.getResource(key);
if(value != null) {
return value;
} else if(delta.containsKey(key)){
return null;
}
}
}
}
return baseObject.getResource(key);
}
/**
* This will return the union of all the keys set between the base object and the overlay objects.
*
* @see org.concord.otrunk.OTDataObject#getResourceKeys()
*/
public String[] getResourceKeys()
{
HashMap<String, OTDataObject> keyTable = new HashMap<String, OTDataObject>();
OTDataObject localActiveDelta = getActiveDeltaObject();
if (localActiveDelta != null) {
String [] userKeys = localActiveDelta.getResourceKeys();
for (String userKey : userKeys) {
// we can put any object, but lets use the userObject
// so we might take advantage of that later
keyTable.put(userKey, localActiveDelta);
}
}
if(middleDeltas != null){
for (OTDataObject delta : middleDeltas) {
String [] userKeys = delta.getResourceKeys();
for (String userKey : userKeys) {
keyTable.put(userKey, delta);
}
}
}
if(baseObject != null) {
String [] authorKeys = baseObject.getResourceKeys();
for (String key : authorKeys) {
keyTable.put(key, baseObject);
}
}
Set<String> keySet = keyTable.keySet();
String [] strKeys = new String [keySet.size()];
strKeys = keySet.toArray(strKeys);
return strKeys;
}
public OTDataObject getOrCreateActiveDeltaObject()
{
if(activeDeltaObject == null) {
activeDeltaObject = database.createActiveDeltaObject(baseObject);
logger.fine("created delta object: " + activeDeltaObject.getGlobalId().toExternalForm());
}
return activeDeltaObject;
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTDataObject#setResource(java.lang.String, java.lang.Object)
*/
public boolean setResource(String key, Object resource)
{
// Need to check if the object being passed in
// is a translated id that we created. If it is we want to strip off our translation
// because it wouldn't be stored that way.
resource = resolveIDResource(resource);
Object oldObject = getResource(key);
if(oldObject != null && oldObject.equals(resource)){
return false;
}
// special hack for -0.0 and 0.0
// see the docs for Float.equals()
if(oldObject instanceof Float &&
resource instanceof Float) {
if(((Float)oldObject).floatValue() ==
((Float)resource).floatValue()){
return false;
}
}
if(!composite){
return baseObject.setResource(key, resource);
}
// get the existing active delta object or create a new one
OTDataObject localActiveDelta = getOrCreateActiveDeltaObject();
return localActiveDelta.setResource(key, resource);
}
/**
* This method is to handle the case where a resource is an
* id. Some of the passed in ids will be from our template
* database. So they are really just temporary ids. These ids are
* relative ids, where the root is the id of the template db and
* the relative part is the id of the original object
*
* We want to store the id of the original object. So this method
* should be called on any resource that could be an id.
*
* If this isn't working properly then an id will be stored that
* doesn't exist.
*
* @param resource
* @return
*/
public Object resolveIDResource(Object resource)
{
if(resource instanceof OTTransientMapID) {
return database.resolveID((OTID) resource);
}
return resource;
}
@SuppressWarnings("unchecked")
public <T extends OTDataCollection> T getResourceCollection(String key,
Class<T> collectionClass)
{
OTDataCollection collection = resourceCollections.get(key);
if(collection != null) {
return (T)collection;
}
// Get the base list that a delta will be built against
Object resourceObj = getNonActiveDeltaResource(key);
// Create a wrapper list so changes to the list will create a new list resource in
// the active delta object.
if(collectionClass.equals(OTDataList.class)) {
collection =
new CompositeDataList(this, (OTDataList)resourceObj, key, composite);
} else if(collectionClass.equals(OTDataMap.class)) {
collection =
new CompositeDataMap(this, (OTDataMap)resourceObj, key, composite);
}
resourceCollections.put(key, collection);
return (T)collection;
}
/**
* We do not allow switching the type of the object on the fly, so the type of the object
* will always be the type of the base object
*/
public OTDataObjectType getType()
{
return baseObject.getType();
}
public URL getCodebase()
{
// FIXME For now return the codebase of the baseObject.
// this needs to be through through more.
return baseObject.getCodebase();
}
public boolean containsKey(String key)
{
// If we are not a composite then do the basic thing
if(!composite){
return baseObject.containsKey(key);
}
// first look in the userObject
// then look in the middle deltas
// then look in the authoringObject
OTDataObject localActiveDelta = getActiveDeltaObject();
if (localActiveDelta != null && localActiveDelta.containsKey(key)) {
return true;
}
if(middleDeltas != null){
for(int i=0; i<middleDeltas.length; i++){
OTDataObject delta = middleDeltas[i];
if (delta != null && delta.containsKey(key)) {
return true;
}
}
}
return baseObject.containsKey(key);
}
public boolean hasOverrideInTopOverlay(String key)
{
OTDataObject deltaObject = getActiveDeltaObject();
if(deltaObject == null){
return false;
}
return deltaObject.containsKey(key);
}
public void removeOverrideInTopOverlay(String name)
{
OTDataObject deltaObject = getActiveDeltaObject();
if(deltaObject == null){
logger.warning("Doesn't have a delta object");
return;
}
if(!(deltaObject instanceof XMLDataObject)){
logger.warning("Can only remove overrides on xml data objects");
return;
}
((XMLDataObject)deltaObject).setSaveNulls(false);
deltaObject.setResource(name, null);
((XMLDataObject)deltaObject).setSaveNulls(true);
}
void setMiddleDeltas(OTDataObject [] middleDeltas)
{
this.middleDeltas = middleDeltas;
// throw away our cached collections
resourceCollections.clear();
}
OTDataObject [] getMiddleDeltas()
{
return middleDeltas;
}
/**
* A helper method to determine if this object has a modification, or is a modification
* in the case of a newly created object.
* @return
*/
public boolean isModified() {
if (composite) {
if (getActiveDeltaObject() != null) {
return true;
}
} else {
return true;
}
return false;
}
public boolean isComposite() {
return composite;
}
}