001package net.gdface.thrift;
002
003import static com.google.common.base.Preconditions.checkArgument;
004import static com.google.common.base.Preconditions.checkNotNull;
005import static net.gdface.thrift.TypeTransformer.getInstance;
006
007import java.lang.reflect.Field;
008import java.lang.reflect.InvocationTargetException;
009import java.lang.reflect.Method;
010import java.lang.reflect.Modifier;
011import java.util.Map;
012import java.util.Map.Entry;
013
014import javax.annotation.concurrent.Immutable;
015
016import com.google.common.base.Throwables;
017import com.google.common.cache.CacheBuilder;
018import com.google.common.cache.CacheLoader;
019import com.google.common.cache.LoadingCache;
020import com.google.common.collect.ImmutableMap;
021import com.microsoft.thrifty.Struct;
022import com.microsoft.thrifty.StructBuilder;
023
024@Immutable
025public class ThriftyStructMetadata {
026    public static final LoadingCache<Class<?>,ThriftyStructMetadata> 
027        STRUCTS_CACHE = 
028                CacheBuilder.newBuilder().build(
029                                new CacheLoader<Class<?>,ThriftyStructMetadata>(){
030                                        @Override
031                                        public ThriftyStructMetadata load(Class<?> key) throws Exception {
032                                                return new ThriftyStructMetadata(key);
033                                        }});
034        private final Class<?> structType;
035        /** 字段ID对应的字段对象 */
036        private final ImmutableMap<String, Field> fields;
037        /** builder对象所有字段的set方法 */
038        private final ImmutableMap<String, Method> buildSetters;
039        private final Class<? extends StructBuilder<?>> builderClass;
040        private final Method buildMethod;
041        @SuppressWarnings("unchecked")
042        private ThriftyStructMetadata(Class<?> structType ) {
043                this.structType = checkNotNull(structType,"struct is null");
044                checkArgument(Struct.class.isAssignableFrom(structType),
045                                "structType %s not immplement the %s",structType.getName(),Struct.class.getName());
046                try {
047                        String className = structType.getName() + "$Builder";
048                        builderClass = (Class<? extends StructBuilder<?>>) Class.forName(className);
049                } catch (Exception e) {
050            Throwables.throwIfUnchecked(e);
051            throw new RuntimeException(e);       
052        } 
053                ImmutableMap.Builder<String, Field> fieldBuilder = ImmutableMap.builder();
054                ImmutableMap.Builder<String, Method> methodBuilder = ImmutableMap.builder();
055
056                for(Field field:structType.getDeclaredFields()){
057                        if((field.getModifiers() & Modifier.STATIC)==0){
058                                fieldBuilder.put(field.getName(), field);
059                                try {
060                                        Method method = builderClass.getMethod(field.getName(), field.getType());
061                                        methodBuilder.put(field.getName(), method);
062                                } catch (NoSuchMethodException e) {
063                                        throw new RuntimeException(e);
064                                } 
065                        } 
066                }
067                fields = fieldBuilder.build();
068                buildSetters = methodBuilder.build();
069                
070                try {
071                        buildMethod = builderClass.getDeclaredMethod("build");
072                } catch (Exception e) {
073            Throwables.throwIfUnchecked(e);
074            throw new RuntimeException(e);       
075        } 
076        }
077        public ImmutableMap<String, Field> getFields() {
078                return fields;
079        }
080        public Class<?> getStructType() {
081                return structType;
082        }
083        @SuppressWarnings("unchecked")
084        public <L,R>void setValue(Object instance,short id,L value){
085                checkNotNull(instance,"instance is null");
086                checkArgument(structType.isInstance(instance),"invalid value type,required %s",structType.getName());
087                Field field = fields.get(id);
088                checkNotNull(field,"invalid field id=%s for %s",Short.toString(id),structType.getName());
089                Class<?> fieldType = field.getType();
090                try {
091                        field.setAccessible(true);
092                        if(value == null){
093                                checkArgument(!fieldType.isPrimitive(),
094                                                "primitive field %s of %s required not null value",field.getName(),structType.getName());
095                                field.set(instance,null);
096                        }
097                        else{
098                                field.set(instance, getInstance().to(value, (Class<L>) value.getClass(), fieldType));
099                        }
100                } catch (Exception e) {
101            Throwables.throwIfUnchecked(e);
102            throw new RuntimeException(e);
103        } 
104        }
105        @SuppressWarnings("unchecked")
106        private <V>V getValue(Object instance,String name){
107                checkArgument(structType.isInstance(instance),"invalid value type,required %s",structType.getName());
108
109                Field field = fields.get(name);
110                checkNotNull(field,"invalid field name=%s for %s",name,structType.getName());
111                try {
112                        return (V) field.get(instance);
113                } catch (Exception e) {
114            Throwables.throwIfUnchecked(e);
115            throw new RuntimeException(e);       
116        } 
117        }
118
119        /**
120         * 根据字段值构造实例
121         * @param fieldValues
122         * @return
123         */
124        @SuppressWarnings("unchecked")
125        public <T>T constructStruct(Map<String, TypeValue> fieldValues){
126                checkNotNull(fieldValues,"fieldValues is null");
127                try {
128                        // new Builder()
129                        StructBuilder<T> builder = (StructBuilder<T>) builderClass.newInstance();
130                        for(Entry<String, TypeValue> entry : fieldValues.entrySet()){
131                                Method method = buildSetters.get(entry.getKey());
132                                checkNotNull(method,"method is null");
133                                TypeValue typeValue= entry.getValue();
134                                // 调用Builder的设置方法设置字段值
135                                method.invoke(builder, getInstance().cast(
136                                                typeValue.value,
137                                                typeValue.type,
138                                                fields.get(entry.getKey()).getGenericType()));
139                        }
140                        // build()
141                        return (T) buildMethod.invoke(builder);
142                } catch (InvocationTargetException e){
143                        Throwables.throwIfUnchecked(e.getCause());
144            throw new RuntimeException(e.getCause());  
145                }catch (Exception e) {
146            Throwables.throwIfUnchecked(e);
147            throw new RuntimeException(e);       
148        }
149        }
150        /**
151         * 从 {@link com.microsoft.thrifty.Struct} 实例中返回所有字段值
152         * @param instance
153         * @return
154         */
155        Map<String, TypeValue> getFieldValues(Object instance){
156                checkNotNull(instance,"instance is null");
157                checkArgument(structType.isInstance(instance),"invalid value type,required %s",structType.getName());
158                ImmutableMap.Builder<String, TypeValue> builder = ImmutableMap.builder();
159                for(Entry<String, Field> entry:fields.entrySet()){
160                        String name = entry.getKey();
161                        Field field = entry.getValue();
162                        builder.put(name, new TypeValue(field.getType(), getValue(instance, name)));
163                }               
164                return builder.build();
165        }
166}