/* * * * 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.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; import com.orientechnologies.common.collection.OLazyIterator; import com.orientechnologies.orient.core.exception.ORecordNotFoundException; import com.orientechnologies.orient.core.record.OIdentityChangeListener; import com.orientechnologies.orient.core.record.ORecord; import com.orientechnologies.orient.core.record.ORecordInternal; import com.orientechnologies.orient.core.record.impl.ODocument; /** * Lazy implementation of Set. Can be bound to a source ORecord object to keep track of changes. This avoid to call the makeDirty() * by hand when the set is changed. * <p> * <b>Internals</b>: * <ul> * <li>stores new records in a separate IdentityHashMap to keep underlying list (delegate) always ordered and minimizing sort * operations</li> * <li></li> * </ul> * * </p> * * @author Luca Garulli (l.garulli--at--orientechnologies.com) * */ public class ORecordLazySet extends ORecordTrackedSet implements Set<OIdentifiable>, ORecordLazyMultiValue, ORecordElement, OIdentityChangeListener { protected boolean autoConvertToRecord = true; public ORecordLazySet(final ODocument iSourceRecord) { super(iSourceRecord); } public ORecordLazySet(ODocument iSourceRecord, Collection<OIdentifiable> iOrigin) { this(iSourceRecord); if (iOrigin != null && !iOrigin.isEmpty()) addAll(iOrigin); } @Override public boolean detach() { return convertRecords2Links(); } @Override public Iterator<OIdentifiable> iterator() { return new OLazyRecordIterator(new OLazyIterator<OIdentifiable>() { { iter = ORecordLazySet.super.map.entrySet().iterator(); } private Iterator<Entry<OIdentifiable, Object>> iter; private Entry<OIdentifiable, Object> last; @Override public boolean hasNext() { return iter.hasNext(); } @Override public OIdentifiable next() { Entry<OIdentifiable, Object> entry = iter.next(); last = entry; if (entry.getValue() != ENTRY_REMOVAL) return (OIdentifiable) entry.getValue(); return entry.getKey(); } @Override public void remove() { iter.remove(); if (last.getKey() instanceof ORecord) ORecordInternal.removeIdentityChangeListener((ORecord) last.getKey(), ORecordLazySet.this); } @Override public OIdentifiable update(OIdentifiable iValue) { if (iValue != null) map.put(iValue.getIdentity(), iValue.getRecord()); return iValue; } }, autoConvertToRecord && getOwner().getInternalStatus() != STATUS.MARSHALLING); } @Override public Iterator<OIdentifiable> rawIterator() { return new OLazyRecordIterator(super.iterator(), false); } @Override public boolean add(OIdentifiable e) { if (map.containsKey(e)) return false; if (e == null) map.put(null, null); else if (e instanceof ORecord && e.getIdentity().isNew()) { ORecordInternal.addIdentityChangeListener((ORecord) e, this); ORecordInternal.track(sourceRecord, e); map.put(e, e); } else if (!e.getIdentity().isPersistent()) { // record id is not fixed yet, so we need to be able to watch for id changes, so get the record for this id to be able to do // this. final ORecord record = e.getRecord(); if (record == null) throw new IllegalArgumentException("Record with id " + e.getIdentity() + " has not be found"); ORecordInternal.addIdentityChangeListener(record, this); ORecordInternal.track(sourceRecord, e); map.put(e, record); } else map.put(e, ENTRY_REMOVAL); setDirty(); fireCollectionChangedEvent(new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>(OMultiValueChangeEvent.OChangeType.ADD, e, e)); return true; } public void convertLinks2Records() { final Iterator<Entry<OIdentifiable, Object>> all = map.entrySet().iterator(); while (all.hasNext()) { Entry<OIdentifiable, Object> entry = all.next(); if (entry.getValue() == ENTRY_REMOVAL) { try { ORecord record = entry.getKey().getRecord(); if (record != null) { ORecordInternal.unTrack(sourceRecord, entry.getKey()); ORecordInternal.track(sourceRecord, record); } entry.setValue(record); } catch (ORecordNotFoundException e) { // IGNORE THIS } } } } @Override public void onAfterIdentityChange(ORecord record) { map.put(record, record); } @Override public void onBeforeIdentityChange(ORecord record) { map.remove(record); } @Override public boolean convertRecords2Links() { return true; } public boolean clearDeletedRecords() { boolean removed = false; final Iterator<Entry<OIdentifiable, Object>> all = map.entrySet().iterator(); while (all.hasNext()) { Entry<OIdentifiable, Object> entry = all.next(); if (entry.getValue() == ENTRY_REMOVAL) { try { if (entry.getKey().getRecord() == null) { all.remove(); removed = true; } } catch (ORecordNotFoundException e) { all.remove(); removed = true; } } } return removed; } public boolean remove(Object o) { if (o == null) return clearDeletedRecords(); final Object old = map.remove(o); if (old != null) { if (o instanceof ORecord) ORecordInternal.removeIdentityChangeListener((ORecord) o, this); setDirty(); fireCollectionChangedEvent(new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>( OMultiValueChangeEvent.OChangeType.REMOVE, (OIdentifiable) o, null, (OIdentifiable) o)); return true; } return false; } @Override public boolean isAutoConvertToRecord() { return autoConvertToRecord; } @Override public void setAutoConvertToRecord(boolean convertToRecord) { this.autoConvertToRecord = convertToRecord; } @Override public boolean equals(Object obj) { if (obj instanceof Set<?>) { Set<Object> coll = ((Set<Object>) obj); if (map.size() == coll.size()) { for (Object obje : coll) { if (!map.containsKey(obje)) return false; } return true; } } return false; } @Override public int hashCode() { return map.hashCode(); } }