/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Lachlan Dowding
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package permafrost.tundra.data;
import com.wm.data.DataException;
import com.wm.data.IData;
import com.wm.data.IDataCursor;
import com.wm.data.IDataFactory;
import com.wm.data.IDataHashCursor;
import com.wm.data.IDataIndexCursor;
import com.wm.data.IDataPortable;
import com.wm.data.IDataSharedCursor;
import com.wm.data.IDataTreeCursor;
import com.wm.util.Table;
import com.wm.util.coder.IDataCodable;
import com.wm.util.coder.ValuesCodable;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.Queue;
/**
* Wraps an IData in an IData and Map compatible wrapper that makes copies of the wrapped IData and its
* nested IData children when they are written to.
*/
public class CopyOnWriteIDataMap extends IDataMap implements Cloneable, Serializable {
private static final long serialVersionUID = 1;
/**
* Whether this object has been written to.
*/
protected volatile boolean copied = false;
/**
* Construct a new CopyOnWriteIDataMap object.
*
* @param document The IData document to wrap in a CopyOnWriteIDataMap representation.
*/
public CopyOnWriteIDataMap(IData document) {
super(document);
}
/**
* Construct a new CopyOnWriteIDataMap object.
*
* @param document The IData document to be wrapped.
* @param comparator The IDataComparator to be used to compare IData objects.
*/
public CopyOnWriteIDataMap(IData document, IDataComparator comparator) {
this(document);
setComparator(comparator);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given IDataCodable object.
*
* @param codable The IDataCodable object to be wrapped.
*/
public CopyOnWriteIDataMap(IDataCodable codable) {
super(codable);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given IDataCodable object.
*
* @param codable The IDataCodable object to be wrapped.
* @param comparator The IDataComparator to be used to compare IData objects.
*/
public CopyOnWriteIDataMap(IDataCodable codable, IDataComparator comparator) {
this(codable);
setComparator(comparator);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given IDataPortable object.
*
* @param portable The IDataPortable object to be wrapped.
*/
public CopyOnWriteIDataMap(IDataPortable portable) {
super(portable);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given IDataPortable object.
*
* @param portable The IDataPortable object to be wrapped.
* @param comparator The IDataComparator to be used to compare IData objects.
*/
public CopyOnWriteIDataMap(IDataPortable portable, IDataComparator comparator) {
this(portable);
setComparator(comparator);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given ValuesCodable object.
*
* @param codable The ValuesCodable object to be wrapped.
*/
public CopyOnWriteIDataMap(ValuesCodable codable) {
super(codable);
}
/**
* Constructs a new CopyOnWriteIDataMap wrapping the given ValuesCodable object.
*
* @param codable The ValuesCodable object to be wrapped.
* @param comparator The IDataComparator to be used to compare IData objects.
*/
public CopyOnWriteIDataMap(ValuesCodable codable, IDataComparator comparator) {
this(codable);
setComparator(comparator);
}
/**
* Constructs a new CopyOnWriteIDataMap seeded with the given Map of key value entries.
*
* @param map The map to see this new object with.
*/
public CopyOnWriteIDataMap(Map<? extends String, ?> map) {
this(IDataHelper.toIData(map));
}
/**
* Constructs a new CopyOnWriteIDataMap seeded with the given Map of key value entries.
*
* @param map The map to see this new object with.
* @param comparator The IDataComparator to be used to compare IData objects.
*/
public CopyOnWriteIDataMap(Map<? extends String, ?> map, IDataComparator comparator) {
this(map);
setComparator(comparator);
}
/**
* Returns a new CopyOnWriteIDataMap wrapping the given IData document.
*
* @param document The document to be wrapped.
* @return A new CopyOnWriteIDataMap wrapping the given IData document.
*/
public static CopyOnWriteIDataMap of(IData document) {
return new CopyOnWriteIDataMap(document);
}
/**
* Returns a new CopyOnWriteIDataMap[] representation of the given IData[] document list.
*
* @param array An IData[] document list.
* @return A new CopyOnWriteIDataMap[] representation of the given IData[] document list.
*/
public static CopyOnWriteIDataMap[] of(IData[] array) {
if (array == null) return null;
CopyOnWriteIDataMap[] output = new CopyOnWriteIDataMap[array.length];
for (int i = 0; i < array.length; i++) {
if (array[i] != null) {
output[i] = CopyOnWriteIDataMap.of(array[i]);
}
}
return output;
}
/**
* Converts the given value if it is an IData or IData[] compatible object to a CopyOnWriteIDataMap or
* CopyOnWriteIDataMap[] respectively.
*
* @param value The value to be normalized.
* @return If the value is an IData or IData[] compatible object, a new CopyOnWriteIDataMap or
* CopyOnWriteIDataMap[] respectively is returned which wraps the given value, otherwise
* the value itself is returned unmodified.
*/
private static Object normalize(Object value) {
if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[]) {
value = CopyOnWriteIDataMap.of(IDataHelper.toIDataArray(value));
} else if (value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) {
value = CopyOnWriteIDataMap.of(IDataHelper.toIData(value));
}
return value;
}
/**
* Updates document to be a copy of itself the first time this method is called, subsequent calls do nothing.
*
* @return True if a copy was made, false otherwise.
*/
private synchronized boolean copyOnWrite() {
if (this.copied) return false;
IData clone = IDataFactory.create();
IDataCursor documentCursor = this.document.getCursor();
IDataCursor cloneCursor = clone.getCursor();
while(documentCursor.next()) {
cloneCursor.insertAfter(documentCursor.getKey(), normalize(documentCursor.getValue()));
}
documentCursor.destroy();
cloneCursor.destroy();
this.document = clone;
this.copied = true;
return this.copied;
}
/**
* Returns an IDataCursor for this IData object. An IDataCursor contains the basic methods you use to traverse an
* IData object and get or set elements within it.
*
* @return An IDataCursor for this object.
*/
@Override
public IDataCursor getCursor() {
return new CopyOnWriteIDataCursor();
}
/**
* Returns an IDataSharedCursor for this IData object. An IDataSharedCursor contains the basic methods you use to
* traverse an IData object and get or set elements within it.
*
* @return An IDataSharedCursor for this object.
*/
@Override
public IDataSharedCursor getSharedCursor() {
return new CopyOnWriteIDataSharedCursor();
}
/**
* Returns an IDataIndexCursor for traversing this IData.
*
* @return An IDataIndexCursor for traversing this IData.
* @throws UnsupportedOperationException As this method is not implemented.
* @deprecated
*/
@Override
public IDataIndexCursor getIndexCursor() {
throw new UnsupportedOperationException("getIndexCursor not implemented");
}
/**
* Returns an IDataTreeCursor for traversing this IData.
*
* @return An IDataTreeCursor for traversing this IData.
* @throws UnsupportedOperationException As this method is not implemented.
* @deprecated
*/
@Override
public IDataTreeCursor getTreeCursor() {
throw new UnsupportedOperationException("getTreeCursor not implemented");
}
/**
* Returns an IDataHashCursor for traversing this IData.
*
* @return An IDataHashCursor for traversing this IData.
* @throws UnsupportedOperationException As this method is not implemented.
* @deprecated
*/
@Override
public IDataHashCursor getHashCursor() {
throw new UnsupportedOperationException("getHashCursor not implemented");
}
/**
* Returns a newly created IData object.
*
* @return A newly created IData object.
*/
public static IData create() {
return new CopyOnWriteIDataMap((IData)null);
}
/**
* Returns a clone of this IData object.
*
* @return A clone of this IData object.
*/
@Override
public IDataMap clone() {
return new CopyOnWriteIDataMap(document);
}
/**
* Copy on write wrapper for an IDataCursor.
*/
private class CopyOnWriteIDataCursor extends IDataCursorEnvelope {
/**
* The current position of the cursor.
*/
private Queue<PositionCommand> position;
/**
* Whether a copy of the owning IData document has been initiated or not yet.
*/
protected volatile boolean copied;
/**
* Constructs a new cursor.
*/
CopyOnWriteIDataCursor() {
this(null);
}
/**
* Constructs a new cursor with the given position.
*
* @param position The initial position of the cursor.
*/
CopyOnWriteIDataCursor(Queue<PositionCommand> position) {
super(CopyOnWriteIDataMap.this.document.getCursor());
if (position != null && position.size() > 0) {
this.position = new ArrayDeque<PositionCommand>(position);
} else {
this.position = new ArrayDeque<PositionCommand>();
}
initialize();
}
/**
* Initializes this cursor.
*/
protected void initialize() {
if (cursor != null) cursor.destroy();
cursor = CopyOnWriteIDataMap.this.document.getCursor();
for (PositionCommand command : position) {
switch(command.getType()) {
case FIRST:
cursor.first();
break;
case FIRST_KEY:
cursor.first(command.getKey());
break;
case NEXT:
cursor.next();
break;
case NEXT_KEY:
cursor.next(command.getKey());
break;
case PREVIOUS:
cursor.previous();
break;
case PREVIOUS_KEY:
cursor.previous(command.getKey());
break;
case LAST:
cursor.last();
break;
case LAST_KEY:
cursor.last(command.getKey());
break;
}
}
position.clear();
copied = CopyOnWriteIDataMap.this.copied;
}
/**
* Makes a copy of the owning IData document, if required.
*
* @return True if a copy was made or the state of the owning IData document changed in the interim.
*/
private boolean copy() {
boolean wasCopied = copyOnWrite();
boolean stateChanged = (wasCopied || (this.copied != CopyOnWriteIDataMap.this.copied));
if (stateChanged) initialize();
return stateChanged;
}
/**
* Returns the given value, optionally copied if required.
*
* @param value The value to be normalized.
* @return The normalized value.
*/
private Object normalize(Object value) {
if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[] || value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) {
if (copy()) {
value = cursor.getValue();
}
}
return value;
}
/**
* Resets this cursor to be unpositioned.
*/
@Override
public void home() {
position.clear();
cursor.home();
}
/**
* Sets the key at the cursor's current position.
*
* @param key The key to be set.
*/
@Override
public void setKey(String key) {
copy();
cursor.setKey(key);
}
/**
* Returns the value at the cursor's current position.
*
* @return The value at the cursor's current position.
*/
@Override
public Object getValue() {
return normalize(cursor.getValue());
}
/**
* Sets the value at the cursor's current position.
*
* @param value The value to be set.
*/
@Override
public void setValue(Object value) {
copy();
cursor.setValue(value);
}
/**
* Deletes the element at the cursor's current position.
*
* @return True if the element was deleted.
*/
@Override
public boolean delete() {
copy();
return cursor.delete();
}
/**
* Inserts the given element before the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
*/
@Override
public void insertBefore(String key, Object value) {
copy();
cursor.insertBefore(key, value);
}
/**
* Inserts the given element after the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
*/
@Override
public void insertAfter(String key, Object value) {
copy();
cursor.insertAfter(key, value);
}
/**
* Inserts a new IData document with the given key before the cursor's current position.
*
* @param key The key to be inserted.
* @return The newly inserted IData document.
*/
@Override
public IData insertDataBefore(String key) {
copy();
return cursor.insertDataBefore(key);
}
/**
* Inserts a new IData document with the given key after the cursor's current position.
*
* @param key The key to be inserted.
* @return The newly inserted IData document.
*/
@Override
public IData insertDataAfter(String key) {
copy();
return cursor.insertDataAfter(key);
}
/**
* Moves the cursor's position to the next element.
*
* @return True if the cursor was repositioned.
*/
@Override
public boolean next() {
boolean success = cursor.next();
if (success) position.add(new PositionCommand(PositionCommandType.NEXT));
return success;
}
/**
* Moves the cursor's position to the next element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
@Override
public boolean next(String key) {
boolean success = cursor.next(key);
if (success) position.add(new PositionCommand(PositionCommandType.NEXT_KEY, key));
return success;
}
/**
* Moves the cursor's position to the previous element.
*
* @return True if the cursor was repositioned.
*/
@Override
public boolean previous() {
boolean success = cursor.previous();
if (success) position.add(new PositionCommand(PositionCommandType.PREVIOUS));
return success;
}
/**
* Moves the cursor's position to the previous element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
@Override
public boolean previous(String key) {
boolean success = cursor.previous(key);
if (success) position.add(new PositionCommand(PositionCommandType.PREVIOUS_KEY, key));
return success;
}
/**
* Moves the cursor's position to the first element.
*
* @return True if the cursor was repositioned.
*/
@Override
public boolean first() {
boolean success = cursor.first();
if (success) {
// clear previous position commands, as this is an absolute position
position.clear();
position.add(new PositionCommand(PositionCommandType.FIRST));
}
return success;
}
/**
* Moves the cursor's position to the first element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
@Override
public boolean first(String key) {
boolean success = cursor.first(key);
if (success) position.add(new PositionCommand(PositionCommandType.FIRST_KEY, key));
return success;
}
/**
* Moves the cursor's position to the last element.
*
* @return True if the cursor was repositioned.
*/
@Override
public boolean last() {
boolean success = cursor.last();
if (success) {
// clear previous position commands, as this is an absolute position
position.clear();
position.add(new PositionCommand(PositionCommandType.LAST));
}
return success;
}
/**
* Moves the cursor's position to the last element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
*/
@Override
public boolean last(String key) {
boolean success = cursor.last(key);
if (success) position.add(new PositionCommand(PositionCommandType.LAST_KEY, key));
return success;
}
/**
* Returns a clone of this cursor.
*
* @return A clone of this cursor.
*/
@Override
public IDataCursor getCursorClone() {
return new CopyOnWriteIDataCursor(position);
}
}
/**
* Copy on write wrapper for an IDataSharedCursor.
*/
private class CopyOnWriteIDataSharedCursor extends IDataSharedCursorEnvelope {
/**
* The current position of the cursor.
*/
private Queue<PositionCommand> position;
/**
* Whether a copy of the owning IData document has been initiated or not yet.
*/
protected volatile boolean copied;
/**
* Constructs a new cursor.
*/
public CopyOnWriteIDataSharedCursor() {
this(null);
}
/**
* Constructs a new cursor with the given position.
*
* @param position The initial position for the cursor.
*/
public CopyOnWriteIDataSharedCursor(Queue<PositionCommand> position) {
super(CopyOnWriteIDataMap.this.document.getSharedCursor());
if (position != null && position.size() > 0) {
this.position = new ArrayDeque<PositionCommand>(position);
} else {
this.position = new ArrayDeque<PositionCommand>();
}
try {
initialize();
} catch(DataException ex) {
throw new RuntimeException(ex);
}
}
/**
* Initializes the cursor.
*
* @throws DataException If an error occurs.
*/
private void initialize() throws DataException {
if (cursor != null) cursor.destroy();
cursor = CopyOnWriteIDataMap.this.document.getSharedCursor();
for (PositionCommand command : position) {
switch(command.getType()) {
case FIRST:
cursor.first();
break;
case FIRST_KEY:
cursor.first(command.getKey());
break;
case NEXT:
cursor.next();
break;
case NEXT_KEY:
cursor.next(command.getKey());
break;
case PREVIOUS:
cursor.previous();
break;
case PREVIOUS_KEY:
cursor.previous(command.getKey());
break;
case LAST:
cursor.last();
break;
case LAST_KEY:
cursor.last(command.getKey());
break;
}
}
position.clear();
copied = CopyOnWriteIDataMap.this.copied;
}
/**
* Makes a copy of the owning IData document, if required.
*
* @return True if a copy was made or the state of the owning IData document changed in the interim.
* @throws DataException If an error occurs.
*/
private boolean copy() throws DataException {
boolean wasCopied = copyOnWrite();
boolean stateChanged = (wasCopied || (this.copied != CopyOnWriteIDataMap.this.copied));
if (stateChanged) initialize();
return stateChanged;
}
/**
* Returns the given value, optionally copied if required.
*
* @param value The value to be normalized.
* @return The normalized value.
* @throws DataException If an error occurs.
*/
private Object normalize(Object value) throws DataException {
if (value instanceof IData[] || value instanceof Table || value instanceof IDataCodable[] || value instanceof IDataPortable[] || value instanceof ValuesCodable[] || value instanceof IData || value instanceof IDataCodable || value instanceof IDataPortable || value instanceof ValuesCodable) {
if (copy()) {
value = cursor.getValue();
}
}
return value;
}
/**
* Resets the cursor to be unpositioned.
*
* @throws DataException If an error occurs.
*/
@Override
public void home() throws DataException {
position.clear();
cursor.home();
}
/**
* Sets the key at the cursor's current position.
*
* @param key The key to be set.
* @throws DataException If an error occurs.
*/
@Override
public void setKey(String key) throws DataException {
copy();
cursor.setKey(key);
}
/**
* Returns the value at the cursor's current position.
*
* @return The value at the cursor's current position.
* @throws DataException If an error occurs.
*/
@Override
public Object getValue() throws DataException {
return normalize(cursor.getValue());
}
/**
* Sets the value at the cursor's current position.
*
* @param value The value to be set.
* @throws DataException If an error occurs.
*/
@Override
public void setValue(Object value) throws DataException {
copy();
cursor.setValue(value);
}
/**
* Returns the value at the cursor's current position.
*
* @return The value at the cursor's current position.
* @throws DataException If an error occurs.
*/
@Override
public Object getValueReference() throws DataException {
return normalize(cursor.getValueReference());
}
/**
* Deletes the element at the cursor's current position.
*
* @return Returns true if the element was deleted.
* @throws DataException If an error occurs.
*/
@Override
public boolean delete() throws DataException {
copy();
return cursor.delete();
}
/**
* Inserts a new element before the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
* @throws DataException If an error occurred.
*/
@Override
public void insertBefore(String key, Object value) throws DataException {
copy();
cursor.insertBefore(key, value);
}
/**
* Inserts a new element after the cursor's current position.
*
* @param key The key to be inserted.
* @param value The value to be inserted.
* @throws DataException If an error occurs.
*/
@Override
public void insertAfter(String key, Object value) throws DataException {
copy();
cursor.insertAfter(key, value);
}
/**
* Inserts a new IData document with the given key before the cursor's current position.
*
* @param key The key to be inserted.
* @return The new IData document that was inserted.
* @throws DataException If an error occurs.
*/
@Override
public IData insertDataBefore(String key) throws DataException {
copy();
return cursor.insertDataBefore(key);
}
/**
* Inserts a new IData document with the given key after the cursor's current position.
*
* @param key The key to be inserted.
* @return The new IData document that was inserted.
* @throws DataException If an error occurs.
*/
@Override
public IData insertDataAfter(String key) throws DataException {
copy();
return cursor.insertDataAfter(key);
}
/**
* Moves the cursor's position to the next element.
*
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean next() throws DataException {
boolean success = cursor.next();
if (success) position.add(new PositionCommand(PositionCommandType.NEXT));
return success;
}
/**
* Moves the cursor's position to the next element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean next(String key) throws DataException {
boolean success = cursor.next(key);
if (success) position.add(new PositionCommand(PositionCommandType.NEXT_KEY, key));
return success;
}
/**
* Moves the cursor's position to the previous element.
*
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean previous() throws DataException {
boolean success = cursor.previous();
if (success) position.add(new PositionCommand(PositionCommandType.PREVIOUS));
return success;
}
/**
* Moves the cursor's position to the previous element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean previous(String key) throws DataException {
boolean success = cursor.previous(key);
if (success) position.add(new PositionCommand(PositionCommandType.PREVIOUS_KEY, key));
return success;
}
/**
* Moves the cursor's position to the first element.
*
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean first() throws DataException {
boolean success = cursor.first();
if (success) {
// clear previous position commands, as this is an absolute position
position.clear();
position.add(new PositionCommand(PositionCommandType.FIRST));
}
return success;
}
/**
* Moves the cursor's position to the first element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean first(String key) throws DataException {
boolean success = cursor.first(key);
if (success) position.add(new PositionCommand(PositionCommandType.FIRST_KEY, key));
return success;
}
/**
* Moves the cursor's position to the last element.
*
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean last() throws DataException {
boolean success = cursor.last();
if (success) {
// clear previous position commands, as this is an absolute position
position.clear();
position.add(new PositionCommand(PositionCommandType.LAST));
}
return success;
}
/**
* Moves the cursor's position to the last element with the given key.
*
* @param key The key to reposition to.
* @return True if the cursor was repositioned.
* @throws DataException If an error occurs.
*/
@Override
public boolean last(String key) throws DataException {
boolean success = cursor.last(key);
if (success) position.add(new PositionCommand(PositionCommandType.LAST_KEY, key));
return success;
}
/**
* Returns a clone of this cursor.
*
* @return A clone of this cursor.
* @throws DataException If an error occurs.
*/
@Override
public IDataSharedCursor getCursorClone() throws DataException {
return new CopyOnWriteIDataSharedCursor(position);
}
}
/**
* The different types of position commands possible with an IDataCursor.
*/
private static enum PositionCommandType {
FIRST, FIRST_KEY, NEXT, NEXT_KEY, PREVIOUS, PREVIOUS_KEY, LAST, LAST_KEY;
}
/**
* Represents a single position command for an IDataCursor.
*/
private static class PositionCommand {
/**
* The type of position command.
*/
protected PositionCommandType type;
/**
* The optional key used for positioning.
*/
protected String key;
/**
* Constructs a new position command object with the given type.
*
* @param type The type of position command.
*/
public PositionCommand(PositionCommandType type) {
this(type, null);
}
/**
* Constructs a new position command with the given type and key.
*
* @param type The type of position command.
* @param key The key used for applying the command.
*/
public PositionCommand(PositionCommandType type, String key) {
this.type = type;
this.key = key;
}
/**
* Returns the type of position command.
*
* @return The type of position command.
*/
public PositionCommandType getType() {
return type;
}
/**
* Returns the key used for applying the command.
*
* @return The key used for applying the command, or null if not required.
*/
public String getKey() {
return key;
}
}
}