/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.sql.Types;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* A class avoiding class duplication between CompositeType and
* DynamicCompositeType.
* Those two differs only in that for DynamicCompositeType, the comparators
* are in the encoded column name at the front of each component.
*/
public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
{
// changes bb position
protected static int getShortLength(ByteBuffer bb)
{
int length = (bb.get() & 0xFF) << 8;
return length | (bb.get() & 0xFF);
}
// changes bb position
protected static void putShortLength(ByteBuffer bb, int length)
{
bb.put((byte) ((length >> 8) & 0xFF));
bb.put((byte) (length & 0xFF));
}
// changes bb position
protected static ByteBuffer getBytes(ByteBuffer bb, int length)
{
ByteBuffer copy = bb.duplicate();
copy.limit(copy.position() + length);
bb.position(bb.position() + length);
return copy;
}
// changes bb position
protected static ByteBuffer getWithShortLength(ByteBuffer bb)
{
int length = getShortLength(bb);
return getBytes(bb, length);
}
public int compare(ByteBuffer o1, ByteBuffer o2)
{
if (null == o1)
return null == o2 ? 0 : -1;
ByteBuffer bb1 = o1.duplicate();
ByteBuffer bb2 = o2.duplicate();
int i = 0;
while (bb1.remaining() > 0 && bb2.remaining() > 0)
{
AbstractType comparator = getNextComparator(i, bb1, bb2);
ByteBuffer value1 = getWithShortLength(bb1);
ByteBuffer value2 = getWithShortLength(bb2);
int cmp = comparator.compare(value1, value2);
if (cmp != 0)
return cmp;
byte b1 = bb1.get();
byte b2 = bb2.get();
if (b1 < 0)
{
if (b2 >= 0)
return -1;
}
else if (b1 > 0)
{
if (b2 <= 0)
return 1;
}
else
{
// b1 == 0
if (b2 != 0)
return -b2;
}
++i;
}
if (bb1.remaining() == 0)
return bb2.remaining() == 0 ? 0 : -1;
// bb1.remaining() > 0 && bb2.remaining() == 0
return 1;
}
public String getString(ByteBuffer bytes)
{
StringBuilder sb = new StringBuilder();
ByteBuffer bb = bytes.duplicate();
int i = 0;
while (bb.remaining() > 0)
{
if (bb.remaining() != bytes.remaining())
sb.append(":");
AbstractType comparator = getAndAppendNextComparator(i, bb, sb);
ByteBuffer value = getWithShortLength(bb);
sb.append(comparator.getString(value));
byte b = bb.get();
if (b != 0)
{
sb.append(":!");
break;
}
++i;
}
return sb.toString();
}
/*
* FIXME: this would break if some of the component string representation
* contains ':'. None of our current comparator do so, so this is probably
* not an urgent matter, but this could break for custom comparator.
* (DynamicCompositeType would break on '@' too)
*/
public ByteBuffer fromString(String source)
{
String[] parts = source.split(":");
List<ByteBuffer> components = new ArrayList<ByteBuffer>();
List<ParsedComparator> comparators = new ArrayList<ParsedComparator>();
int totalLength = 0, i = 0;
boolean lastByteIsOne = false;
for (String part : parts)
{
if (part.equals("!"))
{
lastByteIsOne = true;
break;
}
ParsedComparator p = parseNextComparator(i, part);
AbstractType type = p.getAbstractType();
part = p.getRemainingPart();
ByteBuffer component = type.fromString(part);
totalLength += p.getComparatorSerializedSize() + 2 + component.remaining() + 1;
components.add(component);
comparators.add(p);
++i;
}
ByteBuffer bb = ByteBuffer.allocate(totalLength);
i = 0;
for (ByteBuffer component : components)
{
comparators.get(i).serializeComparator(bb);
putShortLength(bb, component.remaining());
bb.put(component); // it's ok to consume component as we won't use it anymore
bb.put((byte)0);
++i;
}
if (lastByteIsOne)
bb.put(bb.limit() - 1, (byte)1);
bb.rewind();
return bb;
}
public void validate(ByteBuffer bytes) throws MarshalException
{
ByteBuffer bb = bytes.duplicate();
int i = 0;
while (bb.remaining() > 0)
{
AbstractType comparator = validateNextComparator(i, bb);
if (bb.remaining() < 2)
throw new MarshalException("Not enough bytes to read value size of component " + i);
int length = getShortLength(bb);
if (bb.remaining() < length)
throw new MarshalException("Not enough bytes to read value of component " + i);
ByteBuffer value = getBytes(bb, length);
comparator.validate(value);
if (bb.remaining() == 0)
throw new MarshalException("Not enough bytes to read the end-of-component byte of component" + i);
byte b = bb.get();
if (b != 0 && bb.remaining() != 0)
throw new MarshalException("Invalid bytes remaining after an end-of-component at component" + i);
++i;
}
}
public ByteBuffer compose(ByteBuffer bytes)
{
return bytes;
}
public ByteBuffer decompose(ByteBuffer value)
{
return value;
}
public Class<ByteBuffer> getType()
{
return ByteBuffer.class;
}
public String toString(ByteBuffer value)
{
return getString(value);
}
/*
* JDBC metadata. For now we don't allow the use of compositeType with
* JDBC. We'll have to figure out what is the best solution here.
*/
public boolean isSigned()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean isCaseSensitive()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean isCurrency()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getPrecision(ByteBuffer obj)
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getScale(ByteBuffer obj)
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public int getJdbcType()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
public boolean needsQuotes()
{
throw new UnsupportedOperationException("Not support for JDBC yet");
}
abstract protected AbstractType getNextComparator(int i, ByteBuffer bb);
abstract protected AbstractType getNextComparator(int i, ByteBuffer bb1, ByteBuffer bb2);
abstract protected AbstractType getAndAppendNextComparator(int i, ByteBuffer bb, StringBuilder sb);
abstract protected ParsedComparator parseNextComparator(int i, String part);
abstract protected AbstractType validateNextComparator(int i, ByteBuffer bb) throws MarshalException;
protected static interface ParsedComparator
{
AbstractType getAbstractType();
String getRemainingPart();
int getComparatorSerializedSize();
void serializeComparator(ByteBuffer bb);
}
}