/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.types;
import com.foundationdb.server.types.value.*;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.texpressions.Serialization;
import com.foundationdb.server.types.texpressions.SerializeAs;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.util.AkibanAppender;
import com.foundationdb.util.ArgumentValidation;
import com.google.common.primitives.UnsignedBytes;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.regex.Pattern;
public abstract class TClass {
public abstract int jdbcType();
protected abstract DataTypeDescriptor dataTypeDescriptor(TInstance type);
public abstract void fromObject (TExecutionContext contextForErrors, ValueSource in, ValueTarget out);
public abstract TCast castToVarchar();
public abstract TCast castFromVarchar();
public abstract TClass widestComparable();
public void selfCast(TExecutionContext context,
TInstance sourceInstance, ValueSource source, TInstance targetInstance, ValueTarget target) {
ValueTargets.copyFrom(source, target);
}
public boolean normalizeInstancesBeforeComparison() {
return false;
}
public static boolean comparisonNeedsCasting(TInstance left, TInstance right) {
if (left == null || right == null)
return true;
TClass leftClass = left.typeClass();
TClass rightClass = right.typeClass();
if (leftClass.normalizeInstancesBeforeComparison())
return !left.equalsExcludingNullable(right);
else if (leftClass.getClass() == rightClass.getClass())
return !leftClass.compatibleForCompare(rightClass);
else
return true;
}
public boolean compatibleForCompare(TClass other) {
return (this == other);
}
public static boolean areEqual(ValueSource sourceA, ValueSource sourceB) {
return (compare(sourceA, sourceB) == 0);
}
/**
* Compares two (potentially {@code null}) values with {@link ValueSource#getType()} as their types.
* @return The value {@code 0} if {@code sourceA == sourceB};
* a value less than {@code 0} if {@code sourceA < sourceB}; and
* a value greater than {@code 0} if {@code sourceA > sourceB}
*/
public static int compare(ValueSource sourceA, ValueSource sourceB) {
return compare(sourceA.getType(), sourceA, sourceB.getType(), sourceB);
}
/**
* Compare {@code sourceA} and {@code sourceB} as {@code typeA} and {@code typeB}.
* @return See {@link #compare(ValueSource,ValueSource)}
*/
public static int compare(TInstance typeA, ValueSource sourceA, TInstance typeB, ValueSource sourceB) {
if (comparisonNeedsCasting(typeA, typeB))
throw new IllegalArgumentException("can't compare " + typeA + " and " + typeB);
if (sourceA.isNull())
return sourceB.isNull() ? 0 : -1;
if (sourceB.isNull())
return 1;
return typeA.typeClass().doCompare(typeA, sourceA, typeB, sourceB);
}
/**
* As {@link #compare(TInstance,ValueSource,TInstance,ValueSource)} with
* {@code null} expected to be handled already.
*/
protected int doCompare(TInstance typeA, ValueSource sourceA, TInstance typeB, ValueSource sourceB) {
if (sourceA.hasCacheValue() && sourceB.hasCacheValue()) {
Object objectA = sourceA.getObject();
if (objectA instanceof Comparable<?>) {
// assume objectA and objectB are of the same class. If it's comparable, use that
@SuppressWarnings("unchecked")
Comparable<Object> comparableA = (Comparable<Object>) objectA;
return comparableA.compareTo(sourceB.getObject());
}
}
switch (TInstance.underlyingType(typeA)) {
case BOOL:
return Boolean.compare(sourceA.getBoolean(), sourceB.getBoolean());
case INT_8:
return Integer.compare(sourceA.getInt8(), sourceB.getInt8());
case INT_16:
return Integer.compare(sourceA.getInt16(), sourceB.getInt16());
case UINT_16:
return Integer.compare(sourceA.getUInt16(), sourceB.getUInt16());
case INT_32:
return Integer.compare(sourceA.getInt32(), sourceB.getInt32());
case INT_64:
return Long.compare(sourceA.getInt64(), sourceB.getInt64());
case FLOAT:
return Float.compare(sourceA.getFloat(), sourceB.getFloat());
case DOUBLE:
return Double.compare(sourceA.getDouble(), sourceB.getDouble());
case BYTES:
return UnsignedBytes.lexicographicalComparator().compare(sourceA.getBytes(), sourceB.getBytes());
case STRING:
// Remember: TString overrides and handles collator
return sourceA.getString().compareTo(sourceB.getString());
default:
throw new AssertionError(sourceA.getType());
}
}
final void writeCanonical(ValueSource in, TInstance typeInstance, ValueTarget out) {
if (in.isNull())
out.putNull();
else
getValueIO().copyCanonical(in, typeInstance, out);
}
protected Object attributeToObject(int attributeIndex, int value) {
return value;
}
public final boolean attributeIsPhysical(Attribute attribute) {
return attributeIsPhysical(attribute.ordinal());
}
// Usually physical and not implied by the name.
protected abstract boolean attributeIsPhysical(int attributeIndex);
protected abstract boolean attributeAlwaysDisplayed(int attributeIndex);
public void attributeToString(int attributeIndex, long value, StringBuilder output) {
output.append(value);
}
public boolean hasAttributes(Class<?> enumClass) {
return (this.enumClass == enumClass);
}
protected ValueIO getValueIO() {
return DEFAULT_VALUE_IO;
}
/** must not return null */
public abstract TInstance instance(boolean nullable);
/** must not return null */
public TInstance instance(int arg0, boolean nullable)
{
return createInstance(1, arg0, EMPTY, EMPTY, EMPTY, nullable);
}
/** must not return null */
public TInstance instance(int arg0, int arg1, boolean nullable)
{
return createInstance(2, arg0, arg1, EMPTY, EMPTY, nullable);
}
/** must not return null */
public TInstance instance(int arg0, int arg1, int arg2, boolean nullable)
{
return createInstance(3, arg0, arg1, arg2, EMPTY, nullable);
}
/** must not return null */
public TInstance instance(int arg0, int arg1, int arg2, int arg3, boolean nullable)
{
return createInstance(4, arg0, arg1, arg2, arg3, nullable);
}
final void writeCollating(ValueSource inValue, TInstance inInstance, ValueTarget out) {
if (inValue.isNull())
out.putNull();
else
getValueIO().writeCollating(inValue, inInstance, out);
}
final void readCollating(ValueSource inValue, TInstance inInstance, ValueTarget out) {
if (inValue.isNull())
out.putNull();
else
getValueIO().readCollating(inValue, inInstance, out);
}
public TInstance pickInstance(TInstance left, TInstance right) {
if (left.typeClass() != TClass.this || right.typeClass() != TClass.this)
throw new IllegalArgumentException("can't combine " + left + " and " + right + " using " + this);
return doPickInstance(left, right, left.nullability() || right.nullability());
}
public UnderlyingType underlyingType() {
return underlyingType;
}
int nAttributes() {
return attributeSerializations.size();
}
public Collection<? extends Attribute> attributes() {
return attributeSerializations.keySet();
}
public Map<? extends Attribute, ? extends Serialization> attributeSerializations() {
return attributeSerializations;
}
public TName name() {
return name;
}
public int internalRepresentationVersion() {
return internalRepVersion;
}
public int serializationVersion() {
return serializationVersion;
}
public boolean hasFixedSerializationSize() {
return serializationSize >= 0;
}
public int fixedSerializationSize() {
assert hasFixedSerializationSize() : this + " has no fixed serialization size";
return serializationSize;
}
public boolean hasFixedSerializationSize(TInstance type) {
return hasFixedSerializationSize();
}
public int fixedSerializationSize(TInstance type) {
return fixedSerializationSize();
}
public int variableSerializationSize(TInstance type, boolean average) {
if (hasFixedSerializationSize(type))
return fixedSerializationSize(type);
else
throw new UnsupportedOperationException("need to implement variableSerializationSize for " + type);
}
public boolean isUnsigned() {
return false;
}
// object interface
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TClass other = (TClass) o;
return name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name.toString();
}
void format(TInstance type, ValueSource source, AkibanAppender out) {
if (source.isNull())
out.append("NULL");
else
formatter.format(type, source, out);
}
void formatAsLiteral(TInstance type, ValueSource source, AkibanAppender out) {
if (source.isNull())
out.append("NULL");
else
formatter.formatAsLiteral(type, source, out);
}
void formatAsJson(TInstance type, ValueSource source, AkibanAppender out, FormatOptions options) {
if (source.isNull())
out.append("null");
else
formatter.formatAsJson(type, source, out, options);
}
public Object formatCachedForNiceRow(ValueSource source) {
return source.getObject();
}
// for use by subclasses
protected abstract TInstance doPickInstance(TInstance left, TInstance right, boolean suggestedNullability);
protected abstract void validate(TInstance type);
// for use by this class
protected TInstance createInstanceNoArgs(boolean nullable) {
return createInstance(0, EMPTY, EMPTY, EMPTY, EMPTY, nullable);
}
protected TInstance createInstance(int nAttrs, int attr0, int attr1, int attr2, int attr3, boolean nullable) {
return TInstance.create(this, enumClass, nAttrs, attr0, attr1, attr2, attr3, nullable);
}
public ValueCacher cacher() {
return null;
}
// state
protected <A extends Enum<A> & Attribute> TClass(TName name,
Class<A> enumClass,
TClassFormatter formatter,
int internalRepVersion, int serializationVersion, int serializationSize,
UnderlyingType underlyingType)
{
ArgumentValidation.notNull("name", name);
this.name = name;
this.formatter = formatter;
this.internalRepVersion = internalRepVersion;
this.serializationVersion = serializationVersion;
this.serializationSize = serializationSize < 0 ? -1 : serializationSize; // normalize all negative numbers
this.underlyingType = underlyingType;
this.enumClass = enumClass;
this.attributeSerializations = serializationsFor(enumClass);
for (Attribute attribute : attributeSerializations.keySet())
{
String attrValue = attribute.name();
if (!VALID_ATTRIBUTE_PATTERN.matcher(attrValue).matches())
throw new IllegalNameException(attribute + " in " + name + " has invalid name: " + attrValue);
}
}
private static <A extends Enum<A> & Attribute> Map<A, Serialization> serializationsFor(Class<A> enumClass) {
EnumSet<Serialization> seenSerializations = EnumSet.noneOf(Serialization.class);
Map<A, Serialization> serializationsMap = new EnumMap<>(enumClass);
for (A attribute : enumClass.getEnumConstants()) {
Field attributeField;
try {
attributeField = enumClass.getField(attribute.name());
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
SerializeAs serializeAs = attributeField.getAnnotation(SerializeAs.class);
Serialization serialization;
if (serializeAs != null) {
serialization = serializeAs.value();
if (!seenSerializations.add(serialization))
throw new RuntimeException("duplicate serialization policy in " + enumClass);
}
else {
serialization = null;
}
serializationsMap.put(attribute, serialization);
}
return Collections.unmodifiableMap(serializationsMap);
}
protected <A extends Enum<A> & Attribute> TClass(TBundleID bundle,
String name,
Enum<?> category,
Class<A> enumClass,
TClassFormatter formatter,
int internalRepVersion, int serializationVersion, int serializationSize,
UnderlyingType underlyingType)
{
this(new TName(bundle, name, category),
enumClass,
formatter,
internalRepVersion, serializationVersion, serializationSize,
underlyingType);
}
private final TName name;
private final Class<?> enumClass;
protected final TClassFormatter formatter;
private final Map<? extends Attribute, Serialization> attributeSerializations;
private final int internalRepVersion;
private final int serializationVersion;
private final int serializationSize;
private final UnderlyingType underlyingType;
private static final Pattern VALID_ATTRIBUTE_PATTERN = Pattern.compile("[a-zA-Z]\\w*");
private static final int EMPTY = -1;
private static final ValueIO DEFAULT_VALUE_IO = new SimpleValueIO() {
@Override
protected void copy(ValueSource in, TInstance typeInstance, ValueTarget out) {
ValueTargets.copyFrom(in, out);
}
};
}