001/*
002 * Copyright (C) 2012 Facebook, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may
005 * not use this file except in compliance with the License. You may obtain
006 * a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations
014 * under the License.
015 */
016package com.facebook.swift.codec.metadata;
017
018import com.facebook.swift.codec.ThriftProtocolType;
019import com.google.common.base.Preconditions;
020import com.google.common.reflect.TypeParameter;
021import com.google.common.reflect.TypeToken;
022
023import javax.annotation.concurrent.Immutable;
024
025import java.lang.reflect.Type;
026import java.nio.ByteBuffer;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import static com.facebook.swift.codec.ThriftProtocolType.ENUM;
032import static com.facebook.swift.codec.ThriftProtocolType.STRUCT;
033import static com.google.common.base.Preconditions.checkNotNull;
034import static com.google.common.base.Preconditions.checkState;
035
036/**
037 * ThriftType contains all metadata necessary for converting the java type to and from Thrift.
038 */
039@Immutable
040public class ThriftType
041{
042    public static final ThriftType BOOL = new ThriftType(ThriftProtocolType.BOOL, boolean.class);
043    public static final ThriftType BYTE = new ThriftType(ThriftProtocolType.BYTE, byte.class);
044    public static final ThriftType DOUBLE = new ThriftType(ThriftProtocolType.DOUBLE, double.class);
045    public static final ThriftType I16 = new ThriftType(ThriftProtocolType.I16, short.class);
046    public static final ThriftType I32 = new ThriftType(ThriftProtocolType.I32, int.class);
047    public static final ThriftType I64 = new ThriftType(ThriftProtocolType.I64, long.class);
048    public static final ThriftType STRING = new ThriftType(ThriftProtocolType.STRING, String.class);
049    public static final ThriftType BINARY = new ThriftType(ThriftProtocolType.BINARY, ByteBuffer.class);
050    public static final ThriftType VOID = new ThriftType(ThriftProtocolType.STRUCT, void.class);
051
052    public static ThriftType struct(ThriftStructMetadata structMetadata)
053    {
054        return new ThriftType(structMetadata);
055    }
056
057    public static <K, V> ThriftType map(ThriftType keyType, ThriftType valueType)
058    {
059        checkNotNull(keyType, "keyType is null");
060        checkNotNull(valueType, "valueType is null");
061
062        @SuppressWarnings("serial")
063        Type javaType = new TypeToken<Map<K, V>>(){}
064                .where(new TypeParameter<K>(){}, (TypeToken<K>) TypeToken.of(keyType.getJavaType()))
065                .where(new TypeParameter<V>(){}, (TypeToken<V>) TypeToken.of(valueType.getJavaType()))
066                .getType();
067        return new ThriftType(ThriftProtocolType.MAP, javaType, keyType, valueType);
068    }
069
070    public static <E> ThriftType set(ThriftType valueType)
071    {
072        Preconditions.checkNotNull(valueType, "valueType is null");
073
074        @SuppressWarnings("serial")
075        Type javaType = new TypeToken<Set<E>>(){}
076                .where(new TypeParameter<E>(){}, (TypeToken<E>) TypeToken.of(valueType.getJavaType()))
077                .getType();
078        return new ThriftType(ThriftProtocolType.SET, javaType, null, valueType);
079    }
080
081    public static <E> ThriftType list(ThriftType valueType)
082    {
083        checkNotNull(valueType, "valueType is null");
084
085        @SuppressWarnings("serial")
086        Type javaType = new TypeToken<List<E>>(){}
087                .where(new TypeParameter<E>(){}, (TypeToken<E>) TypeToken.of(valueType.getJavaType()))
088                .getType();
089        return new ThriftType(ThriftProtocolType.LIST, javaType, null, valueType);
090    }
091
092    public static ThriftType array(ThriftType valueType)
093    {
094        checkNotNull(valueType, "valueType is null");
095        Class<?> javaType = ReflectionHelper.getArrayOfType(valueType.getJavaType());
096        return new ThriftType(ThriftProtocolType.LIST, javaType, null, valueType);
097    }
098
099    public static ThriftType enumType(ThriftEnumMetadata<?> enumMetadata)
100    {
101        checkNotNull(enumMetadata, "enumMetadata is null");
102        return new ThriftType(enumMetadata);
103    }
104
105    private final ThriftProtocolType protocolType;
106    private final Type javaType;
107    private final ThriftType keyType;
108    private final ThriftType valueType;
109    private final ThriftStructMetadata structMetadata;
110    private final ThriftEnumMetadata<?> enumMetadata;
111    private final ThriftType uncoercedType;
112
113    private ThriftType(ThriftProtocolType protocolType, Type javaType)
114    {
115        Preconditions.checkNotNull(protocolType, "protocolType is null");
116        Preconditions.checkNotNull(javaType, "javaType is null");
117
118        this.protocolType = protocolType;
119        this.javaType = javaType;
120        keyType = null;
121        valueType = null;
122        structMetadata = null;
123        enumMetadata = null;
124        uncoercedType = null;
125    }
126
127    private ThriftType(ThriftProtocolType protocolType, Type javaType, ThriftType keyType, ThriftType valueType)
128    {
129        Preconditions.checkNotNull(protocolType, "protocolType is null");
130        Preconditions.checkNotNull(javaType, "javaType is null");
131        Preconditions.checkNotNull(valueType, "valueType is null");
132
133        this.protocolType = protocolType;
134        this.javaType = javaType;
135        this.keyType = keyType;
136        this.valueType = valueType;
137        this.structMetadata = null;
138        this.enumMetadata = null;
139        this.uncoercedType = null;
140    }
141
142    private ThriftType(ThriftStructMetadata structMetadata)
143    {
144        Preconditions.checkNotNull(structMetadata, "structMetadata is null");
145
146        this.protocolType = STRUCT;
147        this.javaType = structMetadata.getStructClass();
148        keyType = null;
149        valueType = null;
150        this.structMetadata = structMetadata;
151        this.enumMetadata = null;
152        this.uncoercedType = null;
153    }
154
155    private ThriftType(ThriftEnumMetadata<?> enumMetadata)
156    {
157        Preconditions.checkNotNull(enumMetadata, "enumMetadata is null");
158
159        this.protocolType = ENUM;
160        this.javaType = enumMetadata.getEnumClass();
161        keyType = null;
162        valueType = null;
163        this.structMetadata = null;
164        this.enumMetadata = enumMetadata;
165        this.uncoercedType = null;
166    }
167
168    public ThriftType(ThriftType uncoercedType, Type javaType)
169    {
170        this.javaType = javaType;
171        this.uncoercedType = uncoercedType;
172
173        this.protocolType = uncoercedType.getProtocolType();
174        keyType = null;
175        valueType = null;
176        structMetadata = null;
177        enumMetadata = null;
178    }
179
180    public Type getJavaType()
181    {
182        return javaType;
183    }
184
185    public ThriftProtocolType getProtocolType()
186    {
187        return protocolType;
188    }
189
190    public ThriftType getKeyType()
191    {
192        checkState(keyType != null, "%s does not have a key", protocolType);
193        return keyType;
194    }
195
196    public ThriftType getValueType()
197    {
198        checkState(valueType != null, "%s does not have a value", protocolType);
199        return valueType;
200    }
201
202    public ThriftStructMetadata getStructMetadata()
203    {
204        checkState(structMetadata != null, "%s does not have struct metadata", protocolType);
205        return structMetadata;
206    }
207
208    public ThriftEnumMetadata<?> getEnumMetadata()
209    {
210        checkState(enumMetadata != null, "%s does not have enum metadata", protocolType);
211        return enumMetadata;
212    }
213
214    public boolean isCoerced()
215    {
216        return uncoercedType != null;
217    }
218
219    public ThriftType coerceTo(Type javaType)
220    {
221        if (javaType == this.javaType) {
222            return this;
223        }
224
225        Preconditions.checkState(
226                protocolType != ThriftProtocolType.STRUCT &&
227                protocolType != ThriftProtocolType.SET &&
228                protocolType != ThriftProtocolType.LIST &&
229                protocolType != ThriftProtocolType.MAP,
230                "Coercion is not supported for %s", protocolType
231        );
232        return new ThriftType(this, javaType);
233    }
234
235    public ThriftType getUncoercedType()
236    {
237        return uncoercedType;
238    }
239
240    @Override
241    public boolean equals(Object o)
242    {
243        if (this == o) {
244            return true;
245        }
246        if (o == null || getClass() != o.getClass()) {
247            return false;
248        }
249
250        final ThriftType that = (ThriftType) o;
251
252        if (javaType != null ? !javaType.equals(that.javaType) : that.javaType != null) {
253            return false;
254        }
255        if (protocolType != that.protocolType) {
256            return false;
257        }
258
259        return true;
260    }
261
262    @Override
263    public int hashCode()
264    {
265        int result = protocolType != null ? protocolType.hashCode() : 0;
266        result = 31 * result + (javaType != null ? javaType.hashCode() : 0);
267        return result;
268    }
269
270    @Override
271    public String toString()
272    {
273        final StringBuilder sb = new StringBuilder();
274        sb.append("ThriftType");
275        sb.append("{");
276        sb.append(protocolType).append(" ").append(javaType);
277        if (structMetadata != null) {
278            sb.append(" ").append(structMetadata.getStructClass().getName());
279        }
280        else if (keyType != null) {
281            sb.append(" keyType=").append(keyType);
282            sb.append(", valueType=").append(valueType);
283        }
284        else if (valueType != null) {
285            sb.append(" valueType=").append(valueType);
286        }
287        sb.append('}');
288        return sb.toString();
289    }
290}