/* * * * 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 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; import java.io.Serializable; import java.util.*; /** * Implementation of ArrayList bound to a source ORecord object to keep track of changes for literal types. This avoid to call the * makeDirty() by hand when the list is changed. * * @author Luca Garulli (l.garulli--at--orientechnologies.com) * */ @SuppressWarnings({ "serial" }) public class OTrackedList<T> extends ArrayList<T> implements ORecordElement, OTrackedMultiValue<Integer, T>, Serializable { protected final ORecord sourceRecord; private STATUS status = STATUS.NOT_LOADED; protected List<OMultiValueChangeListener<Integer, T>> changeListeners = null; protected Class<?> genericClass; private final boolean embeddedCollection; public OTrackedList(final ORecord iRecord, final Collection<? extends T> iOrigin, final Class<?> iGenericClass) { this(iRecord); genericClass = iGenericClass; if (iOrigin != null && !iOrigin.isEmpty()) addAll(iOrigin); } public OTrackedList(final ORecord iSourceRecord) { this.sourceRecord = iSourceRecord; embeddedCollection = this.getClass().equals(OTrackedList.class); } @Override public ORecordElement getOwner() { return sourceRecord; } @Override public boolean add(T element) { final boolean result = super.add(element); if (result) { addOwnerToEmbeddedDoc(element); fireCollectionChangedEvent(new OMultiValueChangeEvent<Integer, T>(OMultiValueChangeEvent.OChangeType.ADD, super.size() - 1, element)); } addNested(element); return result; } @Override public boolean addAll(final Collection<? extends T> c) { for (T o : c) { add(o); } return true; } @Override public void add(int index, T element) { super.add(index, element); addOwnerToEmbeddedDoc(element); addNested(element); fireCollectionChangedEvent(new OMultiValueChangeEvent<Integer, T>(OMultiValueChangeEvent.OChangeType.ADD, index, element)); } @Override public T set(int index, T element) { final T oldValue = super.set(index, element); if (oldValue != null && !oldValue.equals(element)) { if (oldValue instanceof ODocument) ODocumentInternal.removeOwner((ODocument) oldValue, this); addOwnerToEmbeddedDoc(element); fireCollectionChangedEvent(new OMultiValueChangeEvent<Integer, T>(OMultiValueChangeEvent.OChangeType.UPDATE, index, element, oldValue)); } addNested(element); 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(int index) { final T oldValue = super.remove(index); if (oldValue instanceof ODocument) { ODocumentInternal.removeOwner((ODocument) oldValue, this); } fireCollectionChangedEvent(new OMultiValueChangeEvent<Integer, T>(OMultiValueChangeEvent.OChangeType.REMOVE, index, null, oldValue)); removeNested(oldValue); return oldValue; } private void removeNested(Object element){ if(element instanceof OTrackedMultiValue){ // ((OTrackedMultiValue) element).removeRecordChangeListener(null); } } @Override public boolean remove(Object o) { final int index = indexOf(o); if (index >= 0) { remove(index); return true; } return false; } @Override public boolean removeAll(Collection<?> c) { boolean removed = false; for (Object o : c) removed = removed | remove(o); return removed; } @Override public void clear() { final List<T> origValues; if (changeListeners!=null && changeListeners.isEmpty()) origValues = null; else origValues = new ArrayList<T>(this); if (origValues == null) { for (final T item : this) { if (item instanceof ODocument) ODocumentInternal.removeOwner((ODocument) item, this); } } super.clear(); if (origValues != null) for (int i = origValues.size() - 1; i >= 0; i--) { final T origValue = origValues.get(i); if (origValue instanceof ODocument) ODocumentInternal.removeOwner((ODocument) origValue, this); fireCollectionChangedEvent(new OMultiValueChangeEvent<Integer, T>(OMultiValueChangeEvent.OChangeType.REMOVE, i, null, origValue)); removeNested(origValue); } else setDirty(); } public void reset() { super.clear(); } @SuppressWarnings("unchecked") public <RET> RET setDirty() { if (status != STATUS.UNMARSHALLING && sourceRecord != null && !(sourceRecord.isDirty() && ORecordInternal.isContentChanged(sourceRecord))) sourceRecord.setDirty(); return (RET) this; } @Override public void setDirtyNoChanged() { if (status != STATUS.UNMARSHALLING && sourceRecord != null) sourceRecord.setDirtyNoChanged(); } public void addChangeListener(final OMultiValueChangeListener<Integer, T> changeListener) { if(changeListeners==null){ changeListeners = new LinkedList<OMultiValueChangeListener<Integer, T>>(); } changeListeners.add(changeListener); } public void removeRecordChangeListener(final OMultiValueChangeListener<Integer, T> changeListener) { if(changeListeners!=null) { changeListeners.remove(changeListener); } } public List<T> returnOriginalState(final List<OMultiValueChangeEvent<Integer, T>> multiValueChangeEvents) { final List<T> reverted = new ArrayList<T>(this); final ListIterator<OMultiValueChangeEvent<Integer, T>> listIterator = multiValueChangeEvents .listIterator(multiValueChangeEvents.size()); while (listIterator.hasPrevious()) { final OMultiValueChangeEvent<Integer, T> event = listIterator.previous(); switch (event.getChangeType()) { case ADD: reverted.remove(event.getKey().intValue()); break; case REMOVE: reverted.add(event.getKey(), event.getOldValue()); break; case UPDATE: reverted.set(event.getKey(), event.getOldValue()); break; default: throw new IllegalArgumentException("Invalid change type : " + event.getChangeType()); } } return reverted; } public void fireCollectionChangedEvent(final OMultiValueChangeEvent<Integer, T> event) { if (status == STATUS.UNMARSHALLING) return; setDirty(); if (changeListeners != null) { for (final OMultiValueChangeListener<Integer, T> changeListener : changeListeners) { if (changeListener != null) changeListener.onAfterRecordChanged(event); } } } public STATUS getInternalStatus() { return status; } public void setInternalStatus(final STATUS iStatus) { status = iStatus; } public Class<?> getGenericClass() { return genericClass; } public void setGenericClass(Class<?> genericClass) { this.genericClass = genericClass; } private Object writeReplace() { return new ArrayList<T>(this); } @Override public void replace(OMultiValueChangeEvent<Object, Object> event, Object newValue) { super.set((Integer) event.getKey(), (T) newValue); addNested((T) newValue); } }