ThriftType.java

/*
 * Copyright (C) 2012 Facebook, 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.facebook.swift.codec.metadata;

import com.facebook.swift.codec.ThriftProtocolType;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;

import javax.annotation.concurrent.Immutable;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.facebook.swift.codec.ThriftProtocolType.ENUM;
import static com.facebook.swift.codec.ThriftProtocolType.STRUCT;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * ThriftType contains all metadata necessary for converting the java type to and from Thrift.
 */
@Immutable
public class ThriftType
{
    public static final ThriftType BOOL = new ThriftType(ThriftProtocolType.BOOL, boolean.class);
    public static final ThriftType BYTE = new ThriftType(ThriftProtocolType.BYTE, byte.class);
    public static final ThriftType DOUBLE = new ThriftType(ThriftProtocolType.DOUBLE, double.class);
    public static final ThriftType I16 = new ThriftType(ThriftProtocolType.I16, short.class);
    public static final ThriftType I32 = new ThriftType(ThriftProtocolType.I32, int.class);
    public static final ThriftType I64 = new ThriftType(ThriftProtocolType.I64, long.class);
    public static final ThriftType STRING = new ThriftType(ThriftProtocolType.STRING, ByteBuffer.class);
    public static final ThriftType VOID = new ThriftType(ThriftProtocolType.STRUCT, void.class);

    public static ThriftType struct(ThriftStructMetadata<?> structMetadata)
    {
        return new ThriftType(structMetadata);
    }

    public static <K, V> ThriftType map(ThriftType keyType, ThriftType valueType)
    {
        checkNotNull(keyType, "keyType is null");
        checkNotNull(valueType, "valueType is null");

        @SuppressWarnings("serial")
        Type javaType = new TypeToken<Map<K, V>>(){}
                .where(new TypeParameter<K>(){}, (TypeToken<K>) TypeToken.of(keyType.getJavaType()))
                .where(new TypeParameter<V>(){}, (TypeToken<V>) TypeToken.of(valueType.getJavaType()))
                .getType();
        return new ThriftType(ThriftProtocolType.MAP, javaType, keyType, valueType);
    }

    public static <E> ThriftType set(ThriftType valueType)
    {
        Preconditions.checkNotNull(valueType, "valueType is null");

        @SuppressWarnings("serial")
        Type javaType = new TypeToken<Set<E>>(){}
                .where(new TypeParameter<E>(){}, (TypeToken<E>) TypeToken.of(valueType.getJavaType()))
                .getType();
        return new ThriftType(ThriftProtocolType.SET, javaType, null, valueType);
    }

    public static <E> ThriftType list(ThriftType valueType)
    {
        checkNotNull(valueType, "valueType is null");

        @SuppressWarnings("serial")
        Type javaType = new TypeToken<List<E>>(){}
                .where(new TypeParameter<E>(){}, (TypeToken<E>) TypeToken.of(valueType.getJavaType()))
                .getType();
        return new ThriftType(ThriftProtocolType.LIST, javaType, null, valueType);
    }

    public static ThriftType enumType(ThriftEnumMetadata<?> enumMetadata)
    {
        checkNotNull(enumMetadata, "enumMetadata is null");
        return new ThriftType(enumMetadata);
    }

    private final ThriftProtocolType protocolType;
    private final Type javaType;
    private final ThriftType keyType;
    private final ThriftType valueType;
    private final ThriftStructMetadata<?> structMetadata;
    private final ThriftEnumMetadata<?> enumMetadata;
    private final ThriftType uncoercedType;

    private ThriftType(ThriftProtocolType protocolType, Type javaType)
    {
        Preconditions.checkNotNull(protocolType, "protocolType is null");
        Preconditions.checkNotNull(javaType, "javaType is null");

        this.protocolType = protocolType;
        this.javaType = javaType;
        keyType = null;
        valueType = null;
        structMetadata = null;
        enumMetadata = null;
        uncoercedType = null;
    }

    private ThriftType(ThriftProtocolType protocolType, Type javaType, ThriftType keyType, ThriftType valueType)
    {
        Preconditions.checkNotNull(protocolType, "protocolType is null");
        Preconditions.checkNotNull(javaType, "javaType is null");
        Preconditions.checkNotNull(valueType, "valueType is null");

        this.protocolType = protocolType;
        this.javaType = javaType;
        this.keyType = keyType;
        this.valueType = valueType;
        this.structMetadata = null;
        this.enumMetadata = null;
        this.uncoercedType = null;
    }

    private ThriftType(ThriftStructMetadata<?> structMetadata)
    {
        Preconditions.checkNotNull(structMetadata, "structMetadata is null");

        this.protocolType = STRUCT;
        this.javaType = structMetadata.getStructClass();
        keyType = null;
        valueType = null;
        this.structMetadata = structMetadata;
        this.enumMetadata = null;
        this.uncoercedType = null;
    }

    private ThriftType(ThriftEnumMetadata<?> enumMetadata)
    {
        Preconditions.checkNotNull(enumMetadata, "enumMetadata is null");

        this.protocolType = ENUM;
        this.javaType = enumMetadata.getEnumClass();
        keyType = null;
        valueType = null;
        this.structMetadata = null;
        this.enumMetadata = enumMetadata;
        this.uncoercedType = null;
    }

    public ThriftType(ThriftType uncoercedType, Type javaType)
    {
        this.javaType = javaType;
        this.uncoercedType = uncoercedType;

        this.protocolType = uncoercedType.getProtocolType();
        keyType = null;
        valueType = null;
        structMetadata = null;
        enumMetadata = null;
    }

    public Type getJavaType()
    {
        return javaType;
    }

    public ThriftProtocolType getProtocolType()
    {
        return protocolType;
    }

    public ThriftType getKeyType()
    {
        checkState(keyType != null, "%s does not have a key", protocolType);
        return keyType;
    }

    public ThriftType getValueType()
    {
        checkState(valueType != null, "%s does not have a value", protocolType);
        return valueType;
    }

    public ThriftStructMetadata<?> getStructMetadata()
    {
        checkState(structMetadata != null, "%s does not have struct metadata", protocolType);
        return structMetadata;
    }

    public ThriftEnumMetadata<?> getEnumMetadata()
    {
        checkState(enumMetadata != null, "%s does not have enum metadata", protocolType);
        return enumMetadata;
    }

    public boolean isCoerced()
    {
        return uncoercedType != null;
    }

    public ThriftType coerceTo(Type javaType)
    {
        if (javaType == this.javaType) {
            return this;
        }

        Preconditions.checkState(
                protocolType != ThriftProtocolType.STRUCT &&
                protocolType != ThriftProtocolType.SET &&
                protocolType != ThriftProtocolType.LIST &&
                protocolType != ThriftProtocolType.MAP,
                "Coercion is not supported for %s", protocolType
        );
        return new ThriftType(this, javaType);
    }

    public ThriftType getUncoercedType()
    {
        return uncoercedType;
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        final ThriftType that = (ThriftType) o;

        if (javaType != null ? !javaType.equals(that.javaType) : that.javaType != null) {
            return false;
        }
        if (protocolType != that.protocolType) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode()
    {
        int result = protocolType != null ? protocolType.hashCode() : 0;
        result = 31 * result + (javaType != null ? javaType.hashCode() : 0);
        return result;
    }

    @Override
    public String toString()
    {
        final StringBuilder sb = new StringBuilder();
        sb.append("ThriftType");
        sb.append("{");
        sb.append(protocolType).append(" ").append(javaType);
        if (structMetadata != null) {
            sb.append(" ").append(structMetadata.getStructClass().getName());
        }
        else if (keyType != null) {
            sb.append(" keyType=").append(keyType);
            sb.append(", valueType=").append(valueType);
        }
        else if (valueType != null) {
            sb.append(" valueType=").append(valueType);
        }
        sb.append('}');
        return sb.toString();
    }
}