package org.openntf.domino.graph2.impl;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openntf.domino.AutoMime;
import org.openntf.domino.Document;
import org.openntf.domino.Session;
import org.openntf.domino.View;
import org.openntf.domino.ViewEntry;
import org.openntf.domino.big.impl.NoteCoordinate;
//import javolution.util.FastMap;
//import javolution.util.FastSet;
//import javolution.util.function.Equalities;
import org.openntf.domino.big.impl.NoteList;
import org.openntf.domino.big.impl.ViewEntryCoordinate;
import org.openntf.domino.types.Null;
import org.openntf.domino.types.SessionDescendant;
import org.openntf.domino.utils.TypeUtils;
import javolution.util.FastMap;
public abstract class DElement implements org.openntf.domino.graph2.DElement, Serializable, Map<String, Object> {
private static final Logger log_ = Logger.getLogger(DElement.class.getName());
private static final long serialVersionUID = 1L;
public static final String TYPE_FIELD = "_OPEN_GRAPHTYPE";
public static enum Deferred {
INSTANCE;
private static final long serialVersionUID = 1L;
}
protected transient org.openntf.domino.graph2.DGraph parent_;
protected Object delegateKey_;
protected transient Map<String, Object> delegate_;
protected boolean isRemoved_ = false;
public DElement(final org.openntf.domino.graph2.DGraph parent) {
parent_ = parent;
}
protected org.openntf.domino.graph2.impl.DGraph getParent() {
return (org.openntf.domino.graph2.impl.DGraph) parent_;
}
protected org.openntf.domino.graph2.DElementStore getStore() {
return getParent().findElementStore(this);
}
private Set<String> changedProperties_;
private Set<String> getChangedPropertiesInt() {
if (changedProperties_ == null) {
changedProperties_ = Collections.synchronizedSet(new TreeSet<String>(String.CASE_INSENSITIVE_ORDER));
}
return changedProperties_;
}
private Set<String> removedProperties_;
private Set<String> getRemovedPropertiesInt() {
if (removedProperties_ == null) {
removedProperties_ = Collections.synchronizedSet(new TreeSet<String>(String.CASE_INSENSITIVE_ORDER));
}
return removedProperties_;
}
private Map<String, Object> props_;
private Map<String, Object> getProps() {
if (props_ == null) {
Map<String, Object> localProps = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
for (String key : getDelegate().keySet()) {
if (key != null && key.length() > 0) {
localProps.put(key, Deferred.INSTANCE);
}
}
props_ = Collections.synchronizedMap(localProps);
}
return props_;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getProperty(final String propertyName, final Class<T> type) {
//TODO NTF cached properties should be automatically reset if the base Document is known to have changed
// if ("form".equalsIgnoreCase(propertyName)) {
// System.out.println("Getting form value now...");
// }
Object result = null;
Map<String, Object> props = getProps();
result = props.get(propertyName);
if (result == null || Deferred.INSTANCE.equals(result)) {
try {
Map<String, Object> delegate = getDelegate();
if (delegate instanceof Document) {
Document doc = (Document) delegate;
doc.setAutoMime(AutoMime.WRAP_ALL);
if (doc.hasItem(propertyName)) {
result = doc.getItemValue(propertyName, type);
}
if (result == null || Deferred.INSTANCE.equals(result)) {
try {
Object raw = doc.get(propertyName);
if (raw instanceof Vector) {
if (((Vector) raw).isEmpty()) {
props.put(propertyName, Null.INSTANCE);
return null;
}
}
result = TypeUtils.objectToClass(raw, type, doc.getAncestorSession());
} catch (Throwable t) {
if (log_.isLoggable(Level.FINE)) {
log_.log(Level.FINE, "Invalid property for document " + propertyName, t);
}
}
}
} else if (delegate instanceof SessionDescendant) {
Session s = ((SessionDescendant) delegate).getAncestorSession();
result = TypeUtils.convertToTarget(delegate.get(propertyName), type, s);
} else if (delegate != null) {
try {
result = TypeUtils.convertToTarget(delegate.get(propertyName), type, null);
} catch (Throwable t) {
t.printStackTrace();
}
}
if (result == null) {
props.put(propertyName, Null.INSTANCE);
} else if (result instanceof Serializable) {
props.put(propertyName, result);
} else {
if (log_.isLoggable(Level.FINE)) {
log_.log(Level.FINE,
"Got a value from the document but it's not Serializable. It's a " + result.getClass().getName());
}
props.put(propertyName, result);
}
} catch (Exception e) {
log_.log(Level.WARNING,
"Exception occured attempting to get value from document for " + propertyName + " so we cannot return a value", e);
}
} else if (result == Null.INSTANCE) {
} else {
if (result != null && !type.isAssignableFrom(result.getClass())) {
// System.out.println(propertyName + " returned a " + result.getClass().getName() + " when we asked for a " + type.getName());
try {
Map<String, Object> delegate = getDelegate();
if (delegate instanceof Document) {
Document doc = (Document) delegate;
result = doc.getItemValue(propertyName, type);
} else if (delegate instanceof SessionDescendant) {
Session s = ((SessionDescendant) delegate).getAncestorSession();
result = TypeUtils.convertToTarget(delegate.get(propertyName), type, s);
} else if (delegate != null) {
Object chk = delegate.get(propertyName);
if (chk != null) {
result = TypeUtils.convertToTarget(delegate.get(propertyName), type, null);
}
}
if (result == null) {
props.put(propertyName, Null.INSTANCE);
} else if (result instanceof Serializable) {
props.put(propertyName, result);
} else {
log_.log(Level.FINE,
"Got a value from the document but it's not Serializable. It's a " + result.getClass().getName());
props.put(propertyName, result);
}
} catch (Exception e) {
log_.log(Level.WARNING,
"Exception occured attempting to get value from document for " + propertyName
+ " but we have a value in the cache of type " + result.getClass().getName()
+ " when we were looking for a " + type.getName(),
e);
}
}
}
if (result == Null.INSTANCE) {
result = null;
}
if ("form".equalsIgnoreCase(propertyName)) {
// if (result == null) {
// Factory.println("TEMP DEBUG returning null as value for Form field");
// }
}
if (result == Deferred.INSTANCE) {
System.out.println("Returning Deferred INSTANCE for property " + propertyName);
}
return (T) result;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getProperty(final String key) {
if ("form".equalsIgnoreCase(key)) {
Object result = getProperty(key, String.class);
// System.out.println("Found a " + result.getClass().getName() + " in the form field: '" + (String) result + "'");
return (T) result;
}
Object result = getProperty(key, java.lang.Object.class);
return (T) result;
}
@Override
public Set<String> getPropertyKeys() {
return Collections.unmodifiableSet(getProps().keySet());
}
@SuppressWarnings("rawtypes")
@Override
public void setProperty(final String key, final Object value) {
boolean isEqual = false;
Map<String, Object> props = getProps();
if (props != null) {
if (key != null) {
Object current = null;
if (value != null) {
try {
current = getProperty(key, value.getClass());
} catch (Exception e) {
if (e instanceof ClassCastException) {
current = null;
} else {
throw new RuntimeException(e);
}
}
} else {
current = getProperty(key);
}
if (current == null && value == null) {
// System.out.println("TEMP DEBUG: Both current and new value for " + key + " are null");
return;
}
//NTF The standard equality check has a fast out based on array size, so I think it's safe to use here...
if (key.startsWith(DVertex.IN_PREFIX) || key.startsWith(DVertex.OUT_PREFIX)) {
isEqual = false;//NTF ALWAYS set edge collections. Checking them can be expensive
} else if (value != null && current != null) {
if (!(value instanceof java.util.Collection) && !(value instanceof java.util.Map) && !value.getClass().isArray()) {
isEqual = value.equals(current);
}
if (value instanceof java.util.Collection && current instanceof java.util.Collection) {
if (((java.util.Collection) value).size() == ((java.util.Collection) current).size()) {
Object[] vArray = ((java.util.Collection) value).toArray();
Object[] cArray = ((java.util.Collection) current).toArray();
isEqual = Arrays.deepEquals(vArray, cArray);
} else {
isEqual = false;
}
}
if (value.getClass().isArray() && current.getClass().isArray()) {
Object[] vArray = (Object[]) value;
Object[] cArray = (Object[]) current;
isEqual = Arrays.deepEquals(vArray, cArray);
}
}
if (isEqual) {
// System.out.println("TEMP DEBUG: Not setting property " + key + " because the new value is equal to the existing value");
log_.log(Level.FINE, "Not setting property " + key + " because the new value is equal to the existing value");
}
if (value instanceof Serializable) {
if (current == null || Null.INSTANCE.equals(current)) {
getParent().startTransaction(this);
getChangedPropertiesInt().add(key);
props.put(key, value);
} else if (!isEqual) {
getParent().startTransaction(this);
getChangedPropertiesInt().add(key);
props.put(key, value);
} else {
}
} else if (value == null) {
if (!Null.INSTANCE.equals(current)) {
getParent().startTransaction(this);
getChangedPropertiesInt().add(key);
props.put(key, value);
}
} else {
log_.log(Level.FINE,
"Attempted to set property " + key + " to a non-serializable value: " + value.getClass().getName());
if (current == null || Null.INSTANCE.equals(current)) {
getParent().startTransaction(this);
getChangedPropertiesInt().add(key);
props.put(key, value);
} else if (!isEqual) {
getParent().startTransaction(this);
getChangedPropertiesInt().add(key);
props.put(key, value);
} else {
}
}
} else {
log_.log(Level.WARNING, "propertyName is null on a setProperty request?");
}
} else {
log_.log(Level.WARNING, "Properties are null for element!");
}
}
@SuppressWarnings("unchecked")
@Override
public Object removeProperty(final String key) {
getParent().startTransaction(this);
Object result = getProperty(key);
Map<String, Object> props = getProps();
props.remove(key);
Map<String, Object> source = getDelegate();
source.remove(key);
getRemovedPropertiesInt().add(key);
return result;
}
@Override
public abstract void remove();
void _remove() {
isRemoved_ = true;
getParent().startTransaction(this);
getParent().removeDelegate(this);
}
@Override
public Object getId() {
if (delegateKey_ == null) {
Map<String, Object> delegate = getDelegate();
if (delegate instanceof Document) {
delegateKey_ = new NoteCoordinate((Document) delegate);
} else if (delegate instanceof View) {
delegateKey_ = new NoteCoordinate((View) delegate);
} else if (delegate instanceof ViewEntry) {
delegateKey_ = new ViewEntryCoordinate((ViewEntry) delegate);
}
}
return delegateKey_;
}
@Override
public Class<?> getDelegateType() {
Class<?> result = null;
if (delegate_ == null) {
if (delegateKey_ instanceof ViewEntryCoordinate) {
result = org.openntf.domino.ViewEntry.class;
} else if (delegateKey_ instanceof NoteCoordinate) {
if (((NoteCoordinate) delegateKey_).isView()) {
result = org.openntf.domino.View.class;
} else {
result = org.openntf.domino.Document.class;
}
} else {
result = null;
}
} else {
if (delegate_ instanceof ViewEntry) {
result = org.openntf.domino.ViewEntry.class;
} else if (delegate_ instanceof View) {
result = org.openntf.domino.View.class;
} else if (delegate_ instanceof Document) {
if (delegateKey_ instanceof NoteCoordinate) {
if (((NoteCoordinate) delegateKey_).isView()) {
result = org.openntf.domino.View.class;
} else {
result = org.openntf.domino.Document.class;
}
}
} else {
result = delegate_.getClass();
}
}
return result;
}
@Override
public boolean hasProperty(final String key) {
return getPropertyKeys().contains(key);
}
@Override
public <T> T getProperty(final String key, final Class<T> type, final boolean allowNull) {
T result = getProperty(key, type);
if (allowNull) {
return result;
} else {
if (result == null || Null.INSTANCE == result || Deferred.INSTANCE == result) {
return TypeUtils.getDefaultInstance(type);
} else {
return result;
}
}
}
@Override
public int incrementProperty(final String key) {
// TODO NTF it would be really great to figure out a way to use primitives here
Integer result = getProperty(key, Integer.class);
if (result == null)
result = 0;
setProperty(key, ++result);
return result;
}
@Override
public int decrementProperty(final String key) {
// TODO NTF it would be really great to figure out a way to use primitives here
Integer result = getProperty(key, Integer.class);
if (result == null)
result = 0;
setProperty(key, --result);
return result;
}
@Override
public Map<String, Object> getDelegate() {
if (delegate_ instanceof Document) {
try {
//FIXME: This shouldn't be done this way. .isDead should really know for sure if it is not going to work across threads...
((Document) delegate_).containsKey("Foo");
} catch (Throwable t) {
delegate_ = (Map<String, Object>) getParent().findDelegate(delegateKey_);
if (delegateKey_ == null && delegate_ instanceof Document) {
delegateKey_ = ((Document) delegate_).getMetaversalID();
}
}
} else if (delegate_ instanceof View) {
try {
//FIXME: This shouldn't be done this way. .isDead should really know for sure if it is not going to work across threads...
((View) delegate_).isDefaultView();
} catch (Throwable t) {
delegate_ = (Map<String, Object>) getParent().findDelegate(delegateKey_);
}
}
if (delegate_ == null) {
delegate_ = (Map<String, Object>) getParent().findDelegate(delegateKey_);
}
if (delegate_ == null) {
// System.err.println("Domino graph element " + getClass().getSimpleName() + " has a null delegate for key " + delegateKey_
// + ". This will not turn out well.");
// if (this instanceof DVertex) {
// Throwable t = new Throwable();
// t.printStackTrace();
// }
}
return delegate_;
}
@Override
public void setDelegate(final Map<String, Object> delegate) {
delegate_ = delegate;
}
@Override
public Map<String, Object> toMap(final String[] props) {
FastMap<String, Object> result = new FastMap<String, Object>();
for (String prop : props) {
result.put(prop, getProperty(prop));
}
return result.unmodifiable();
}
@Override
public Map<String, Object> toMap(final Set<String> props) {
return toMap(props.toArray(TypeUtils.DEFAULT_STR_ARRAY));
}
@Override
public void fromMap(final Map<String, Object> map) {
}
protected void applyChanges() {
if (isRemoved_) {
return;//NTF there's no point in applying changes to an element that's been removed.
}
Map<String, Object> props = getProps();
Map<String, Object> delegate = getDelegate();
if (delegate == null) {
throw new IllegalStateException("Get delegate returned null for id " + getId() + " so we cannot apply changes to it.");
}
Set<String> changes = getChangedPropertiesInt();
if (!props.isEmpty() && !changes.isEmpty()) {
// System.out.println("TEMP DEBUG: Writing " + getChangedPropertiesInt().size() + " changed properties for " + getId());
for (String s : getChangedPropertiesInt()) {
String key = s;
Object v = props.get(key);
// System.out.println("TEMP DEBUG: Writing a " + v.getClass().getSimpleName() + " to " + key);
if (s != null && v != null) {
if (s.startsWith(DVertex.IN_PREFIX) || s.startsWith(DVertex.OUT_PREFIX)) {
if (delegate instanceof Document) {
if (v instanceof NoteList) {
byte[] bytes = ((NoteList) v).toByteArray();
// PW: This block is encountered for every edge - EdgeLists always get marked as changed.
// TODO: This needs to check the edges have changed. If they haven't changed, it shouldn't save
((Document) delegate).writeBinary(s, bytes, 2048 * 24);
//FIXME NTF .writeBinary needs to clear any extra items added to the document if the binary content shrank
// System.out.println("TEMP DEBUG: Writing a NoteList (" + ((NoteList) v).size() + ") of size " + bytes.length
// + " to a Document in " + s);
} else {
((Document) delegate).replaceItemValue(s, v, false);
}
} else {
try {
delegate.put(s, v);
} catch (Throwable t) {
System.err.println("ALERT Failed to write a property of " + s + " to element id " + getId()
+ " which has a delegate of type " + delegate.getClass().getName() + " due to a "
+ t.getClass().getSimpleName());
t.printStackTrace();
}
}
} else {
try {
delegate.put(s, v);
} catch (Throwable t) {
System.err.println("ALERT Failed to write a property of " + s + " to element id " + getId() + " due to a "
+ t.getClass().getSimpleName());
t.printStackTrace();
}
}
}
}
getChangedPropertiesInt().clear();
} else {
// System.out.println("TEMP ALERT: No changed properties for element " + getId());
}
for (String key : getRemovedPropertiesInt()) {
delegate.remove(key);
}
getRemovedPropertiesInt().clear();
if (delegate instanceof Document) {
Document doc = (Document) delegate;
// if (!doc.hasItem("form")) {
// System.err.println("Graph element being saved without a form value.");
// }
doc.save();
}
}
@Override
public void rollback() {
getProps().clear();
getChangedPropertiesInt().clear();
getRemovedPropertiesInt().clear();
}
@Override
public void commit() {
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(final Object arg0) {
return getDelegate().containsKey(arg0);
}
@Override
public boolean containsValue(final Object arg0) {
throw new UnsupportedOperationException();
}
@Override
public Set<java.util.Map.Entry<String, Object>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public Object get(final Object arg0) {
//NTF this might not work. We're trying to avoid an infinite loop.
return getDelegate().get(String.valueOf(arg0));
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public Set<String> keySet() {
return getPropertyKeys();
}
@Override
public Object put(final String arg0, final Object arg1) {
Map delegate = getDelegate();
if (delegate == null) {
throw new IllegalStateException("An element of type " + getClass().getSimpleName() + " with id " + getId()
+ " has no delegate and therefore cannot put new value updates.");
}
getDelegate().put(arg0, arg1);
return arg1;
}
@Override
public void putAll(final Map<? extends String, ? extends Object> arg0) {
throw new UnsupportedOperationException();
}
@Override
public Object remove(final Object arg0) {
Object result = getProperty(String.valueOf(arg0));
removeProperty(String.valueOf(arg0));
return result;
}
@Override
public int size() {
return getPropertyKeys().size();
}
@Override
public Collection<Object> values() {
throw new UnsupportedOperationException();
}
@Override
public Document asDocument() {
Document result = null;
Object raw = getDelegate();
if (raw instanceof Document) {
result = (Document) raw;
} else if (raw instanceof DProxyVertex) {
result = (Document) ((DProxyVertex) raw).getDelegate();
// System.out.println("Element has a delegate of a DProxyVertex. It should be the other way around?")
}
return result;
}
}