/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.db.record;
import java.io.Serializable;
import java.util.*;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
/**
* Implementation of LinkedHashMap bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() by
* hand when the map is changed.
*
* @author Luca Garulli (l.garulli--at--orientechnologies.com)
*
*/
@SuppressWarnings("serial")
public class OTrackedMap<T> extends LinkedHashMap<Object, T> implements ORecordElement, OTrackedMultiValue<Object, T>, Serializable {
final protected ORecord sourceRecord;
private STATUS status = STATUS.NOT_LOADED;
private List<OMultiValueChangeListener<Object, T>> changeListeners = null;
protected Class<?> genericClass;
private final boolean embeddedCollection;
public OTrackedMap(final ORecord iRecord, final Map<Object, T> iOrigin, final Class<?> cls) {
this(iRecord);
genericClass = cls;
if (iOrigin != null && !iOrigin.isEmpty())
putAll(iOrigin);
}
public OTrackedMap(final ORecord iSourceRecord) {
this.sourceRecord = iSourceRecord;
embeddedCollection = this.getClass().equals(OTrackedMap.class);
}
@Override
public ORecordElement getOwner() {
return sourceRecord;
}
@Override
public T put(final Object key, final T value) {
if (key == null)
throw new IllegalArgumentException("null key not supported by embedded map");
boolean containsKey = containsKey(key);
T oldValue = super.put(key, value);
if (containsKey && oldValue == value)
return oldValue;
if (oldValue instanceof ODocument)
ODocumentInternal.removeOwner((ODocument) oldValue, this);
addOwnerToEmbeddedDoc(value);
if (containsKey)
fireCollectionChangedEvent(new OMultiValueChangeEvent<Object, T>(OMultiValueChangeEvent.OChangeType.UPDATE, key, value,
oldValue));
else
fireCollectionChangedEvent(new OMultiValueChangeEvent<Object, T>(OMultiValueChangeEvent.OChangeType.ADD, key, value));
addNested(value);
return oldValue;
}
private void addNested(T element) {
if (element instanceof OTrackedMultiValue) {
((OTrackedMultiValue) element)
.addChangeListener(new ONestedValueChangeListener((ODocument) sourceRecord, this, (OTrackedMultiValue) element));
}
}
private void addOwnerToEmbeddedDoc(T e) {
if (embeddedCollection && e instanceof ODocument && !((ODocument) e).getIdentity().isValid())
ODocumentInternal.addOwner((ODocument) e, this);
if (e instanceof ODocument)
ORecordInternal.track(sourceRecord, (ODocument) e);
}
@Override
public T remove(final Object iKey) {
boolean containsKey = containsKey(iKey);
final T oldValue = super.remove(iKey);
if (oldValue instanceof ODocument)
ODocumentInternal.removeOwner((ODocument) oldValue, this);
if (containsKey) {
fireCollectionChangedEvent(
new OMultiValueChangeEvent<Object, T>(OMultiValueChangeEvent.OChangeType.REMOVE, iKey, null, oldValue));
removeNested(oldValue);
}
return oldValue;
}
@Override
public void clear() {
final Map<Object, T> origValues;
if (changeListeners == null)
origValues = null;
else
origValues = new HashMap<Object, T>(this);
if (origValues == null) {
for (T value : super.values())
if (value instanceof ODocument) {
ODocumentInternal.removeOwner((ODocument) value, this);
}
}
super.clear();
if (origValues != null) {
for (Map.Entry<Object, T> entry : origValues.entrySet()) {
if (entry.getValue() instanceof ODocument) {
ODocumentInternal.removeOwner((ODocument) entry.getValue(), this);
}
fireCollectionChangedEvent(new OMultiValueChangeEvent<Object, T>(OMultiValueChangeEvent.OChangeType.REMOVE, entry.getKey(),
null, entry.getValue()));
removeNested(entry.getValue());
}
} else
setDirty();
}
private void removeNested(Object element){
if(element instanceof OTrackedMultiValue){
// ((OTrackedMultiValue) element).removeRecordChangeListener(null);
}
}
@Override
public void putAll(Map<?, ? extends T> m) {
for (Map.Entry<?, ? extends T> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@SuppressWarnings({ "unchecked" })
public OTrackedMap<T> setDirty() {
if (status != STATUS.UNMARSHALLING && sourceRecord != null
&& !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord)))
sourceRecord.setDirty();
return this;
}
@Override
public void setDirtyNoChanged() {
if (status != STATUS.UNMARSHALLING && sourceRecord != null)
sourceRecord.setDirtyNoChanged();
}
public STATUS getInternalStatus() {
return status;
}
public void setInternalStatus(final STATUS iStatus) {
status = iStatus;
}
public void addChangeListener(OMultiValueChangeListener<Object, T> changeListener) {
if(changeListeners == null)
changeListeners = new LinkedList<OMultiValueChangeListener<Object, T>>();
changeListeners.add(changeListener);
}
public void removeRecordChangeListener(OMultiValueChangeListener<Object, T> changeListener) {
if (changeListeners != null)
changeListeners.remove(changeListener);
}
public Map<Object, T> returnOriginalState(final List<OMultiValueChangeEvent<Object, T>> multiValueChangeEvents) {
final Map<Object, T> reverted = new HashMap<Object, T>(this);
final ListIterator<OMultiValueChangeEvent<Object, T>> listIterator = multiValueChangeEvents.listIterator(multiValueChangeEvents
.size());
while (listIterator.hasPrevious()) {
final OMultiValueChangeEvent<Object, T> event = listIterator.previous();
switch (event.getChangeType()) {
case ADD:
reverted.remove(event.getKey());
break;
case REMOVE:
reverted.put(event.getKey(), event.getOldValue());
break;
case UPDATE:
reverted.put(event.getKey(), event.getOldValue());
break;
default:
throw new IllegalArgumentException("Invalid change type : " + event.getChangeType());
}
}
return reverted;
}
public void fireCollectionChangedEvent(final OMultiValueChangeEvent<Object, T> event) {
if (status == STATUS.UNMARSHALLING)
return;
setDirty();
if (changeListeners != null) {
for (final OMultiValueChangeListener<Object, T> changeListener : changeListeners) {
if (changeListener != null)
changeListener.onAfterRecordChanged(event);
}
}
}
public Class<?> getGenericClass() {
return genericClass;
}
public void setGenericClass(Class<?> genericClass) {
this.genericClass = genericClass;
}
private Object writeReplace() {
return new LinkedHashMap<Object, T>(this);
}
@Override
public void replace(OMultiValueChangeEvent<Object, Object> event, Object newValue) {
super.put(event.getKey(), (T) newValue);
addNested((T) newValue);
}
}