/**
* 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.error.AkibanInternalException;
import com.foundationdb.server.types.value.UnderlyingType;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.server.types.value.ValueTarget;
import com.foundationdb.server.types.texpressions.TPreparedExpression;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.util.AkibanAppender;
import java.util.ArrayList;
import java.util.List;
/**
* A type including nullability and various other attributes (such as collation).
*
* Note:
* A null TInstance can represent one of 4 things:
* * A literal NULL, e.g. (SELECT NULL)
* * A Parameter, e.g. (SELECT ?)
* * An unkown return value, presumably due to one of the two above
* * Temporarily unset type until it has been figured out.
*/
public final class TInstance {
// static helpers
public static TClass tClass(TInstance type) {
return type == null ? null : type.typeClass();
}
public static UnderlyingType underlyingType(TInstance type) {
TClass tClass = tClass(type);
return tClass == null ? null : tClass.underlyingType();
}
// TInstance interface
public void writeCanonical(ValueSource in, ValueTarget out) {
tclass.writeCanonical(in, this, out);
}
public void writeCollating(ValueSource in, ValueTarget out) {
tclass.writeCollating(in, this, out);
}
public void readCollating(Value in, Value out) {
tclass.readCollating(in, this, out);
}
public void format(ValueSource source, AkibanAppender out) {
tclass.format(this, source, out);
}
public void formatAsLiteral(ValueSource source, AkibanAppender out) {
tclass.formatAsLiteral(this, source, out);
}
public void formatAsJson(ValueSource source, AkibanAppender out, FormatOptions options) {
tclass.formatAsJson(this, source, out, options);
}
public Object attributeToObject(Attribute attribute) {
if (enumClass != attribute.getClass())
throw new IllegalArgumentException("Illegal attribute: " + attribute.name());
int value;
int attributeIndex = attribute.ordinal();
switch (attributeIndex) {
case 0: value = attr0; break;
case 1: value = attr1; break;
case 2: value = attr2; break;
case 3: value = attr3; break;
default: throw new AssertionError("index out of range for " + tclass + ": " + attribute);
}
return tclass.attributeToObject(attributeIndex, value);
}
public int attribute(Attribute attribute) {
if (enumClass != attribute.getClass())
throw new IllegalArgumentException("Illegal attribute: " + attribute.name());
int index = attribute.ordinal();
switch (index) {
case 0: return attr0;
case 1: return attr1;
case 2: return attr2;
case 3: return attr3;
default: throw new IllegalArgumentException("index out of range for " + tclass + ": " + index);
}
}
public boolean hasAttributes(Class<?> enumClass) {
return (this.enumClass == enumClass);
}
public TClass typeClass() {
return tclass;
}
public boolean nullability() {
return isNullable;
}
public Object getMetaData() {
return metaData;
}
public TInstance withNullable(boolean isNullable) {
return (isNullable == this.isNullable)
? this
: new TInstance(tclass, enumClass, tclass.nAttributes(), attr0, attr1, attr2, attr3, isNullable);
}
/**
* Convenience method for <tt>typeClass().dataTypeDescriptor(this)</tt>.
* @return this instance's DataTypeDescriptor
* @see TClass#dataTypeDescriptor(TInstance)
*/
public DataTypeDescriptor dataTypeDescriptor() {
return tclass.dataTypeDescriptor(this);
}
/**
* @param o additional meta data for this TInstance
* @return
* <code>false</code> if this method has already been called on this object.
* The new meta data will <e>not</e> override the current one.
* <code>true</code> if this object's meta data is still <code>null</code>.
*/
public boolean setMetaData (Object o) {
if (metaData != null)
return false;
metaData = o;
return true;
}
public String toStringIgnoringNullability(boolean useShorthand) {
String className = useShorthand ? tclass.name().unqualifiedName() : tclass.name().toString();
int nattrs = tclass.nAttributes();
if (nattrs == 0)
return className;
// assume 5 digits per attribute as a wild guess. If it's wrong, no biggie. 2 chars for open/close paren
int capacity = className.length() + 2 + (5*nattrs);
StringBuilder sb = new StringBuilder(capacity);
sb.append(className).append('(');
long[] attrs = new long[] { attr0, attr1, attr2, attr3 };
for (int i = 0; i < nattrs; ++i) {
tclass.attributeToString(i, attrs[i], sb);
if (i+1 < nattrs)
sb.append(", ");
}
sb.append(')');
return sb.toString();
}
public String toStringConcise(boolean lowerCase) {
String name = tclass.name().unqualifiedName();
if (lowerCase)
name = name.toLowerCase();
StringBuilder sb = new StringBuilder();
sb.append(name);
int nattrs = tclass.nAttributes();
boolean first = true;
long[] attrs = new long[] { attr0, attr1, attr2, attr3 };
for (int i = 0; i < nattrs; ++i) {
if (tclass.attributeAlwaysDisplayed(i)) {
if (first) {
sb.append("(");
first = false;
}
else {
sb.append(", ");
}
tclass.attributeToString(i, attrs[i], sb);
}
}
if (!first) {
sb.append(")");
}
return sb.toString();
}
// object interface
@Override
public String toString() {
String result = toStringIgnoringNullability(false);
if (tclass.nAttributes() != 0) // TODO there's no reason to do this except that it's backwards-compatible
result += (isNullable ? " NULL" : " NOT NULL"); // with existing tests.
return result;
}
@Override
public boolean equals(Object o) {
return equalsIncludingNullable(o);
}
public boolean equalsIncludingNullable(Object o) {
return equals(o, true);
}
public boolean equalsExcludingNullable(Object o) {
return equals(o, false);
}
private boolean equals(Object o, boolean withNullable) {
if (this == o) return true;
if (!(o instanceof TInstance)) return false;
TInstance other = (TInstance) o;
return attr0 == other.attr0
&& attr1 == other.attr1
&& attr2 == other.attr2
&& attr3 == other.attr3
&& ((!withNullable) || (isNullable == other.isNullable))
&& tclass.equals(other.tclass);
}
@Override
public int hashCode() {
int result = tclass.hashCode();
result = 31 * result + attr0;
result = 31 * result + attr2;
result = 31 * result + attr1;
result = 31 * result + attr3;
result = 31 * result + (isNullable ? 0 : 1);
return result;
}
// package-private
static TInstance create(TClass tclass, Class<?> enumClass, int nAttrs, int attr0, int attr1, int attr2, int attr3,
boolean isNullable)
{
TInstance result = new TInstance(tclass, enumClass, nAttrs, attr0, attr1, attr2, attr3, isNullable);
tclass.validate(result);
return result;
}
static TInstance create(TInstance template, int attr0, int attr1, int attr2, int attr3, boolean nullable) {
return create(template.tclass, template.enumClass, template.tclass.nAttributes(),
attr0, attr1, attr2, attr3, nullable);
}
Class<?> enumClass() {
return enumClass;
}
int attrByPos(int i) {
switch (i) {
case 0: return attr0;
case 1: return attr1;
case 2: return attr2;
case 3: return attr3;
default: throw new AssertionError("out of range: " + i);
}
}
// state
private TInstance(TClass tclass, Class<?> enumClass, int nAttrs, int attr0, int attr1, int attr2, int attr3,
boolean isNullable)
{
if (tclass.nAttributes() != nAttrs) {
throw new AkibanInternalException(tclass.name() + " requires "+ tclass.nAttributes()
+ " attributes, saw " + nAttrs);
}
// normalize inputs past nattrs
switch (nAttrs) {
case 0:
attr0 = -1;
case 1:
attr1 = -1;
case 2:
attr2 = -1;
case 3:
attr3 = -1;
case 4:
break;
default:
throw new IllegalArgumentException("too many nattrs: " + nAttrs + " (" + enumClass.getSimpleName() + ')');
}
this.tclass = tclass;
this.attr0 = attr0;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
this.enumClass = enumClass;
this.isNullable = isNullable;
}
private final TClass tclass;
private final int attr0, attr1, attr2, attr3;
private final boolean isNullable;
private Object metaData;
private final Class<?> enumClass;
}