org.jsimpledb.CopyState Maven / Gradle / Ivy
Show all versions of jsimpledb-main Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package org.jsimpledb;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import javax.annotation.concurrent.NotThreadSafe;
import org.jsimpledb.core.DeletedObjectException;
import org.jsimpledb.core.ObjId;
import org.jsimpledb.core.ReferenceField;
import org.jsimpledb.core.util.ObjIdMap;
import org.jsimpledb.core.util.ObjIdSet;
/**
* Maintains state about a multi-object copy operation.
*
*
* This class keeps tracks of which objects have already been copied when copying objects between transactions.
*
*
* In addition, it supports assigning different object ID's to objects as they are copied into the destination
* transaction.
*
*
* Instances are not thread safe.
*
* @see JObject#copyTo JObject.copyTo()
* @see JObject#cascadeCopyTo JObject.cascadeCopyTo()
*/
@NotThreadSafe
public class CopyState implements Cloneable {
// Hitch-hiker used by JTransaction.copyTo(). Maps referred-to-but-deleted object -> referring object & field
/*final*/ ObjIdMap deletedAssignments = new ObjIdMap<>();
private final TreeMap traversedMap = new TreeMap<>(Ints.lexicographicalComparator());
private /*final*/ ObjIdSet copied;
private /*final*/ ObjIdMap objectIdMap;
private boolean suppressNotifications;
/**
* Default constructor.
*
*
* Object ID's will not be remapped.
*
*
* Equivalent to: {@link #CopyState(ObjIdSet, ObjIdMap) CopyState(new ObjIdSet(), null)}.
*/
public CopyState() {
this(new ObjIdSet(), null);
}
/**
* Default remapping constructor.
*
*
* Object ID's will be remapped based on {@code objectIdMap}.
*
*
* Equivalent to: {@link #CopyState(ObjIdSet, ObjIdMap) CopyState(new ObjIdSet(), objectIdMap)}.
*
* @param objectIdMap mapping from source object ID to destination object ID,
* or null to disable object ID remapping (i.e., use the same ID)
*/
public CopyState(ObjIdMap objectIdMap) {
this(new ObjIdSet(), objectIdMap);
}
/**
* Primary constructor.
*
*
* This constructor allows an object ID map to be provided via {@code objectIdMap}, which specifies the
* destination transaction object ID to use when copying the corresponding source transaction object.
*
* @param copied the ID's of objects that have already been copied
* @param objectIdMap mapping from source object ID to destination object ID,
* or null to disable object ID remapping (i.e., use the same ID)
* @throws IllegalArgumentException if {@code copied} is null
*/
public CopyState(ObjIdSet copied, ObjIdMap objectIdMap) {
Preconditions.checkArgument(copied != null, "null copied");
this.copied = copied;
this.objectIdMap = objectIdMap;
}
/**
* Determine if an object has already been copied, and if not mark it so.
*
* @param id object ID (in the source transaction) of the object being copied
* @return true if {@code id} was not previously marked copied, otherwise false
* @throws IllegalArgumentException if {@code id} is null
*/
public boolean markCopied(ObjId id) {
Preconditions.checkArgument(id != null, "null id");
return this.copied.add(id);
}
/**
* Determine if an object has been marked as copied.
*
* @param id object ID (in the source transaction) of the object being copied
* @return true if {@code id} has been marked copied, otherwise false
* @throws IllegalArgumentException if {@code id} is null
*/
public boolean isCopied(ObjId id) {
Preconditions.checkArgument(id != null, "null id");
return this.copied.contains(id);
}
/**
* Determine whether to suppress {@link org.jsimpledb.annotation.OnCreate @OnCreate} and
* {@link org.jsimpledb.annotation.OnCreate @OnChange} notifications in the destination transaction.
*
*
* Note that for notifications to be delivered in a {@link SnapshotJTransaction}, these annotations must
* also have {@code snapshotTransactions = true}, even if this property is set to false.
*
*
* Default is false.
*
* @return true if {@link org.jsimpledb.annotation.OnCreate @OnCreate} and
* {@link org.jsimpledb.annotation.OnCreate @OnChange} notifications should be suppressed, otherwise false
*/
public boolean isSuppressNotifications() {
return this.suppressNotifications;
}
/**
* Configure whether to suppress {@link org.jsimpledb.annotation.OnCreate @OnCreate} and
* {@link org.jsimpledb.annotation.OnCreate @OnChange} notifications in the destination transaction.
*
*
* Note that for notifications to be delivered in a {@link SnapshotJTransaction}, these annotations must
* also have {@code snapshotTransactions = true}, even if this property is set to false.
*
* @param suppressNotifications true if {@link org.jsimpledb.annotation.OnCreate @OnCreate} and
* {@link org.jsimpledb.annotation.OnCreate @OnChange} notifications should be suppressed, otherwise false
*/
public void setSuppressNotifications(boolean suppressNotifications) {
this.suppressNotifications = suppressNotifications;
}
/**
* Determine if the specified reference path has already been traversed starting at the given object, and if not mark it so.
*
* @param id object ID (in the source transaction) of the object being copied
* @param fields reference path storage IDs
* @return true if {@code fields} was not previously marked as traversed from {@code id}, otherwise false
* @throws IllegalArgumentException if either parameter is null
* @throws IllegalArgumentException if {@code fields} has length zero
*/
public boolean markTraversed(ObjId id, int[] fields) {
// Sanity check
Preconditions.checkArgument(id != null, "null id");
Preconditions.checkArgument(fields != null, "null fields");
Preconditions.checkArgument(fields.length > 0, "empty fields");
// Mark the path, and all prefixes of the path, as having been traversed
boolean fullPath = true;
for (int limit = fields.length; limit > 0; limit--) {
final int[] prefix = fullPath ? fields : Arrays.copyOfRange(fields, 0, limit);
if (!this.traversedMap.computeIfAbsent(prefix, p -> new ObjIdSet()).add(id))
return !fullPath; // we have already marked this prefix (and every shorter prefix)
fullPath = false;
}
// Done
return true;
}
/**
* Get the mapping from object ID in the source transaction to object ID to use in the destination transaction.
*
*
* This method returns the {@code objectIdMap} parameter given to the constructor, if any.
*
* @return mapping from source transaction object ID to destination transaction object ID, or null if none configured
*/
public ObjIdMap getObjectIdMap() {
return this.objectIdMap;
}
/**
* Get the object ID to use in the destination transaction for the copy of the object with the given object ID
* in the source transaction.
*
*
* The implementation in {@link CopyState} behaves as follows: if a null {@code objectIdMap} parameter was given
* to the constructor, {@code objectIdMap} does not contain {@code srcId}, or the corresponding value is null,
* it returns {@code srcId}. Otherwise, the corresponding value from {@code objectIdMap} is returned.
*
* @param srcId source transaction object ID
* @return corresponding destination transaction object ID
* @throws IllegalArgumentException if {@code srcId} is null
*/
public ObjId getDestinationId(ObjId srcId) {
Preconditions.checkArgument(srcId != null);
if (this.objectIdMap == null)
return srcId;
final ObjId dstId = this.objectIdMap.get(srcId);
return dstId != null ? dstId : srcId;
}
// Check for any remaining deleted assignments, and remove the deletedAssignments field
void checkDeletedAssignments(JTransaction jtx) {
// Get an arbitrary remaining deleted assignment (if any)
if (this.deletedAssignments.isEmpty())
return;
final Map.Entry entry = this.deletedAssignments.entrySet().iterator().next();
// Throw exception
final DeletedAssignment deletedAssignment = entry.getValue();
final ObjId id = deletedAssignment.getId();
final ReferenceField field = deletedAssignment.getField();
final ObjId targetId = entry.getKey();
throw new DeletedObjectException(targetId, "illegal assignment of deleted object " + targetId
+ " (" + jtx.tx.getTypeDescription(targetId) + ") to " + field + " in object " + id
+ " (" + jtx.tx.getTypeDescription(id) + ")");
}
// Cloneable
@Override
public CopyState clone() {
final CopyState clone;
try {
clone = (CopyState)super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
for (Map.Entry entry : this.traversedMap.entrySet())
clone.traversedMap.put(entry.getKey(), entry.getValue().clone());
clone.copied = this.copied.clone();
clone.objectIdMap = this.objectIdMap.clone();
clone.deletedAssignments = new ObjIdMap<>(); // does not go along
return clone;
}
}