001package net.gdface.codegen.thrift;
002
003import org.apache.commons.cli.CommandLine;
004import org.apache.commons.cli.Options;
005import org.apache.commons.cli.ParseException;
006import org.apache.commons.configuration2.Configuration;
007import org.apache.commons.configuration2.PropertiesConfiguration;
008import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
009import org.apache.commons.configuration2.io.FileHandler;
010
011import static com.google.common.base.Preconditions.checkArgument;
012import static com.google.common.base.Preconditions.checkState;
013
014import com.google.common.base.Strings;
015import com.google.common.base.Throwables;
016import com.google.common.collect.ImmutableMap;
017import com.google.common.collect.Lists;
018import com.google.common.collect.Maps;
019import com.google.common.collect.Sets;
020
021import net.gdface.codegen.generator.CodeWriter;
022import net.gdface.codegen.generator.CxxCodeWriter;
023import net.gdface.codegen.generator.GeneratorConfiguration;
024import net.gdface.codegen.generator.JavaCodeWriter;
025import net.gdface.utils.ConditionChecks;
026import net.gdface.utils.MiscellaneousUtils;
027import net.gdface.utils.SimpleLog;
028
029import java.io.File;
030import java.util.Collections;
031import java.util.List;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.Set;
035
036/**
037 * decorator生成器参数对象
038 * @author guyadong
039 *
040 */
041public class ThriftServiceDecoratorConfiguration extends GeneratorConfiguration 
042                                        implements ThriftConstants{
043        private static final String ENCODING = "UTF-8";
044        /**
045         * 任务类型定义
046         * @author guyadong
047         *
048         */
049        public static enum TaskType{            
050                /** 生成thrift service代码 */SERVICE("service", true),
051                /** 生成thrift/swift client代码 */CLIENT("client", false),
052                /** 生成基于Microsoft/thrifty client代码 */CLIENT_THRIFTY("client_thrifty", false),
053                /** 生成eRPC代理服务代码 */ERPC_PROXY("erpc_proxy", false);
054                /** 对应的模板文件路径前缀 */
055                public final String folder;
056                /** 是否转换引用类型 */
057                public final boolean castReferType;
058                private TaskType(String folder, boolean castReferType){
059                        this.folder = folder;
060                        this.castReferType = castReferType;
061                }
062        };
063        /**
064         * 生成代码语言类型定义
065         * @author guyadong
066         *
067         */
068        public static enum LanguageType{
069                /** 生成thrift java代码 */JAVA(""),
070                /** 生成thrift c++代码 */CPP("_cpp"),
071                /** 生成thrift c++代码 */C_GLIB("_c_glib")
072                ;
073                private static LanguageType current = null;
074                /** 模板根文件夹名结尾 */
075                public final String postfix;
076                private LanguageType(String postfix){
077                        this.postfix = postfix;
078                }
079                /** 
080                 * 返回对应的 {@link CodeWriter}实例
081                 * @param outputFolder
082                 */
083                public CodeWriter getCodeWriter(File outputFolder){
084                        switch(this){
085                        case JAVA:
086                                return new JavaCodeWriter(outputFolder);
087                        case CPP:
088                                return new CxxCodeWriter(outputFolder);
089                        case C_GLIB:
090                                return new CxxCodeWriter(outputFolder);
091                        default:
092                                        throw new UnsupportedOperationException("unsupported language type:" + this.name());
093                        }
094                }
095                public static LanguageType getCurrent() {
096                        return current;
097                }
098                public synchronized static void setCurrent(LanguageType current) {
099                        checkState(null == LanguageType.current 
100                                        || LanguageType.current.equals(current),
101                                        "LanguageType.current can be initialized only onece");
102                        LanguageType.current = current;
103                }
104        };
105        public static final String DEFAULT_TEMPLATE_FOLDER = "thrift";
106        public static final String DEFAULT_LANGUAGE = "JAVA";
107        /** refClass 的默认值 */
108        public static final Class<?> DEF_REF_CLASS = Object.class;
109        private static final String NO_REF_CLASS = "";
110    private static final Map<String, String> defaultExcludeFields = ImmutableMap.of("gu.sql2java.BaseBean","modified,initialized");
111    /** interface class -- reference class */
112        private Map<Class<?>, Class<?>> interfaceClasses;
113        /** interface class -- thrift service class */
114        private Map<Class<?>, Class<?>> thriftServiceClasses;
115        private TaskType taskType;
116        private LanguageType languageType;
117        private String thriftClientPackage;
118        private String sourcepath;
119        private String classpath;
120        private final PropertiesConfiguration config = new PropertiesConfiguration();
121        private Set<String> reqiredTags = Collections.emptySet();
122        private Set<String> commonTypes = Collections.emptySet();
123        private String programName;
124        private String portPrefix;
125        private Map<Class<?>, List<String>> excludeMethods = Maps.newHashMap();
126        private Map<Class<?>, List<String>> includeMethods = Maps.newHashMap();
127
128        private final Map<Class<?>, List<String>> excludeFields;
129        private int erpcForwardPort;
130        private int erpcProxyPort;
131    private int defaultMaxLength=256;
132    private int errmsgMaxLength=256;
133        private int binaryOutputSize=256;
134        public static final ThriftServiceDecoratorConfiguration INSTANCE = new ThriftServiceDecoratorConfiguration();
135        private ThriftServiceDecoratorConfiguration() {
136                super();
137                // 指定模板文件夹
138                this.defaultValue.setProperty(TEMPLATE_FOLDER_OPTION_LONG, DEFAULT_TEMPLATE_FOLDER);
139                // 指定refClass的默认值,避免默认值为{@code null}
140                this.defaultValue.setProperty(REFERENCE_CLASS_OPTION_LONG,NO_REF_CLASS);
141                // 指定refClass的默认值,避免默认值为{@code null}
142                this.defaultValue.setProperty(THRIFT_CLIENT_PKG_OPTION_LONG,"");
143                // 指定refClass的默认值,避免默认值为{@code null}
144                this.defaultValue.setProperty(LANGUAGE_OPTION_LONG,DEFAULT_LANGUAGE);
145                // 指定CONFIG的默认值,避免默认值为{@code null}
146                this.defaultValue.setProperty(CONFIG_OPTION_LONG,null);
147                // 指定sourcepath的默认值,避免默认值为{@code null}
148                this.defaultValue.setProperty(SOURCE_PREFIX_OPTION_LONG,"");
149                // 指定classpath的默认值,避免默认值为{@code null}
150                this.defaultValue.setProperty(CLASS_PATH_OPTION_LONG,"");
151                // 指定TAGS的默认值,避免默认值为{@code null}
152                this.defaultValue.setProperty(TAGS_OPTION_LONG,"");
153                // 指定COMMON_TYPES的默认值,避免默认值为{@code null}
154                this.defaultValue.setProperty(COMMON_TYPES_OPTION_LONG,"");
155                // 指定programName的默认值,避免默认值为{@code null}
156                this.defaultValue.setProperty(ERPC_PROGRAM_OPTION_LONG,"");
157                // 指定portPrefix的默认值,避免默认值为{@code null}
158                this.defaultValue.setProperty(ERPC_PORT_PREFIX_OPTION_LONG,"");
159                // 指定excludeMethods的默认值,避免默认值为{@code null}
160                this.defaultValue.setProperty(EXCLUDE_METHODS_OPTION_LONG,new String[0]);
161                // 指定includeMethods的默认值,避免默认值为{@code null}
162                this.defaultValue.setProperty(INCLUDE_METHODS_OPTION_LONG,new String[0]);
163                // 指定excludeFields的默认值,避免默认值为{@code null}
164                this.defaultValue.setProperty(EXCLUDE_FIELDS_OPTION_LONG,new String[0]);
165
166                // 指定excludeFields的默认值,避免默认值为{@code null}
167                this.defaultValue.setProperty(THRIFT_SERVICE_CLASS_OPTION_LONG,"");
168
169                excludeFields = Maps.newHashMap();
170                for(Entry<String, String> entry : defaultExcludeFields.entrySet()){
171                        try {
172                                excludeFields.put(Class.forName(entry.getKey()), MiscellaneousUtils.elementsOf(entry.getValue()));
173                        } catch (ClassNotFoundException e) {
174                                SimpleLog.log(e.toString());
175                        }
176                }
177
178                // 指定erpcForwardPort的默认值,避免默认值为{@code null}
179                this.defaultValue.setProperty(ERPC_FORWARD_PORT_OPTION_LONG,0);
180                // 指定erpcProxyPort的默认值,避免默认值为{@code null}
181                this.defaultValue.setProperty(ERPC_PROXY_PORT_OPTION_LONG,0);
182                // 指定defaultMaxLength的默认值,避免默认值为{@code null}
183                this.defaultValue.setProperty(ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG,256);
184                // 指定errmsgMaxLength的默认值,避免默认值为{@code null}
185                this.defaultValue.setProperty(ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG,256);
186                // 指定binaryOutputSize的默认值,避免默认值为{@code null}
187                this.defaultValue.setProperty(ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG,256);
188        }
189
190        @Override
191        public void loadConfig(Options options, CommandLine cmd) throws ParseException {
192                super.loadConfig(options, cmd);
193                try {
194                        List<Class<?>> interfaceList = toClassArray((String) getProperty(INTERFACE_CLASS_OPTION_LONG));
195                        List<Class<?>> refList = toClassArray((String) getProperty(REFERENCE_CLASS_OPTION_LONG));
196                        this.thriftClientPackage = (String)getProperty(THRIFT_CLIENT_PKG_OPTION_LONG);
197                        if(refList.size()>0 && refList.size() != interfaceList.size()){
198                                throw new ParseException("mismatch number interface class and reference class");
199                        }
200                        interfaceClasses = Maps.newLinkedHashMap();
201                        for(int i = 0 ;i < interfaceList.size() ; ++i){
202                                Class<?> key = interfaceList.get(i);
203                                if(interfaceList.get(i)!=DEF_REF_CLASS){
204                                        try{
205                                                Class<?> value = refList.get(i);
206                                                interfaceClasses.put(key, value);
207                                        }catch(IndexOutOfBoundsException e){
208                                                interfaceClasses.put(key, DEF_REF_CLASS);
209                                        }
210                                }
211                        }
212                        if(interfaceList.isEmpty()){
213                                throw new ParseException("NOT FOUND VALID interface class define");
214                        }
215                } catch (ClassNotFoundException e) {
216                        throw new ParseException("ClassNotFoundException:"+e.getMessage());
217                }
218                try{
219                        taskType = TaskType.valueOf((String) getProperty(TASK_TYPE_OPTION_LONG));
220                        if((taskType == TaskType.CLIENT || taskType == TaskType.CLIENT_THRIFTY) && this.thriftClientPackage.isEmpty()){
221                                throw new IllegalArgumentException(String.format("must set param :%s",THRIFT_CLIENT_PKG_OPTION_LONG));
222                        }
223                }catch(IllegalArgumentException e){
224                        throw new ParseException(e.getMessage());
225                }
226                try{
227                        languageType = LanguageType.valueOf((String) getProperty(LANGUAGE_OPTION_LONG));
228                        LanguageType.setCurrent(languageType);
229                }catch(IllegalArgumentException e){
230                        throw new ParseException(e.getMessage());
231                }
232                if(hasProperty(CONFIG_OPTION_LONG)){
233                        File configFile = new File((String) getProperty(CONFIG_OPTION_LONG));
234                        checkArgument(configFile.isFile() && configFile.getName().endsWith(".properties"),
235                                        "%s must be a .properties file",CONFIG_OPTION_LONG);
236                        try {
237                                // 指定文件编码方式,否则properties文件读取中文会是乱码,要求文件编码是UTF-8
238                            FileBasedConfigurationBuilder.setDefaultEncoding(PropertiesConfiguration.class, ENCODING);
239                            new FileHandler(config).load(configFile);
240                        } catch (Exception e) {
241                                Throwables.throwIfUnchecked(e);
242                                throw new RuntimeException(e);
243                        }
244                }
245                sourcepath = getProperty(SOURCE_PREFIX_OPTION_LONG);
246                classpath = getProperty(CLASS_PATH_OPTION_LONG);
247                // 允许 , 做分隔符
248                classpath = classpath.replaceAll(",", File.pathSeparator);
249                // linux下允许用 ;号做分隔符
250                if(File.pathSeparatorChar  != ';'){
251                        classpath = classpath.replaceAll(";", File.pathSeparator);
252                }
253                String tags = getProperty(TAGS_OPTION_LONG);
254                if(!Strings.isNullOrEmpty(tags)){
255                        this.reqiredTags = Sets.newHashSet(MiscellaneousUtils.elementsOf(tags));
256                }
257                String types = getProperty(COMMON_TYPES_OPTION_LONG);
258                if(!Strings.isNullOrEmpty(types)){
259                        this.commonTypes = Sets.newHashSet(MiscellaneousUtils.elementsOf(types));
260                }
261                String program = getProperty(ERPC_PROGRAM_OPTION_LONG);
262                if(Strings.isNullOrEmpty(program)){
263                        ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY),ParseException.class,"must define argument: %s",ERPC_PROGRAM_OPTION_LONG);
264                }else{
265                        this.programName = program;
266                }
267                String prefix = getProperty(ERPC_PORT_PREFIX_OPTION_LONG);
268                if(Strings.isNullOrEmpty(prefix)){
269                        ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY),ParseException.class,"must define argument: %s",ERPC_PORT_PREFIX_OPTION_LONG);
270                }else{
271                        this.portPrefix = prefix;
272                }
273                String[] excludeMethodNames = getProperty(EXCLUDE_METHODS_OPTION_LONG);
274                if(excludeMethodNames != null){
275                        for(String str : excludeMethodNames){
276                                String[] entry = str.split(":");
277                                try {
278                                        List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
279                                        if(!names.isEmpty()){
280                                                excludeMethods.put(Class.forName(entry[0]),names);
281                                        }
282                                } catch (ClassNotFoundException e) {
283                                        SimpleLog.log(e.toString());
284                                }
285                        }
286                }
287                String[] inludeMethodNames = getProperty(INCLUDE_METHODS_OPTION_LONG);
288                if(inludeMethodNames != null){
289                        for(String str : inludeMethodNames){
290                                String[] entry = str.split(":");
291                                try {
292                                        List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
293                                        if(!names.isEmpty()){
294                                                includeMethods.put(Class.forName(entry[0]),names);
295                                        }
296                                } catch (ClassNotFoundException e) {
297                                        SimpleLog.log(e.toString());
298                                }
299                        }
300                }
301                String[] excludeFieldsEntry = getProperty(EXCLUDE_FIELDS_OPTION_LONG);
302                if(excludeFieldsEntry != null){
303                        for(String str : excludeFieldsEntry){
304                                String[] entry = str.split(":");
305                                try {
306                                        List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
307                                        if(!names.isEmpty()){
308                                                excludeFields.put(Class.forName(entry[0]),names);
309                                        }
310                                } catch (ClassNotFoundException e) {
311                                        SimpleLog.log(e.toString());
312                                }
313                        }
314                }
315                
316                try {
317                        List<Class<?>> interfaceList = toClassArray((String) getProperty(INTERFACE_CLASS_OPTION_LONG));
318                        List<Class<?>> tsList = toClassArray((String) getProperty(THRIFT_SERVICE_CLASS_OPTION_LONG));
319                        ImmutableMap.Builder<Class<?>, Class<?>> builer= ImmutableMap.builder();
320                        if(!tsList.isEmpty()){ 
321                                ConditionChecks.checkTrue(tsList.size() == interfaceList.size(), 
322                                                ParseException.class, 
323                                                "mismatch number interface class and thrift service class");
324                                for(int i=0; i<tsList.size(); ++i){
325                                        builer.put(interfaceList.get(i), tsList.get(i));
326                                }
327                        }else{
328                                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY), 
329                                                ParseException.class, 
330                                                "must define argument: %s for ERPC_PROXY task",THRIFT_SERVICE_CLASS_OPTION_LONG);
331                        }
332                        thriftServiceClasses = builer.build();
333                } catch (ClassNotFoundException e) {
334                        throw new ParseException("ClassNotFoundException:"+e.getMessage());
335                }
336
337                erpcForwardPort = ((Number)getProperty(ERPC_FORWARD_PORT_OPTION_LONG)).intValue();
338                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || erpcForwardPort>0, 
339                                ParseException.class, 
340                                "must define argument: %s for ERPC_PROXY task",ERPC_FORWARD_PORT_OPTION_LONG);
341                
342                erpcProxyPort = ((Number)getProperty(ERPC_PROXY_PORT_OPTION_LONG)).intValue();
343                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || erpcProxyPort>0, 
344                                ParseException.class, 
345                                "must define argument: %s for ERPC_PROXY task",ERPC_PROXY_PORT_OPTION_LONG);
346
347                defaultMaxLength = ((Number)getProperty(ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG)).intValue();
348                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || defaultMaxLength>0, 
349                                ParseException.class, 
350                                "must define argument: %s for ERPC_PROXY task",ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG);
351
352                errmsgMaxLength = ((Number)getProperty(ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG)).intValue();
353                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || errmsgMaxLength>0, 
354                                ParseException.class, 
355                                "must define argument: %s for ERPC_PROXY task",ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG);
356                
357                binaryOutputSize = ((Number)getProperty(ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG)).intValue();
358                ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || binaryOutputSize>0, 
359                                ParseException.class, 
360                                "must define argument: %s for ERPC_PROXY task",ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG);
361
362        }
363        
364        private List<Class<?>> toClassArray(String input) throws ClassNotFoundException{
365                String[] classNames = input.split(",");
366                List<Class<?>>result = Lists.newArrayList(); 
367                for(String name:classNames){
368                        result.add(name.isEmpty()? DEF_REF_CLASS : Class.forName(name));
369                }
370                return result;
371        }
372
373        public Map<Class<?>, Class<?>> getInterfaceClasses() {
374                return interfaceClasses;
375        }
376
377        public Map<Class<?>, Class<?>> getThriftServiceClasses() {
378                return thriftServiceClasses;
379        }
380
381        public TaskType getTaskType() {
382                return taskType;
383        }
384
385        public LanguageType getLanguageType() {
386                return languageType;
387        }
388
389        @Override
390        public String getTemplateFolder() {
391                String folder = super.getTemplateFolder();
392                StringBuilder sb = new StringBuilder(folder);           
393                if(DEFAULT_TEMPLATE_FOLDER.equals(folder)){
394                        sb.append(getLanguageType().postfix);   
395                }               
396                sb.append("/").append(getTaskType().folder);
397                return sb.toString();
398        }
399
400        public String getThriftClientPackage() {
401                return thriftClientPackage;
402        }
403        public CodeWriter getCodeWriter(){
404                return this.getLanguageType().getCodeWriter(getOutputLocation());
405        }
406
407        /**
408         * @return config
409         */
410        public Configuration getConfig() {
411                return config;
412        }
413        /**
414         * @return sourcepath
415         */
416        public String getSourcepath() {
417                return sourcepath;
418        }
419
420        /**
421         * @return classpath
422         */
423        public String getClasspath() {
424                return classpath;
425        }
426
427        public Set<String> getReqiredTags() {
428                return reqiredTags;
429        }
430
431        public Set<String> getCommonTypes() {
432                return commonTypes;
433        }
434
435        /**
436         * @return programName
437         */
438        public String getProgramName() {
439                return programName;
440        }
441
442        /**
443         * @return portPrefix
444         */
445        public String getPortPrefix() {
446                return portPrefix;
447        }
448
449        /**
450         * @return excludeMethods
451         */
452        public Map<Class<?>, List<String>> getExcludeMethods() {
453                return excludeMethods;
454        }
455
456        /**
457         * @return includeMethods
458         */
459        public Map<Class<?>, List<String>> getIncludeMethods() {
460                return includeMethods;
461        }
462
463        /**
464         * @return excludeFields
465         */
466        public Map<Class<?>, List<String>> getExcludeFields() {
467                return excludeFields;
468        }
469
470        /**
471         * @return erpcForwardPort
472         */
473        public int getErpcForwardPort() {
474                return erpcForwardPort;
475        }
476
477        /**
478         * @return erpcProxyPort
479         */
480        public int getErpcProxyPort() {
481                return erpcProxyPort;
482        }
483
484        /**
485         * @return defaultMaxLength
486         */
487        public int getDefaultMaxLength() {
488                return defaultMaxLength;
489        }
490
491        /**
492         * @return errmsgMaxLength
493         */
494        public int getErrmsgMaxLength() {
495                return errmsgMaxLength;
496        }
497
498        /**
499         * @return binaryOutputSize
500         */
501        public int getBinaryOutputSize() {
502                return binaryOutputSize;
503        }
504        
505}