001package net.gdface.codegen.thrift;
002
003import net.gdface.annotation.CodegenDefaultvalue;
004import net.gdface.annotation.CodegenInvalidValue;
005import net.gdface.annotation.CodegenLength;
006import net.gdface.annotation.CodegenRequired;
007import net.gdface.codegen.AbstractSchema;
008import net.gdface.codegen.generator.GeneratorUtils;
009import net.gdface.codegen.thrift.ThriftServiceDecoratorConfiguration.LanguageType;
010import net.gdface.thrift.ThriftDecorator;
011import net.gdface.thrift.ThriftUtils;
012import net.gdface.thrift.TypeTransformer;
013import net.gdface.thrift.exception.BaseServiceException;
014import net.gdface.utils.NameStringUtils;
015
016import static com.google.common.base.Preconditions.*;
017
018import java.beans.PropertyDescriptor;
019import java.lang.reflect.Method;
020import java.lang.reflect.Type;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Comparator;
024import java.util.List;
025import java.util.Map;
026import java.util.Map.Entry;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import com.facebook.swift.codec.ThriftField;
032import com.facebook.swift.codec.ThriftField.Requiredness;
033import com.facebook.swift.codec.metadata.ThriftCatalogWithTransformer;
034import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
035import com.facebook.swift.codec.metadata.ThriftStructMetadata;
036import com.google.common.base.Function;
037import com.google.common.base.Predicate;
038import com.google.common.base.Predicates;
039import com.google.common.base.Strings;
040import com.google.common.base.Throwables;
041import com.google.common.collect.ImmutableList;
042import com.google.common.collect.ImmutableMap;
043import com.google.common.collect.ImmutableMap.Builder;
044import com.google.common.collect.Iterables;
045import com.google.common.collect.Lists;
046import com.google.common.collect.Maps;
047import com.google.common.primitives.Primitives;
048
049/**
050 * 装饰类信息
051 * @author guyadong
052 *
053 */
054public class ThriftStructDecorator extends AbstractSchema implements ThriftConstants,Comparator<ThriftStructDecorator>,Comparable<ThriftStructDecorator>{
055        private static final Logger logger = LoggerFactory.getLogger(ThriftStructDecorator.class);
056
057        private final Map<String, PropertyDescriptor> fields;
058        private final String decoratorPackage;
059        private final String decoratorClassName;
060        private final TypeHelper typeHelper = new TypeHelper(this);
061
062        private final Map<String, CxxType> cxxFields;
063
064        private final boolean hasOptional;
065        private final boolean hasCanMove;
066
067        private final CxxType cxxType;
068
069        private final TraverseTypeForTryFind findString= new TraverseTypeForTryFind(new Predicate<CxxTypeMeta>(){
070                @Override
071                public boolean apply(CxxTypeMeta input) {
072                        return null != input && (input.isBinary() || input.isString());
073                }});
074        private final TraverseTypeForTryFind findBinary= new TraverseTypeForTryFind(new Predicate<CxxTypeMeta>(){
075                @Override
076                public boolean apply(CxxTypeMeta input) {
077                        return null != input && (input.isBinary());
078                }});
079        private final TraverseTypeForTryFind findMap= new TraverseTypeForTryFind(new Predicate<CxxTypeMeta>(){
080                @Override
081                public boolean apply(CxxTypeMeta input) {
082                        return null != input && input.isMap();
083                }});
084        private final TraverseTypeForTryFind findSet= new TraverseTypeForTryFind(new Predicate<CxxTypeMeta>(){
085                @Override
086                public boolean apply(CxxTypeMeta input) {
087                        return null != input && input.isSet();
088                }});
089        private final TraverseTypeForTryFind findArray= new TraverseTypeForTryFind(new Predicate<CxxTypeMeta>(){
090                @Override
091                public boolean apply(CxxTypeMeta input) {
092                        return null != input && input.isList();
093                }});
094        private final Predicate<PropertyDescriptor> codegenInvalidValueFilter = new Predicate<PropertyDescriptor>() {
095
096                @Override
097                public boolean apply(PropertyDescriptor input) {
098                        if(input != null){
099                                CodegenInvalidValue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenInvalidValue.class);
100                                if(ann != null){
101                                        if(GeneratorUtils.isString(input.getPropertyType()) || GeneratorUtils.isBinary(input.getPropertyType())){
102                                                // 对于String,二进制类型不判断空字符串,
103                                                // 空字符串可以视为缺省值
104                                                return true;
105                                        }                                       
106                                        return  ann.value().length()>0 || ann.exp().length() > 0;
107                                }                               
108                        }
109                        return false;
110                }
111        };
112        private final Predicate<PropertyDescriptor> codegenDefaultvalueFilter = new Predicate<PropertyDescriptor>() {
113
114                @Override
115                public boolean apply(PropertyDescriptor input) {
116                        if(input != null){
117                                CodegenDefaultvalue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenDefaultvalue.class);
118                                if(ann != null){
119                                        if(GeneratorUtils.isString(input.getPropertyType()) || GeneratorUtils.isBinary(input.getPropertyType())){
120                                                // 对于String,二进制类型不判断空字符串,
121                                                // 空字符串可以视为缺省值
122                                                return true;
123                                        }                                       
124                                        return  ann.value().length()>0;
125                                }       
126                        }
127                        return false;
128                }
129        };
130        private final Predicate<PropertyDescriptor> codegenLengthFilter = new Predicate<PropertyDescriptor>() {
131
132                @Override
133                public boolean apply(PropertyDescriptor input) {
134                        if(input != null && !input.getPropertyType().isPrimitive() && !Primitives.unwrap(input.getPropertyType()).isPrimitive()){
135                                CodegenLength ann = GeneratorUtils.extractFieldAnnotation(input, CodegenLength.class);
136                                return ann != null && (ann.value()> 0 || ann.max()> 0);
137                        }
138                        return false;
139                }
140        };
141        private static final String FIELD_NEW_ = "_new";
142        private static final String FIELD_NEW = "new";
143        private static final String FIELD_MODIFIED = "modified";
144        private static final String FIELD_INITIALIZED = "initialized";
145        static String sql2javaBeanPkgPrefix = null;
146        /** sql2java生成的JavaBean 类验证器 */
147        static final Predicate<ThriftStructDecorator> SQL2JAVA_BEAN_FILTER = new Predicate<ThriftStructDecorator>(){
148                private boolean checkField(ThriftStructDecorator input,String name,Class<?> type){
149                        try{
150                                        PropertyDescriptor field = input.getField(name);
151                                        return (field.getPropertyType().equals(type))
152                                                        && field.getReadMethod() != null 
153                                                        && field.getWriteMethod() != null;
154                                }catch (Exception e) {
155                                        return false;
156                                }
157                }
158                @Override
159                public boolean apply(ThriftStructDecorator input) {
160                        try{
161                                boolean m1 = checkField(input,FIELD_NEW,boolean.class) 
162                                                && checkField(input,FIELD_MODIFIED,int.class)
163                                                && checkField(input,FIELD_INITIALIZED,int.class);
164                                boolean m2 = checkField(input,FIELD_NEW,boolean.class) 
165                                                && checkField(input,FIELD_MODIFIED,int[].class)
166                                                && checkField(input,FIELD_INITIALIZED,int[].class);
167                                boolean m = m1 || m2;
168                                if(!Strings.isNullOrEmpty(ThriftStructDecorator.sql2javaBeanPkgPrefix)){
169                                        m = m && input.getBaseClass().getPackage().getName().startsWith(ThriftStructDecorator.sql2javaBeanPkgPrefix);
170                                }
171                                return m;
172                        }catch (Exception e) {
173                                return false;
174                        }
175                }};
176        /**
177         * 对{@code baseClass}生成装饰类
178         * @param baseClass 
179         */
180        public ThriftStructDecorator(Class<? > baseClass) {
181                this.baseClass = checkNotNull(baseClass);
182                TypeHelper.checkNotGeneric(baseClass);
183                if(baseClass.isInterface()){
184                        throw new IllegalArgumentException("baseClass must not be interface");
185                }
186                if(LanguageType.CPP.equals(LanguageType.getCurrent()) 
187                                && ThriftUtils.isException(baseClass) 
188                                && !ThriftUtils.isThriftStruct(baseClass)){
189                        // 所有非thrift struct的服务异常会被生成对应的装饰类(以BaseServiceException为父类),
190                        // 所以生成C++代码时要包含 BaseServiceException 的字段
191                        Builder<String,PropertyDescriptor> builder = ImmutableMap.builder();
192                        builder.putAll(typeHelper.getFields(BaseServiceException.class,Predicates.<PropertyDescriptor>alwaysTrue(),true));
193                        builder.putAll(typeHelper.getFields(baseClass,Predicates.<PropertyDescriptor>alwaysTrue(),true));
194                        fields = builder.build();
195                }else{
196                        fields = ImmutableMap.copyOf(typeHelper.getFields(baseClass,Predicates.<PropertyDescriptor>alwaysTrue(),true));
197                }
198                hasOptional = Iterables.tryFind(fields.values(), new Predicate<PropertyDescriptor>(){
199                        @Override
200                        public boolean apply(PropertyDescriptor input) {
201                                return !input.getReadMethod().getReturnType().isPrimitive();
202                        }}).isPresent();
203
204                cxxFields=LanguageType.CPP.equals(LanguageType.getCurrent()) 
205                                ? getThriftTypes(fields) 
206                                : Collections.<String, CxxType>emptyMap();
207                cxxType = CxxType.getThriftType(getBaseClass());
208                hasCanMove = Iterables.tryFind(cxxFields.values(), new Predicate<CxxType>(){
209                        @Override
210                        public boolean apply(CxxType input) {
211                                return input.getStubType().isCanMove();
212                        }}).isPresent();
213                StringBuffer buffer = new StringBuffer(baseClass.getPackage().getName());
214                buffer.append( "." + ThriftUtils.DECORATOR_PKG_SUFFIX);
215                switch(ThriftServiceDecoratorConfiguration.INSTANCE.getTaskType()){
216                case CLIENT:
217                        buffer.append(".").append(ThriftUtils.CLIENT_SUFFIX);break;
218                case CLIENT_THRIFTY:
219                        buffer.append(".").append(ThriftUtils.CLIENT_SUFFIX);break;
220                default:
221                                break;
222                }
223                decoratorPackage = buffer.toString();
224                decoratorClassName = decoratorPackage + "." + this.baseClass.getSimpleName();           
225        }
226        @Override
227        public boolean compile() {
228                // 循环扫描检查所有的引用类型
229                for(Entry<String, PropertyDescriptor> entry:fields.entrySet()){
230                        PropertyDescriptor descriptor = entry.getValue();
231                        Type returnType = descriptor.getReadMethod().getGenericReturnType();
232                        typeHelper.checkType(returnType);
233                        typeHelper.addReferTypes(returnType);   
234                        if(NameStringUtils.isThriftReserved( descriptor.getName())){
235                                logger.warn("field name '{}' of {} is thrift IDL reserved word", descriptor.getName(),this.getBaseClass().getName());
236                        }
237                        if(NameStringUtils.isJavaReserved( descriptor.getName())){
238                                logger.warn("field name '{}' of {} is java reserved word", descriptor.getName(),this.getBaseClass().getName());
239                        }
240                }
241                if(typeHelper.needTransformer()){
242                        addImportedClass(TypeTransformer.class);
243                }
244                addImportedClass(ThriftDecorator.class);
245                
246                return true;
247        }
248        public Method getGetMethod(String field){
249                PropertyDescriptor descriptor = getField(field);
250                return descriptor.getReadMethod();              
251        }
252        public Method getSetMethod(String field){
253                PropertyDescriptor descriptor = getField(field);
254                return descriptor.getWriteMethod();             
255        }
256
257        public Type getFieldType(String field){
258                PropertyDescriptor descriptor = getField(field);
259                return descriptor.getReadMethod().getGenericReturnType();
260        }
261        
262        public PropertyDescriptor getField(String field){
263                PropertyDescriptor descriptor = fields.get(field);
264                if(null == descriptor){
265                        throw new IllegalArgumentException(String.format("INVALID field %s in %s", field,this.baseClass.getSimpleName()));
266                }
267                return descriptor;
268        }
269        /**
270         * 返回数据结构的所有字段名,如果是sql2java生成的Thrift Struct 对象,则忽略'new','initialized','modified'字段
271         * @return 字段名列表
272         */
273        public List<String> getFields(){                
274                if(isThriftStruct()){
275                        ThriftStructMetadata thriftMetadata = ThriftCatalogWithTransformer.CATALOG.getThriftStructMetadata(getBaseClass());
276                        if(isSql2javaBean()){
277                                List<String> list = Lists.newLinkedList();
278                                // 与thrift struct中定义的字段顺序保持一致
279                                for(ThriftFieldMetadata field:thriftMetadata.getFields()){
280                                        String name = field.getName();
281                                        if(!name.equals(FIELD_NEW_) && !name.equals(FIELD_INITIALIZED) && !name.equals(FIELD_MODIFIED)){
282                                                list.add(name);
283                                        }
284                                }                       
285                                return list;
286                        }else{
287                                List<String> list = Lists.newLinkedList();
288                                // 与thrift struct中定义的字段顺序保持一致
289                                for(ThriftFieldMetadata field:thriftMetadata.getFields()){
290                                        list.add(field.getName());
291                                }                       
292                                return list;
293                        }
294                }
295                ArrayList<String> list = new ArrayList<>(this.fields.keySet());
296                // 字段名排序,确保输出的代码顺序稳定
297                Collections.sort(list);
298                return list;
299        }
300        /**
301         * 返回数据结构的非primitive类型所有字段名
302         * 
303         * @return 字段名列表
304         */
305        public List<String> getRefFields(){
306                return Lists.newArrayList(Iterables.filter(getFields(), new Predicate<String>() {
307
308                        @Override
309                        public boolean apply(String input) {
310                                return !getField(input).getPropertyType().isPrimitive();
311                        }
312                }));
313        }
314        /**
315         * @return 返回枚举类型的所有对象
316         */
317        public Object getEnums(){
318                if(isEnum()){
319                        try {
320                                return baseClass.getMethod("values").invoke(null);
321                        } catch (Exception e) {
322                                Throwables.throwIfUnchecked(e);
323                                throw new RuntimeException(e);
324                        }
325                }
326                return Collections.emptyList();
327        }
328        public List<PropertyDescriptor> getFieldDescriptors(){
329                return ImmutableList.copyOf(this.fields.values());
330        }
331        public String getDecoratorPackage() {
332                return decoratorPackage;
333        }
334
335        public String getDecoratorClassName() {
336                return decoratorClassName;
337        }
338
339        public List<ThriftStructDecorator> getDecorateTypes() {
340                return typeHelper.getDecorateTypes();
341        }
342        public List<ThriftStructDecorator> getThriftTypes() {
343                return typeHelper.getThriftTypes();
344        }
345        public boolean isException(){
346                return Exception.class.isAssignableFrom(getBaseClass());
347        }
348        public boolean isThriftStruct(){
349                return ThriftUtils.isThriftStruct(getBaseClass());
350        }
351        public boolean isEnum(){
352                return getBaseClass().isEnum();
353        }
354        public boolean isBean(){
355                return !isException() && !isEnum(); 
356        }
357        public boolean isSql2javaBean(){
358                return isBean() && SQL2JAVA_BEAN_FILTER.apply(this);
359        }
360        public boolean hasStringConstructor(){
361                return ThriftUtils.hasConstructor(getBaseClass(), String.class);
362        }
363        public String toThriftType(Type type) {
364                return typeHelper.toThriftType(type);
365        }
366
367        public String toClientThriftType(Type type) {
368                return typeHelper.toClientThriftType(type);
369        }
370        public String toClientThriftyType(Type type) {
371                return typeHelper.toClientThriftyType(type);
372        }
373
374        public String toThriftyDecoratorType(Type type) {
375                return typeHelper.toThriftyDecoratorType(type);
376        }
377        @Override
378        public int hashCode() {
379                final int prime = 31;
380                int result = 1;
381                result = prime * result + ((baseClass == null) ? 0 : baseClass.hashCode());
382                return result;
383        }
384
385        @Override
386        public boolean equals(Object obj) {
387                if (this == obj)
388                        return true;
389                if (obj == null)
390                        return false;
391                if (!(obj instanceof ThriftStructDecorator))
392                        return false;
393                ThriftStructDecorator other = (ThriftStructDecorator) obj;
394                if (baseClass == null) {
395                        if (other.baseClass != null)
396                                return false;
397                } else if (!baseClass.equals(other.baseClass))
398                        return false;
399                return true;
400        }
401
402        @Override
403        public String toString() {
404                StringBuilder builder = new StringBuilder();
405                builder.append("ThriftStructDecorator [baseClass=");
406                builder.append(baseClass);
407                builder.append("]");
408                return builder.toString();
409        }
410        public void removeDecorateTypesFromImports(){
411                // 排除枚举类型
412                Iterable<Class<?>> it = Iterables.filter(typeHelper.getTypesWithDecorator(),new Predicate<Class<?>>() {
413                        @Override
414                        public boolean apply(Class<?> input) {
415                                return !input.isEnum();
416                        }
417                });
418                ArrayList<Class<?>> types = Lists.newArrayList(it);
419                this.removeClassFromImports(types);
420        }
421
422        @Override
423        public int compare(ThriftStructDecorator o1, ThriftStructDecorator o2) {
424                return o1.getBaseClass().getSimpleName().compareTo(o2.getBaseClass().getSimpleName());
425        }
426
427        @Override
428        public int compareTo(ThriftStructDecorator o) {
429                return getBaseClass().getSimpleName().compareTo(o.getBaseClass().getSimpleName());
430        }
431
432        private static ImmutableMap<String, CxxType> getThriftTypes(Map<String, PropertyDescriptor> fields){            
433                Map<String, CxxType> m = Maps.transformValues(checkNotNull(fields,"fields is null"), 
434                                new Function<PropertyDescriptor,CxxType>(){
435                                        @Override
436                                        public CxxType apply(PropertyDescriptor input) {
437                                                return CxxType.getThriftType(input.getReadMethod().getGenericReturnType());
438                                        }                       
439                });
440                return ImmutableMap.copyOf(m);
441        }
442        public ImmutableMap<String, CxxType> getCxxFieldsAsMap(){
443                return ImmutableMap.copyOf(this.cxxFields);
444        }
445        public CxxType getCxxField(String name){                
446                return cxxFields.get(checkNotNull(name,"name is null"));
447        }
448        /**
449         * 是否有optional字段
450         * @return
451         */
452        public boolean isHasOptionalField(){
453                return hasOptional;
454        }
455        
456        /**
457         * 是否有需要移动的字段
458         * @return
459         */
460        public boolean isHasCanMoveField() {
461                return hasCanMove;
462        }
463        public CxxType getCxxType() {
464                return cxxType;
465        }
466        
467        private static class TraverseTypeForTryFind implements Predicate<CxxType>{
468                private Predicate<CxxTypeMeta> finder;
469                TraverseTypeForTryFind(Predicate<CxxTypeMeta> finder){
470                        this.finder = finder;
471                }
472                @Override
473                public boolean apply(CxxType input) {
474                        if(null == input){
475                                return false;
476                        }
477                        CxxTypeMeta type = input.getUiType();
478                        if(finder.apply(type)){
479                                return true;
480                        }
481                        if(type.isContainer()){
482                                CxxTypeMeta keyType = type.getKeyType();
483                                CxxTypeMeta valueType = type.getValueType();
484                                if(null != keyType && finder.apply(keyType)){
485                                        return true;
486                                }
487                                if(null != valueType && finder.apply(valueType)){
488                                        return true;
489                                }
490                        }
491                        return false;
492                }}
493        public boolean isUseString(){
494                return Iterables.tryFind(cxxFields.values(), findString).isPresent();
495        }
496        public boolean isUseBinary(){
497                return Iterables.tryFind(cxxFields.values(), findBinary).isPresent();
498        }
499        public boolean isUseMap(){
500                return Iterables.tryFind(cxxFields.values(), findMap).isPresent();
501        }
502        public boolean isUseSet(){
503                return Iterables.tryFind(cxxFields.values(), findSet).isPresent();
504        }
505        public boolean isUseVector(){
506                return Iterables.tryFind(cxxFields.values(), findArray).isPresent();
507        }       
508        public boolean isUseCodegenInvalidValue(){
509                return Iterables.tryFind(fields.values(), codegenInvalidValueFilter).isPresent();
510        }
511        public boolean isUseCodegenDefaultvalue(){
512                return Iterables.tryFind(fields.values(), codegenDefaultvalueFilter).isPresent();
513        }
514        public boolean isUseCodegenLength(){
515                return Iterables.tryFind(fields.values(), codegenLengthFilter).isPresent();
516        }
517        public boolean needRenderCodegen(PropertyDescriptor input){
518                return codegenInvalidValueFilter.apply(input) || codegenDefaultvalueFilter.apply(input) || codegenLengthFilter.apply(input);
519        }
520        public String renderCodegenInvalidValue(PropertyDescriptor input){
521                if(codegenInvalidValueFilter.apply(input)){
522                        CodegenInvalidValue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenInvalidValue.class);
523                        if(ann.value().length() >0){
524                                return String.format("@CodegenInvalidValue(\"%s\")", ann.value());
525                        }else{
526                                return String.format("@CodegenInvalidValue(exp=\"%s\")", ann.exp());
527                        }
528                }
529                return null;
530        }
531        public String renderCodegenDefaultValue(PropertyDescriptor input){
532                if(codegenDefaultvalueFilter.apply(input)){
533                        CodegenDefaultvalue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenDefaultvalue.class);
534                        return String.format("@CodegenDefaultvalue(\"%s\")", ann.value());
535                }
536                return null;
537        }
538        public String renderCodegenLength(PropertyDescriptor input){
539                if(codegenLengthFilter.apply(input)){
540                        CodegenLength ann = GeneratorUtils.extractFieldAnnotation(input, CodegenLength.class);
541                        if(ann.value() >0){
542                                return String.format("@CodegenLength(\"%d\")", ann.value());
543                        }else{
544                                return String.format("@CodegenLength(max=\"%d\")", ann.max());
545                        }
546                }
547                return null;
548        }       
549        public Integer lengthLimitOf(PropertyDescriptor input){
550                if(codegenLengthFilter.apply(input)){
551                        CodegenLength ann = GeneratorUtils.extractFieldAnnotation(input, CodegenLength.class);
552                        int limit;
553                        if(ann.value() >0){
554                                limit = ann.value();
555                        }else{
556                                limit = ann.max();
557                        }
558                        if(String.class.equals(input.getPropertyType())){
559                                limit++;
560                        }
561                        return limit;
562                }
563                return null;
564        }
565        public String defaultValueOf(PropertyDescriptor input){
566                if(codegenDefaultvalueFilter.apply(input)){
567                        CodegenDefaultvalue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenDefaultvalue.class);
568                        return ann.value();
569                }
570                return null;
571        }
572        public String invalidValueOf(PropertyDescriptor input){
573                if(codegenInvalidValueFilter.apply(input)){
574                        CodegenInvalidValue ann = GeneratorUtils.extractFieldAnnotation(input, CodegenInvalidValue.class);
575                        if(ann.value().length() >0){
576                                return ann.value();
577                        }else{
578                                return ann.exp();
579                        }
580                }
581                return null;
582        }
583        public String initValueOf(PropertyDescriptor input){
584                String initValue = invalidValueOf(input);       
585                return initValue == null ? defaultValueOf(input) : initValue;
586        }
587        public boolean isRequired(PropertyDescriptor input){
588                if(input != null){
589                        CodegenRequired ann = GeneratorUtils.extractFieldAnnotation(input, CodegenRequired.class);
590                        if(ann != null){
591                                return ann.value();
592                        }
593                        ThriftField thriftField=GeneratorUtils.extractFieldAnnotation(input, ThriftField.class);
594                        if(thriftField != null){
595                                return Requiredness.REQUIRED.equals(thriftField.requiredness());
596                        }
597                }
598                return false;
599        }
600        public boolean isRequired(String name){
601                return isRequired(fields.get(name));
602        }
603}