001package net.gdface.thrift;
002
003import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_FIELD;
004import static com.google.common.base.Preconditions.*;
005import static java.lang.String.format;
006
007import java.lang.reflect.Constructor;
008import java.lang.reflect.Field;
009import java.lang.reflect.GenericArrayType;
010import java.lang.reflect.InvocationTargetException;
011import java.lang.reflect.Method;
012import java.lang.reflect.ParameterizedType;
013import java.lang.reflect.Type;
014import java.lang.reflect.TypeVariable;
015import java.lang.reflect.WildcardType;
016import java.net.URI;
017import java.net.URL;
018import java.nio.ByteBuffer;
019import java.util.Collection;
020import java.util.Date;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ExecutionException;
026import java.util.concurrent.Executor;
027
028import org.apache.thrift.TApplicationException;
029import org.apache.thrift.protocol.TProtocolException;
030
031import com.facebook.swift.codec.ThriftField.Requiredness;
032import com.facebook.swift.codec.ThriftStruct;
033import com.facebook.swift.codec.metadata.ThriftCatalog;
034import com.facebook.swift.codec.metadata.ThriftCatalogWithTransformer;
035import com.facebook.swift.codec.metadata.ThriftConstructorInjection;
036import com.facebook.swift.codec.metadata.ThriftExtraction;
037import com.facebook.swift.codec.metadata.ThriftFieldExtractor;
038import com.facebook.swift.codec.metadata.ThriftFieldInjection;
039import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
040import com.facebook.swift.codec.metadata.ThriftInjection;
041import com.facebook.swift.codec.metadata.ThriftMethodExtractor;
042import com.facebook.swift.codec.metadata.ThriftMethodInjection;
043import com.facebook.swift.codec.metadata.ThriftParameterInjection;
044import com.facebook.swift.codec.metadata.ThriftStructMetadata;
045import com.facebook.swift.service.RuntimeTApplicationException;
046import com.google.common.base.Throwables;
047import com.google.common.collect.ImmutableMap;
048import com.google.common.collect.ImmutableSet;
049import com.google.common.reflect.TypeParameter;
050import com.google.common.reflect.TypeToken;
051import com.google.common.util.concurrent.FutureCallback;
052import com.google.common.util.concurrent.Futures;
053import com.google.common.util.concurrent.ListenableFuture;
054
055/**
056 * thrift工具
057 * @author guyadong
058 *
059 */
060public class ThriftUtils {
061        public static final ThriftCatalog CATALOG = new ThriftCatalogWithTransformer();
062        public static final Set<Class<?>> THRIFT_BUILTIN_KNOWNTYPES = 
063        ImmutableSet.of(
064                        boolean.class,
065                        byte.class,
066                        double.class,
067                        short.class,
068                        int.class,
069                        long.class,
070                        String.class,
071                        ByteBuffer.class,
072                        void.class,
073                        Boolean.class,
074                        Byte.class,
075                        Short.class,
076                        Integer.class,
077                        Long.class,
078                        Double.class);
079        public static final Map<Class<?>,Class<?>> CAST_TYPES = 
080                ImmutableMap.<Class<?>,Class<?>>builder()
081                        .put(byte[].class,ByteBuffer.class)
082                        .put(Date.class,Long.class)
083                        .put(java.sql.Date.class,Long.class)
084                        .put(java.sql.Time.class,Long.class)
085                        .put(float.class,double.class)
086                        .put(Float.class,Double.class)
087                        .put(URI.class,String.class)
088                        .put(URL.class,String.class)
089                        .build();
090        public static final String DECORATOR_PKG_SUFFIX="decorator";
091        public static final String CLIENT_SUFFIX="client";
092        public static final String DECORATOR_CLIENT_PKG_SUFFIX= DECORATOR_PKG_SUFFIX + "." + CLIENT_SUFFIX;
093        public ThriftUtils() {
094        }
095
096        /**
097         * 构造{@code metadata}指定类型的实例并填充字段
098         * 参见 {@link com.facebook.swift.codec.internal.reflection.ReflectionThriftStructCodec#constructStruct(Map<Short, Object>)}
099         * @param data
100         * @param metadata
101         * @return
102         * @throws Exception
103         */
104        @SuppressWarnings("unchecked")
105        public static <T>T constructStruct(Map<Short, TypeValue> data,ThriftStructMetadata metadata) 
106                throws Exception{
107                T instance;
108            {
109                ThriftConstructorInjection constructor = metadata.getConstructorInjection().get();
110                Type[] dstTypes = constructor.getConstructor().getGenericParameterTypes();
111                Type[] srcTypes = new Type[constructor.getParameters().size()];
112                Object[] parametersValues = new Object[constructor.getParameters().size()];
113                checkState(dstTypes.length == parametersValues.length);
114                for (ThriftParameterInjection parameter : constructor.getParameters()) {
115                        TypeValue value = data.get(parameter.getId());
116                    parametersValues[parameter.getParameterIndex()] = value.value;
117                    srcTypes[parameter.getParameterIndex()] = value.type;
118                }
119                for(int i =0;i<dstTypes.length;++i){
120                        parametersValues[i] = TypeTransformer.getInstance().cast(parametersValues[i], srcTypes[i], dstTypes[i]);
121                }
122                try {
123                    instance = (T) constructor.getConstructor().newInstance(parametersValues);
124                } catch (InvocationTargetException e) {
125                    if (e.getTargetException() != null) {
126                        Throwables.throwIfUnchecked(e.getTargetException());
127                        throw new RuntimeException(e.getTargetException());
128                    }
129                    throw e;
130                } 
131            }
132                return fillStructField(data,metadata,instance);
133        }
134
135        /**
136         * 填充{@code instance}实例的字段<br>
137         * 参见 {@link com.facebook.swift.codec.internal.reflection.ReflectionThriftStructCodec#constructStruct(Map<Short, Object>)}
138         * @param data
139         * @param metadata
140         * @param instance
141         * @return
142         * @throws Exception
143         */
144        @SuppressWarnings("unchecked")
145        public static <T>T fillStructField(Map<Short, TypeValue> data,ThriftStructMetadata metadata,T instance) 
146                throws Exception{
147                checkArgument(null != instance,"instance is null");
148                // inject fields
149            for (ThriftFieldMetadata fieldMetadata : metadata.getFields(THRIFT_FIELD)) {
150                for (ThriftInjection injection : fieldMetadata.getInjections()) {
151                    if (injection instanceof ThriftFieldInjection) {
152                        ThriftFieldInjection fieldInjection = (ThriftFieldInjection) injection;
153                        TypeValue value = data.get(fieldInjection.getId());
154                        if (value != null) {
155                                Field f = fieldInjection.getField();
156                            f.set(instance, TypeTransformer.getInstance().cast(value.value,value.type,f.getGenericType()));
157                        }
158                    }
159                }
160            }
161        
162            // inject methods
163            for (ThriftMethodInjection methodInjection : metadata.getMethodInjections()) {
164                boolean shouldInvoke = false;
165                Object[] parametersValues = new Object[methodInjection.getParameters().size()];
166                Type[] srcTypes = new Type[methodInjection.getParameters().size()];
167                for (ThriftParameterInjection parameter : methodInjection.getParameters()) {
168                        TypeValue value = data.get(parameter.getId());
169                    if (value != null) {
170                        parametersValues[parameter.getParameterIndex()] = value.value;
171                        srcTypes[parameter.getParameterIndex()] = value.type;
172                        shouldInvoke = true;
173                    }
174                }
175        
176                if (shouldInvoke) {
177                    try {                       
178                        Method method = methodInjection.getMethod();
179                        Type[] parameterTypes = method.getGenericParameterTypes();
180                        for(int i = 0 ;i<parametersValues.length;++i){
181                                parametersValues[i]=TypeTransformer.getInstance().cast(parametersValues[i], srcTypes[i], parameterTypes[i]);
182                        }
183                        method.invoke(instance, parametersValues);
184                    }
185                    catch (InvocationTargetException e) {
186                        if (e.getTargetException() != null) {
187                                Throwables.throwIfUnchecked(e.getTargetException());
188                                throw new RuntimeException(e.getTargetException());
189                        }
190                        throw e;
191                    }
192                }
193            }
194        
195            // builder method
196            if (metadata.getBuilderMethod().isPresent()) {
197                ThriftMethodInjection builderMethod = metadata.getBuilderMethod().get();
198                Object[] parametersValues = new Object[builderMethod.getParameters().size()];
199                for (ThriftParameterInjection parameter : builderMethod.getParameters()) {
200                    Object value = data.get(parameter.getId());
201                    parametersValues[parameter.getParameterIndex()] = value;
202                }
203        
204                try {
205                    instance = (T) builderMethod.getMethod().invoke(instance, parametersValues);
206                    if (instance == null) {
207                        throw new IllegalArgumentException("Builder method returned a null instance");
208        
209                    }
210                    if (!metadata.getStructClass().isInstance(instance)) {
211                        throw new IllegalArgumentException(format("Builder method returned instance of type %s, but an instance of %s is required",
212                                instance.getClass().getName(),
213                                metadata.getStructClass().getName()));
214                    }
215                }
216                catch (InvocationTargetException e) {
217                    if (e.getTargetException() != null) {
218                        Throwables.throwIfUnchecked(e.getTargetException());
219                        throw new RuntimeException(e.getTargetException());
220                    }
221                    throw e;
222                }
223            }
224            return (T) instance;
225        }
226
227        /**
228         * 获取{@code field}指定的字段值<br>
229         * 
230         * @param instance
231         * @param field
232         * @return
233         * @throws Exception
234         * @see {@link com.facebook.swift.codec.internal.reflection.AbstractReflectionThriftCodec#getFieldValue(Object, ThriftFieldMetadata)}
235         */
236        public static TypeValue getFieldValue(Object instance, ThriftFieldMetadata field) throws Exception {
237                try {
238                        if (field.getExtraction().isPresent()) {
239                                ThriftExtraction extraction = field.getExtraction().get();
240                                if (extraction instanceof ThriftFieldExtractor) {
241                                        ThriftFieldExtractor thriftFieldExtractor = (ThriftFieldExtractor) extraction;
242                                        Field f = thriftFieldExtractor.getField();
243                                        return new TypeValue(f.getGenericType(),f.get(instance));
244                                } else if (extraction instanceof ThriftMethodExtractor) {
245                                        ThriftMethodExtractor thriftMethodExtractor = (ThriftMethodExtractor) extraction;
246                                        Method method = thriftMethodExtractor.getMethod();
247                                        return new TypeValue(method.getGenericReturnType(),method.invoke(instance));
248                                }
249                                throw new IllegalAccessException("Unsupported field extractor type " + extraction.getClass().getName());
250                        }
251                        throw new IllegalAccessException("No extraction present for " + field);
252                } catch (InvocationTargetException e) {
253                        if (e.getTargetException() != null) {
254                                Throwables.throwIfInstanceOf(e.getTargetException(), Exception.class);
255                        }
256                        throw e;
257                }
258        }
259
260        /**
261         * 根据{@code metadata}类型数据获取{@code instance}实例所有的字段值
262         * 参见 {@link com.facebook.swift.codec.internal.reflection.ReflectionThriftStructCodec#write(Object, org.apache.thrift.protocol.TProtocol)}
263         * @param instance
264         * @param metadata
265         * @return 字段值映射表
266         */
267        public static Map<Short, TypeValue> getFieldValues(Object instance, ThriftStructMetadata metadata) {
268                checkArgument(null != instance && null != metadata && metadata.getStructClass().isInstance(instance), 
269                                "instance,metadata must not be null");
270                
271                Collection<ThriftFieldMetadata> fields = metadata.getFields(THRIFT_FIELD);
272                Map<Short, TypeValue> data = new HashMap<>(fields.size());
273                for (ThriftFieldMetadata field : fields) {
274                        try {
275                    // is the field readable?
276                    if (field.isWriteOnly()) {
277                        continue;
278                    }
279                                TypeValue value = getFieldValue(instance, field);
280                                if (value.value == null) {
281                                        if (field.getRequiredness() == Requiredness.REQUIRED) {
282                                                throw new TProtocolException("required field was not set");
283                                        } else {
284                                                continue;
285                                        }
286                                }
287                                data.put(field.getId(), value);
288                        } catch (Exception e) {
289                                Throwables.throwIfUnchecked(e);
290                                throw new RuntimeException(e);
291                        }
292                }
293                return data;
294        }
295        /**
296         * @param instance
297         * @param metadata
298         * @return
299         * @deprecated name spell error, replaced by {@link #getFieldValues(Object, ThriftStructMetadata)}
300         */
301        public static Map<Short, TypeValue> getFiledValues(Object instance, ThriftStructMetadata metadata) {
302                return getFieldValues(instance,metadata);
303        }
304        public static boolean isThriftStruct(Type type){
305                return type instanceof Class<?> 
306                        ? ((Class<?>)type).isAnnotationPresent(ThriftStruct.class) 
307                        : false;
308        }
309
310        public static boolean isThriftDecorator(Type type){
311                return type instanceof Class<?> 
312                                ? ThriftDecorator.class.isAssignableFrom((Class<?>)type) 
313                                : false;
314        }
315
316        public static boolean isPrimitiveArray(Type type){
317                if(type instanceof Class<?>){
318                        Class<?> clazz = (Class<?>)type;
319                        return clazz.isArray() && clazz.getComponentType().isPrimitive();
320                }
321                return false;
322        }
323
324        public static boolean isThriftBuildinType(Type type){           
325                return THRIFT_BUILTIN_KNOWNTYPES.contains(type);
326        }
327
328        public static boolean isPrimitivefloat(Type type){
329                return type == float.class;
330        }
331        public static boolean isfloat(Type type){
332                return type == float.class || type == Float.class;
333        }
334        public static boolean isCastType(Type type){
335                return  CAST_TYPES.containsKey(type);
336        }
337
338        public static boolean isException(Type type){
339                return null == type 
340                                ? false 
341                                : Exception.class.isAssignableFrom(TypeToken.of(type).getRawType());
342        }
343        public static <T>Constructor<T> getConstructor(Class<T> clazz,Class<?>...parameterTypes){
344                try {
345                        return clazz.getConstructor(parameterTypes);
346                } catch (NoSuchMethodException e) {
347                        return null;
348                } 
349        }
350        public static <T> boolean hasConstructor(Class<T> clazz,Class<?>...parameterTypes){
351                return getConstructor(clazz,parameterTypes) != null;
352        }
353        public static boolean isThriftException(Type type){
354                return isException(type) && isThriftStruct(type);
355        }
356        public static boolean isThriftException(Type left, Type right){
357                return isThriftException(left) && isThriftException(right);
358        }
359
360        public static boolean needTransformer(Type type){
361                return ! isThriftBuildinType(type) && ! isfloat(type);
362        }
363        public static interface Action{
364                void doClass(Class<?> type);
365        }
366        public static void traverseTypes(Type type,Action action){
367                checkArgument(null !=action,"action is null");
368                if(type instanceof Class<?>){
369                        action.doClass((Class<?>) type);
370                }else if( type instanceof ParameterizedType){
371                        ParameterizedType paramType = (ParameterizedType)type;
372                        Type rawType = paramType.getRawType();
373                        Type[] typeArgs = paramType.getActualTypeArguments();
374                        traverseTypes(rawType,action);
375                        for(Type arg:typeArgs){
376                                traverseTypes(arg,action);
377                        }
378                }else if (type instanceof GenericArrayType) {
379                        traverseTypes(((GenericArrayType) type).getGenericComponentType(),action);
380                } else if (type instanceof TypeVariable) {
381                        for (Type t : ((TypeVariable<?>) type).getBounds()) {
382                                traverseTypes(t,action);
383                        }
384                } else if (type instanceof WildcardType) {
385                        for (Type t : ((WildcardType) type).getLowerBounds()) {
386                                traverseTypes(t,action);
387                        }
388                        for (Type t : ((WildcardType) type).getUpperBounds()) {
389                                traverseTypes(t,action);
390                        }
391                } else{
392                        throw new IllegalArgumentException(String.format("not allow type %s", type.toString()));
393                }
394        }
395        @SuppressWarnings("serial")
396        public static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
397                  return new TypeToken<Map<K, V>>() {}
398                    .where(new TypeParameter<K>() {}, keyToken)
399                    .where(new TypeParameter<V>() {}, valueToken);
400        }
401        @SuppressWarnings("serial")
402        public static <T> TypeToken<List<T>> listToken(TypeToken<T> keyToken) {
403                  return new TypeToken<List<T>>() {}
404                    .where(new TypeParameter<T>() {}, keyToken);
405        }
406        @SuppressWarnings("serial")
407        public static <T> TypeToken<Set<T>> setToken(TypeToken<T> keyToken) {
408                  return new TypeToken<Set<T>>() {}
409                    .where(new TypeParameter<T>() {}, keyToken);
410        }
411
412        /**
413         * @param type
414         * @return
415         * @see #getDecoratorType(Type)
416         */
417        public static boolean hasDecoratorType(Type type){
418                return getDecoratorType(type) !=null;
419        }
420        /**
421         * @param type
422         * @return
423         * @see #getDecoratorType(Class)
424         */
425        @SuppressWarnings("unchecked")
426        public static <T>Class<? extends ThriftDecorator<T>> getDecoratorType(Type type){
427                if(!isThriftStruct(type)){
428                        return getDecoratorType((Class<T>)type);
429                }
430                return null;
431        }
432        /**
433         * 返回{@code clazz}对应的装饰类
434         * @param clazz
435         * @return 如果没有装饰类则返回{@code null}
436         */
437        @SuppressWarnings("unchecked")
438        public static <T,D extends ThriftDecorator<T>>Class<D> getDecoratorType(Class<T> clazz){
439                if(!isThriftStruct(clazz)){
440                        String decoratorClazzName = clazz.getPackage().getName() 
441                                        + "."
442                                        + DECORATOR_CLIENT_PKG_SUFFIX 
443                                        + "." 
444                                        + clazz.getSimpleName();
445                        try {
446                                Class<?> decoratorClazz = Class.forName(decoratorClazzName);
447                                checkState(isThriftDecoratorPair(decoratorClazz,clazz),
448                                                "%s must immplement %s",
449                                                decoratorClazz.getName(),
450                                                ThriftDecorator.class.getName());
451                                return (Class<D>) decoratorClazz;
452                        } catch (ClassNotFoundException e) {
453                        }
454                }
455                return null;
456        }
457
458        /**
459         * 判断 {@code left and right}之间是否为装饰类和被装饰类关系
460         * @param left 装饰类
461         * @param right 补装饰类
462         * @return
463         */
464        public static <L,R>boolean isThriftDecoratorPair(Class<L>left,Class<R>right){
465                try {
466                        return isThriftDecorator(left) 
467                                        && left.getMethod("delegate").getReturnType() == right;
468                } catch (NoSuchMethodException e) {
469                        throw new RuntimeException(e);
470                }
471        }
472
473        /**
474         * 判断 {@code right}是否为{@code left}对应的client端存根类型
475         * @param left
476         * @param right
477         * @return
478         */
479        public static <L,R>boolean isThriftClientPair(Class<L>left,Class<R>right){
480                return getMiddleClass(left,right)!=null;
481        }
482        /**
483         * 返回 {@code left & right}之间的decorator类型
484         * @param left 原始类型
485         * @param right {@code left}对应的client端存根类型
486         * @return
487         */
488        public static <L,M extends ThriftDecorator<L>,R>Class<M> getMiddleClass(Class<L>left,Class<R>right){
489                if(isThriftStruct(right)){
490                        Class<M> decoratorClass = getDecoratorType(left);
491                        if(null != decoratorClass && decoratorClass.getSimpleName().equals(left.getSimpleName())){
492                                return decoratorClass;
493                        }                       
494                }
495                return null;
496        }
497        /**
498         * @param left
499         * @param right
500         * @return
501         * @see #getMiddleClass(Class, Class)
502         */
503        @SuppressWarnings("unchecked")
504        public static <L,M extends ThriftDecorator<L>,R>Class<M> getMiddleClassChecked(Class<L>left,Class<R>right){
505                return (Class<M>) checkNotNull(
506                                                getMiddleClass(left,right),
507                                                "NOT FOUND decorator class for %s",
508                                                left.getName());
509        }
510        public static final String ISLOCAL_METHOD_NAME = "isLocal";
511        public static boolean isIsLocalMethod(Method method){
512                if(null == method){
513                        return false;
514                }
515                return method.getName().equals(ISLOCAL_METHOD_NAME)
516                                && method.getParameterTypes().length == 0 
517                                && method.getExceptionTypes().length == 0
518                                && method.getReturnType() == boolean.class;
519        }
520
521        /** 避免{@code null}抛出异常 */
522        public static <T> T returnNull(RuntimeTApplicationException e){
523            Throwable cause = e.getCause();
524            if (cause instanceof TApplicationException  
525                    && ((TApplicationException) cause).getType() == TApplicationException.MISSING_RESULT){
526                return null;
527            }
528            throw e;
529        }
530        /** 避免{@code null}抛出异常 
531         * @throws Throwable */
532        public static <T> T returnNull(Throwable e) throws Throwable{
533                if(e instanceof RuntimeTApplicationException){
534                        return returnNull((RuntimeTApplicationException)e);
535                }
536            throw e;
537        }
538        public static<V> void addCallback(
539                        final ListenableFuture<V> future,
540                        final FutureCallback<? super V> callback,Executor executor) {
541                checkArgument(null != callback,"callback is null");
542                checkArgument(null != executor,"executor is null");
543                Runnable callbackListener =
544                                new Runnable() {
545                        @Override
546                        public void run() {
547                                V value;
548                                try {
549                                        value = Futures.getDone(future);
550                                } catch (ExecutionException e) {
551                                        try{
552                                                // value is null
553                                                value = returnNull(e.getCause()); 
554                                        }catch(Throwable t){
555                                                callback.onFailure(t);
556                                                return;
557                                        }                    
558                                } catch (Throwable e) {
559                                        callback.onFailure(e);
560                                        return;
561                                }
562                                callback.onSuccess(value);
563                        }
564                };
565                future.addListener(callbackListener, executor);
566        }
567}