/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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. */ package com.cinchapi.concourse.server.concurrent; import java.nio.ByteBuffer; import java.util.Arrays; import javax.annotation.Nullable; import com.cinchapi.concourse.server.model.Text; import com.cinchapi.concourse.server.model.Value; import com.cinchapi.concourse.thrift.Operator; import com.cinchapi.concourse.util.ByteBuffers; /** * A specialized {@link Token} that is used to define the scope of a lock * acquired by a {@link RangeLockService}. * <p> * In general, callers interested in range locking should not use a RangeToken * directly but should feed the relevant data to * {@link RangeLockService#getReadLock(Text, Operator, Value...)} or * {@link RangeLockService#getWriteLock(Text, Value)}. * </p> * * @author Jeff Nelson */ public class RangeToken extends Token { /** * Return a {@link RangeToken} that wraps {@code key} {@code operator} * {@code values} for the purpose of reading. * * @param key * @param operator * @param values * @return the RangeToken */ public static RangeToken forReading(Text key, Operator operator, Value... values) { // Check to see what, if any, additional range values must be added to // properly block writers that may interfere with our read. int length = values.length; if(operator == Operator.GREATER_THAN || operator == Operator.GREATER_THAN_OR_EQUALS) { values = Arrays.copyOf(values, length + 1); values[length] = Value.POSITIVE_INFINITY; } else if(operator == Operator.LESS_THAN || operator == Operator.LESS_THAN_OR_EQUALS) { values = Arrays.copyOf(values, length + 1); values[length] = Value.NEGATIVE_INFINITY; } else if(operator == Operator.REGEX || operator == Operator.NOT_REGEX) { // NOTE: This will block any writers on the #key whenever there is a // REGEX or NOT_REGEX read, which isn't the most efficient approach, // but is the least burdensome, which is okay for now... values = ALL_VALUES; } return new RangeToken(key, operator, values); } /** * Return a {@link RangeToken} that wraps {@code key} as {@code value} for * the purpose of writing. * * @param key * @param value * @return the RangeToken */ public static RangeToken forWriting(Text key, Value value) { return new RangeToken(key, null, value); } /** * Return the RangeToken encoded in {@code bytes} so long as those bytes * adhere to the format specified by the {@link #getBytes()} method. This * method assumes that all the bytes in the {@code bytes} belong to the * RangeToken. In general, it is necessary to get the appropriate RangeToken * slice from the parent ByteBuffer using * {@link ByteBuffers#slice(ByteBuffer, int, int)}. * * @param bytes * @return the RangeToken */ public static RangeToken fromByteBuffer(ByteBuffer bytes) { return new RangeToken(bytes); } /** * Return the ByteBuffer with the serialized form appropriate for a * RangeToken that describes {@code key} {@code operator} {@code values}. * * @param key * @param operator * @param values * @return the ByteBuffer */ private static ByteBuffer serialize(Text key, Operator operator, Value... values) { int size = 1 + 4 + key.size() + (4 * values.length); for (Value value : values) { size += value.size(); } ByteBuffer bytes = ByteBuffer.allocate(size); bytes.put(operator != null ? (byte) operator.ordinal() : NULL_OPERATOR); bytes.putInt(key.size()); key.copyTo(bytes); for (Value value : values) { bytes.putInt(value.size()); value.copyTo(bytes); } bytes.rewind(); return bytes; } /** * A static reference to the range that indicates a RangeToken covers the * entire range of values. */ private static final Value[] ALL_VALUES = { Value.NEGATIVE_INFINITY, Value.POSITIVE_INFINITY }; /** * A flag to indicate that {@link #operator} is NULL in the serialized form. */ private static final byte NULL_OPERATOR = (byte) Operator.values().length; /** * An empty byte buffer that is passed off to the super constructor. */ private static final ByteBuffer NULL_BYTES = ByteBuffer.wrap(new byte[0]); private final Text key; @Nullable private final Operator operator; private final Value[] values; @Nullable private ByteBuffer bytes; /** * Construct a new instance. * * @param bytes */ private RangeToken(ByteBuffer bytes) { super(bytes); byte operator = bytes.get(); this.operator = operator == NULL_OPERATOR ? null : Operator.values()[operator]; this.key = Text.fromByteBuffer(ByteBuffers.get(bytes, bytes.getInt())); this.values = new Value[this.operator == Operator.BETWEEN ? 2 : 1]; int i = 0; while (bytes.hasRemaining()) { values[i] = Value.fromByteBuffer(ByteBuffers.get(bytes, bytes.getInt())); } } /** * Construct a new instance. * * @param key * @param operator * @param values */ private RangeToken(Text key, Operator operator, Value... values) { super(NULL_BYTES); this.key = key; this.operator = operator; this.values = values; } /** * Return the {@code key} associated with this RangeToken. * * @return the key */ public Text getKey() { return key; } /** * Return the {@code operator} associated with this RangeToken, if it * exists. * * @return the operator */ @Nullable public Operator getOperator() { return operator; } /** * Return the collection of {@code values} associated with this RangeToken. * * @return the values */ public Value[] getValues() { return values; } /** * Return {@code true} if this RangeToken intersects the {@code other} * RangeToken. This RangeToken is considered to intersect another one if the * left point of this RangeToken is less than or equal to the right point of * the other one and the right point of this RangeToken is greater than or * equal to the left point of the other one. * * @param other * @return {@code true} if this RangeToken intersects the other */ public boolean intersects(RangeToken other) { Value value = other.values[0]; Value myValue = this.values[0]; Operator myOperator = this.operator == null ? Operator.EQUALS : this.operator; Operator operator = other.operator == null ? Operator.EQUALS : other.operator; switch (myOperator) { case EQUALS: switch (operator) { case EQUALS: return myValue.compareTo(value) == 0; case NOT_EQUALS: return myValue.compareTo(value) != 0; case GREATER_THAN: return myValue.compareTo(value) > 0; case GREATER_THAN_OR_EQUALS: return myValue.compareTo(value) >= 0; case LESS_THAN: return myValue.compareTo(value) < 0; case LESS_THAN_OR_EQUALS: return myValue.compareTo(value) <= 0; case BETWEEN: return myValue.compareTo(value) >= 0 && myValue.compareTo(other.values[1]) < 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case NOT_EQUALS: switch (operator) { case EQUALS: return myValue.compareTo(value) != 0; case NOT_EQUALS: case GREATER_THAN: case GREATER_THAN_OR_EQUALS: case LESS_THAN: case LESS_THAN_OR_EQUALS: return true; case BETWEEN: return myValue.compareTo(value) != 0 || myValue.compareTo(other.values[1]) != 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case GREATER_THAN: switch (operator) { case EQUALS: case LESS_THAN: case LESS_THAN_OR_EQUALS: return value.compareTo(myValue) > 0; case NOT_EQUALS: case GREATER_THAN: case GREATER_THAN_OR_EQUALS: return true; case BETWEEN: return other.values[1].compareTo(myValue) > 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case GREATER_THAN_OR_EQUALS: switch (operator) { case EQUALS: case LESS_THAN_OR_EQUALS: return value.compareTo(myValue) >= 0; case LESS_THAN: return value.compareTo(myValue) > 0; case NOT_EQUALS: case GREATER_THAN: case GREATER_THAN_OR_EQUALS: return true; case BETWEEN: return other.values[1].compareTo(myValue) > 0; // end of range // not // included for BETWEEN case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case LESS_THAN: switch (operator) { case EQUALS: case GREATER_THAN: case GREATER_THAN_OR_EQUALS: return value.compareTo(myValue) < 0; case NOT_EQUALS: case LESS_THAN: case LESS_THAN_OR_EQUALS: return true; case BETWEEN: return value.compareTo(myValue) < 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case LESS_THAN_OR_EQUALS: switch (operator) { case EQUALS: case GREATER_THAN_OR_EQUALS: return value.compareTo(myValue) <= 0; case LESS_THAN: case LESS_THAN_OR_EQUALS: case NOT_EQUALS: return true; case GREATER_THAN: return value.compareTo(myValue) < 0; case BETWEEN: return value.compareTo(myValue) <= 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case BETWEEN: Value myOtherValue = this.values[1]; switch (operator) { case EQUALS: return value.compareTo(myValue) >= 0 && value.compareTo(myOtherValue) < 0; case NOT_EQUALS: return value.compareTo(myValue) != 0 || value.compareTo(myOtherValue) != 0; case GREATER_THAN: case GREATER_THAN_OR_EQUALS: return value.compareTo(myOtherValue) < 0; case LESS_THAN: return value.compareTo(myValue) > 0; case LESS_THAN_OR_EQUALS: return value.compareTo(myValue) >= 0; case BETWEEN: return myOtherValue.compareTo(value) >= 0 && myValue.compareTo(other.values[1]) <= 0; case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } case REGEX: case NOT_REGEX: return true; default: throw new UnsupportedOperationException(); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(key); sb.append(operator != null ? " " + operator : " AS"); for (Value value : values) { sb.append(" " + value); } return sb.toString(); } @Override public ByteBuffer getBytes() { if(bytes == null) { bytes = serialize(key, operator, values); } return ByteBuffers.asReadOnlyBuffer(bytes); } @Override public int size() { if(bytes == null) { bytes = serialize(key, operator, values); } return bytes.capacity(); } @Override public void copyTo(ByteBuffer buffer) { if(bytes == null) { bytes = serialize(key, operator, values); } ByteBuffers.copyAndRewindSource(bytes, buffer); } }