001package gu.sql2java;
002
003import static com.google.common.base.Preconditions.*;
004import static com.google.common.base.MoreObjects.*;
005import static gu.sql2java.NameUtilities.*;
006import java.lang.reflect.Method;
007import java.util.Arrays;
008import java.util.Collection;
009import java.util.Collections;
010import java.util.Comparator;
011import java.util.Iterator;
012import java.util.List;
013import java.util.Map;
014import java.util.Map.Entry;
015import java.util.ServiceLoader;
016import java.util.concurrent.ExecutionException;
017
018import com.google.common.base.Function;
019import com.google.common.base.Joiner;
020import com.google.common.base.Predicate;
021import com.google.common.base.Strings;
022import com.google.common.base.Throwables;
023import com.google.common.cache.CacheBuilder;
024import com.google.common.cache.CacheLoader;
025import com.google.common.cache.LoadingCache;
026import com.google.common.collect.Collections2;
027import com.google.common.collect.ImmutableBiMap;
028import com.google.common.collect.ImmutableList;
029import com.google.common.collect.ImmutableMap;
030import com.google.common.collect.Iterables;
031import com.google.common.collect.Lists;
032import com.google.common.collect.Maps;
033import com.google.common.collect.Ordering;
034import com.google.common.primitives.Ints;
035import com.google.common.util.concurrent.UncheckedExecutionException;
036
037/**
038 * meta data used to define a table
039 * @author guyadong
040 *
041 */
042public class RowMetaData implements IRowMetaData{
043        protected static final String UNKNOW_TABLENAME="UNKNOWN";
044        protected static final String UNKNOW_TABLETYPE="UNKNOWN";
045        public final String tablename;
046        public final String tableType;
047        public final Class<? extends BaseBean> beanType;
048        public final ImmutableList<String> columnNames;
049        public final String columnFields;
050        public final String columnFullFields;
051        public final ImmutableList<String> columnJavaNames;
052        public final ImmutableList<Method> getterMethods;
053        public final ImmutableList<Method> setterMethods;
054        public final ImmutableList<Class<?>> columnTypes;
055        private final ImmutableMap<String, Integer> nameIndexsMap;
056        private final ImmutableMap<String, Integer> javaNameIndexsMap;
057        public final int[] defaultColumnIdList;
058        public final int[] sqlTypes;
059        public final ImmutableMap<String, Class<?>> typesMap;
060
061        public final int columnCount;
062        public final int[] primaryKeyIds;
063        public final String[] primaryKeyNames;
064        public final int primaryKeyCount;
065        /** lazy load */
066        private volatile ImmutableMap<String, Object[]> junctionTablePkMap;
067        private final Map<String, String> junctionTablePkStrMap;
068        public final Class<?> lockColumnType;
069        public final String lockColumnName;
070        /**
071         * tablename-ForeignKeyMetaData map
072         */
073        public final ImmutableMap<String,ForeignKeyMetaData> foreignKeys;
074        /**
075         * universal name-ForeignKeyMetaData map
076         */
077        public final ImmutableMap<String, ForeignKeyMetaData> foreignKeysRn;
078        public final ImmutableMap<String, IndexMetaData> indices;
079        public final ImmutableMap<String, IndexMetaData> indicesRn;
080        public final Function<String, Integer> COLUMNID_FUN = new Function<String, Integer>(){
081
082                @Override
083                public Integer apply(String input) {
084                        return columnIDOf(input);
085                }};
086        public final Function<Integer,String> COLUMNNAME_FUN = new Function<Integer,String>(){
087                
088                @Override
089                public String apply(Integer input) {
090                        return columnNameOf(input);
091                }};
092        /** lazy load */
093        private volatile ImmutableList<ForeignKeyMetaData> importedKeys;
094        /** lazy load */
095        private volatile ImmutableMap<String, ForeignKeyMetaData> importKeysMap;
096        public final int autoincrementColumnId;
097        protected RowMetaData(
098                        String tablename,
099                        String tableType,
100                        Class<? extends BaseBean> beanType, 
101                        List<String> columnNames,
102                        List<String> columnJavaNames, 
103                        List<String> getters,
104                        List<String> setters, 
105                        Class<?>[] columnTypes, 
106                        int[] sqlTypes, 
107                        List<String> primaryKeyNames, 
108                        Map<String, String> junctionTablePkMap, 
109                        Class<?> lockColumnType, 
110                        String lockColumnName, 
111                        List<String> foreignKeys, 
112                        List<String> indices, 
113                        String autoincrement) {
114                columnJavaNames = firstNonNull(columnJavaNames,Collections.<String>emptyList());
115                getters = firstNonNull(getters,Collections.<String>emptyList());
116                setters = firstNonNull(setters,Collections.<String>emptyList());          
117                this.junctionTablePkStrMap = firstNonNull(junctionTablePkMap, Collections.<String,String>emptyMap());
118                primaryKeyNames = firstNonNull(primaryKeyNames, Collections.<String>emptyList());
119                foreignKeys = firstNonNull(foreignKeys, Collections.<String>emptyList());
120                indices = firstNonNull(indices, Collections.<String>emptyList());
121                autoincrement = firstNonNull(autoincrement, "");
122                this.tablename = checkNotNull(tablename,"tablename is null");
123                this.tableType = checkNotNull(tableType,"tableType is null");
124                this.beanType = checkNotNull(beanType,"beanType is null");
125                this.columnNames = ImmutableList.copyOf(checkNotNull(columnNames,"columnNames is null"));
126                this.columnJavaNames = ImmutableList.copyOf(columnJavaNames);
127                this.columnTypes = ImmutableList.copyOf(checkNotNull(columnTypes,"columnTypes is null"));
128                this.sqlTypes = Arrays.copyOf(checkNotNull(sqlTypes,"sqlTypes is null"),sqlTypes.length);
129                checkArgument(this.columnNames.size() == this.columnTypes.size() && this.columnTypes.size() == this.sqlTypes.length,
130                                "MISMATCH LENGTH for input list");
131                checkArgument(this.columnJavaNames.isEmpty() || this.columnJavaNames.size() == this.sqlTypes.length,
132                                "MISMATCH LENGTH for columnJavaNames");
133                checkArgument(getters.isEmpty() || getters.size() == this.sqlTypes.length,
134                                "MISMATCH LENGTH for getters");
135                checkArgument(setters.isEmpty() || setters.size() == this.sqlTypes.length,
136                                "MISMATCH LENGTH for setters");
137                
138                ImmutableMap.Builder<String, Integer> nameIndexBuilder = ImmutableMap.builder();
139                ImmutableMap.Builder<String, Integer> javaNameIndexBuilder = ImmutableMap.builder();
140                ImmutableMap.Builder<String, Class<?>> nameTypeBuilder = ImmutableMap.builder();
141                this.defaultColumnIdList =  new int[sqlTypes.length];
142                for(int i = 0; i < sqlTypes.length; ++i){
143                        defaultColumnIdList[i] = i;
144                        nameIndexBuilder.put(columnNames.get(i), i);
145                        nameTypeBuilder.put(columnNames.get(i), columnTypes[i]);
146                        if(!columnJavaNames.isEmpty()){
147                                javaNameIndexBuilder.put(columnJavaNames.get(i), i);
148                        }
149                }
150                columnFields = Joiner.on(",").join(columnNames);
151                columnFullFields = Joiner.on(",").join(Lists.transform(columnNames, new Function<String,String>(){
152
153                        @Override
154                        public String apply(String input) {
155                                if("UNKNOWN".equals(RowMetaData.this.tableType)){
156                                        return input;
157                                }
158                                return RowMetaData.this.tablename + "." + input;
159                        }}));
160                        
161                this.nameIndexsMap = nameIndexBuilder.build();
162                this.autoincrementColumnId = firstNonNull(nameIndexsMap.get(autoincrement), -1).intValue();
163
164                this.javaNameIndexsMap = javaNameIndexBuilder.build();
165                this.typesMap = nameTypeBuilder.build();
166                this.columnCount = sqlTypes.length;
167                this.primaryKeyNames = primaryKeyNames.toArray(new String[0]);
168                
169                this.primaryKeyCount = primaryKeyNames.size();
170                this.primaryKeyIds = new int[primaryKeyNames.size()];
171                for(int i = 0 ; i < primaryKeyIds.length; ++i){
172                        String name = primaryKeyNames.get(i);
173                        checkArgument(primaryKeyIds[i]>=0,"INVALID primary key name %s",name);
174                        primaryKeyIds[i] = columnIDOf(name);
175                }
176                this.lockColumnType = lockColumnType;
177                this.lockColumnName = lockColumnName;
178                ImmutableMap.Builder<String,ForeignKeyMetaData> fkBuilder = ImmutableMap.builder();
179                ImmutableMap.Builder<String,ForeignKeyMetaData> fkRnameBuilder = ImmutableMap.builder();
180                for(String fk:foreignKeys){
181                        ForeignKeyMetaData data = new ForeignKeyMetaData(fk, tablename);
182                        fkBuilder.put(data.name,data);
183                        fkRnameBuilder.put(toCamelCase(data.readableName, true),data);
184                }
185                this.foreignKeys = fkBuilder.build();
186                this.foreignKeysRn = fkRnameBuilder.build();
187                ImmutableMap.Builder<String,IndexMetaData> indexBuilder = ImmutableMap.builder();
188                ImmutableMap.Builder<String,IndexMetaData> indexRnameBuilder = ImmutableMap.builder();
189                for(String fk:indices){
190                        IndexMetaData data = new IndexMetaData(fk, tablename);
191                        indexBuilder.put(data.name,data);
192                        indexRnameBuilder.put(data.readableName,data);
193                }
194                this.indices = indexBuilder.build();
195                this.indicesRn = indexBuilder.build();
196                ImmutableList.Builder<Method> getterMethodBuilder = ImmutableList.builder();
197                ImmutableList.Builder<Method> setterMethodBuilder = ImmutableList.builder();
198
199                for(int i = 0; i < sqlTypes.length; ++i){
200                        try {
201                                if(!getters.isEmpty()){
202                                        getterMethodBuilder.add(beanType.getMethod(getters.get(i)));
203                                }
204                                if(!setters.isEmpty()){
205                                        setterMethodBuilder.add(beanType.getMethod(setters.get(i), columnTypes[i]));
206                                }
207                        } catch (Exception e) {
208                                Throwables.throwIfUnchecked(e);
209                                throw new RuntimeException(e);
210                        }
211                }
212
213                this.getterMethods = getterMethodBuilder.build();
214                this.setterMethods = setterMethodBuilder.build();
215        }
216        /**
217         * return column name specified by column id
218         * @param columnId column id
219         * @return column name or null if columnId is invalid
220         */
221        public String columnNameOf(int columnId){
222            try{
223                return columnNames.get(columnId);
224            } catch(IndexOutOfBoundsException e){
225                return null;
226            }
227        }
228        /**
229         * return column full name(with table name,such as tablename.columnname) specified by column id
230         * @param columnId column id
231         * @return column full name or null if columnId is invalid
232         */
233        public String fullNameOf(int columnId){
234            try{
235                if(tablename.startsWith(UNKNOW_TABLENAME)){
236                        return columnNames.get(columnId);
237                }
238                return tablename + "." + columnNames.get(columnId);
239            } catch(IndexOutOfBoundsException e){
240                return null;
241            }
242        }
243    /**
244     * return column ordinal id(base 0) specified by column name
245     * @param column column name or full name,or java field name
246     * @return column ordinal id(base 0) or -1 if column name is invalid
247     */
248    public final int columnIDOf(String column){
249        if(null != column){
250                String prefix = tablename + ".";
251                if(column.startsWith(prefix)){
252                        column = column.substring(prefix.length());
253                }
254                return firstNonNull(nameIndexsMap.get(column), 
255                                firstNonNull(javaNameIndexsMap.get(column), -1));
256        }
257        return -1;
258        }
259    
260    /**
261     * return column ordinal id(base 0) specified by column names
262     * @param columns array of column name or full name,or java field name
263     * @return array of column ordinal id(base 0) or empty array  if columns is null
264     * @see #columnIDOf(String)
265     */
266    public final int[] columnIDsOf(String... columns){
267                return null == columns ? new int[0] : Ints.toArray(Lists.transform(Arrays.asList(columns), COLUMNID_FUN));
268        }
269    /**
270     * return column ordinal id(base 0) specified by column names
271     * @param columns collection of column name or full name,or java field name
272     * @return array of column ordinal id(base 0) or empty array  if columns is null
273     * @see #columnIDOf(String)
274     */
275    public final int[] columnIDsOf(Collection<String> columns){
276                return null == columns ? new int[0] : Ints.toArray(Collections2.transform(columns, COLUMNID_FUN));
277        }
278    /**
279         * return column names by column names
280         * @param columnIds array of column id
281         * @return array of column name or empty array  if columnIds is null
282         * @see #columnNameOf(int)
283         */
284        public final List<String> columnNamesOf(int... columnIds){
285                return null == columnIds ? Collections.<String>emptyList() : Lists.transform(Ints.asList(columnIds), COLUMNNAME_FUN);
286        }
287        /**
288     * @param columnId column id
289     * @return java type of column,or NULL if columnId is invalid 
290     */
291        public Class<?> columnTypeOf(int columnId){
292            try{
293                return columnTypes.get(columnId);
294            } catch(IndexOutOfBoundsException e){
295                return null;
296            }
297        }
298        /**
299         * @param column column name
300         * @return java type of column,or NULL if column is invalid 
301         */
302        public Class<?> columnTypeOf(String column){
303                try{
304                        return columnTypes.get(columnIDOf(column));
305                } catch(IndexOutOfBoundsException e){
306                        return null;
307                }
308        }
309        
310    /**
311     * @param columnId column id
312     * @return SQL type of column,or throw {@link IllegalArgumentException} if columnId is invalid
313     * @see  java.sql.Types
314     */
315        public int sqlTypeOf(int columnId){
316                try{
317                        return sqlTypes[columnId];
318                } catch(IndexOutOfBoundsException e){
319                        throw new IllegalArgumentException(String.format("INVALID columnID %d",columnId));
320                }
321        }
322        
323        /**
324         * lazy load
325         */
326        private final LoadingCache<String,Boolean> checkLinkedTableCache = CacheBuilder.newBuilder().build(
327                        new CacheLoader<String,Boolean>(){
328
329                                @Override
330                                public Boolean load(final String tablename) throws Exception {
331                                        return Iterables.tryFind(getJunctionTablePkMap().values(), new Predicate<Object[]>() {
332
333                                                @Override
334                                                public boolean apply(Object[] input) {
335                                                        return tablename.equals(input[0]);
336                                                }
337                                        }).isPresent();
338                                }});
339        
340        /**
341         * check if the table specified by tablename is linked table of current table  
342         * @param tablename
343         * @return true if be linked table
344         */
345        public boolean isLinkedTable(String tablename){
346                try {
347                        return checkLinkedTableCache.get(tablename);
348                } catch (ExecutionException e) {
349                        Throwables.throwIfUnchecked(e.getCause());
350                        throw new RuntimeException(e);
351                }
352        }
353        /**
354         * @return junctionTablePkMap
355         */
356        public ImmutableMap<String, Object[]> getJunctionTablePkMap() {
357                if(junctionTablePkMap == null){
358                        synchronized (this) {
359                                if(junctionTablePkMap == null){
360                                        ImmutableMap.Builder<String, Object[]> builder = ImmutableMap.builder();
361                                        for(Entry<String, String> entry:junctionTablePkStrMap.entrySet()){
362                                                String[] values = entry.getValue().split("\\.");
363                                                String foreignTable = values[0];
364                                                int columnId = getMedaData(foreignTable).columnIDOf(values[1]);
365                                                checkArgument(columnId >=0,"INVALID foreign key description %s", entry.getValue());
366                                                builder.put(entry.getKey(), new Object[]{foreignTable,columnId});
367                                        }
368                                        this.junctionTablePkMap = builder.build();
369                                }
370                        }
371                }
372                return junctionTablePkMap;
373        }
374        
375        /**
376         * lazy load
377         */
378        private final LoadingCache<String,Map<String, String>> junctionMapCache = CacheBuilder.newBuilder().build(
379                        new CacheLoader<String,Map<String, String>>(){
380
381                                @Override
382                                public Map<String, String> load(final String linkedTableName) throws Exception {
383                                        Map<String, Object[]> m = Maps.filterValues(getJunctionTablePkMap(), new Predicate<Object[]>(){
384
385                                                @Override
386                                                public boolean apply(Object[] input) {
387                                                        return input[0].equals(linkedTableName);
388                                                }});
389                                        return Maps.transformValues(m, new Function<Object[],String>() {
390
391                                                @Override
392                                                public String apply(Object[] input) {
393                                                        return getMedaData((String)input[0]).fullNameOf((Integer)input[1]);
394                                                }
395                                        });
396                                }});
397        
398        public Map<String, String> junctionMapOf(String linkedTableName){
399                try {
400                        return junctionMapCache.get(linkedTableName);
401                } catch (ExecutionException e) {
402                        Throwables.throwIfUnchecked(e.getCause());
403                        throw new RuntimeException(e);
404                }
405        }
406        /** lazy load */
407        private final LoadingCache<String,ImmutableBiMap<Integer,Integer>> foreignKeyIdCache = CacheBuilder.newBuilder().build(
408                        new CacheLoader<String, ImmutableBiMap<Integer,Integer>>(){
409
410                        @Override
411                        public ImmutableBiMap<Integer, Integer> load(String fkName) throws Exception {
412                                ForeignKeyMetaData foreignkey = foreignKeys.get(fkName);
413                                checkArgument(foreignkey != null,"INVALID foreign key name:%s",fkName);
414                                ImmutableBiMap.Builder<Integer, Integer> builder = ImmutableBiMap.builder();
415                                for(Entry<String, String> entry:foreignkey.columnMaps.entrySet()){
416                                        builder.put(columnIDOf(entry.getKey()), getMedaData(foreignkey.foreignTable).columnIDOf(entry.getValue()));
417                                }
418                                return builder.build();
419                        }});
420        
421        /**
422         * 
423         * @param fkName foreign key name
424         * @return map of column id TO foreign table column id
425         */
426        public ImmutableBiMap<Integer, Integer> foreignKeyIdMapOf(String fkName){
427                try {
428                        return foreignKeyIdCache.get(fkName);
429                } catch (ExecutionException e) {
430                        Throwables.throwIfUnchecked(e.getCause());
431                        throw new RuntimeException(e);
432                }
433        }
434        
435        /**
436         * lazy load
437         */
438        private final LoadingCache<String,int[]> foreignKeyIdArrayCache = CacheBuilder.newBuilder().build(
439                        new CacheLoader<String,int[]>(){
440
441                                @Override
442                                public int[] load(String fkName) throws Exception {
443                                        ForeignKeyMetaData foreignkey = checkNotNull(foreignKeys.get(fkName),"INVALID foreign key name:%s",fkName);
444                                        return foreignkey.foreignKeyIdArray(COLUMNID_FUN);
445                                }});
446        
447        public int[] foreignKeyIdArrayOf(String fkName){
448                try {
449                        return foreignKeyIdArrayCache.get(fkName);
450                } catch (ExecutionException | UncheckedExecutionException e) {
451                        Throwables.throwIfUnchecked(e.getCause());
452                        throw new RuntimeException(e);
453                }
454        }
455        public List<ForeignKeyMetaData> foreignKeysOf(final String foreignTable){
456                Iterable<ForeignKeyMetaData> found = Iterables.filter(foreignKeys.values(), 
457                                new Predicate<ForeignKeyMetaData>(){
458                                        @Override
459                                        public boolean apply(ForeignKeyMetaData input) {
460                                                return input.foreignTable.equals(foreignTable);
461                                        }});
462                return Lists.newArrayList(found);
463        }
464        
465        public ImmutableList<ForeignKeyMetaData> getImportedKeys(){
466                if(this.importedKeys == null){
467                        synchronized (this) {
468                                if(this.importedKeys == null){
469                                        ImmutableList.Builder<ForeignKeyMetaData> builder = ImmutableList.builder();
470                                        for(RowMetaData metaData:tableMetadata.values()){
471                                                builder.addAll(Iterables.filter(metaData.foreignKeys.values(), new Predicate<ForeignKeyMetaData>() {
472
473                                                        @Override
474                                                        public boolean apply(ForeignKeyMetaData input) {
475                                                                return input.foreignTable.equals(tablename);
476                                                        }
477                                                }));
478                                        }
479                                        this.importedKeys = builder.build();
480                                }
481                        }
482                }
483                return this.importedKeys;
484        }
485        
486        public ForeignKeyMetaData getImportedKey(String fkName){
487                if(this.importKeysMap == null){
488                        synchronized (this) {
489                                if(this.importKeysMap == null){
490                                        this.importKeysMap = Maps.uniqueIndex(getImportedKeys(), new Function<ForeignKeyMetaData, String>() {
491
492                                                @Override
493                                                public String apply(ForeignKeyMetaData input) {
494                                                        return input.name;
495                                                }
496                                        });
497                                }
498                        }
499                }
500                return checkNotNull(importKeysMap.get(fkName),"invalid ikIndex %s",fkName);
501        }
502        
503        private volatile ImmutableList<ForeignKeyMetaData> foreignKeysForListeners;
504        /**
505         * @return 返回 所有需要输出foreign key listener的 {@link ForeignKeyMetaData}对象
506         */
507        public ImmutableList<ForeignKeyMetaData> getForeignKeysForListener(){
508                // double check
509                if(foreignKeysForListeners == null){
510                        synchronized (this) {
511                                if(foreignKeysForListeners == null){
512                                        Collection<ForeignKeyMetaData> c = Maps.filterEntries(foreignKeys, 
513                                                        new Predicate<Entry<String, ForeignKeyMetaData>>(){
514                                                                @Override
515                                                                public boolean apply(Entry<String, ForeignKeyMetaData> input) {
516                                                                        ForeignKeyMetaData fk = input.getValue();
517                                                                        return fk.updateRule.isNoAction() 
518                                                                                        && !Strings.isNullOrEmpty(fk.deleteRule.eventOfDeleteRule);
519                                                                }}).values();
520                                        // 排序输出
521                                        foreignKeysForListeners = ImmutableList.copyOf(Ordering.natural().onResultOf(new Function<ForeignKeyMetaData,String>(){
522                                                @Override
523                                                public String apply(ForeignKeyMetaData input) {
524                                                        return input.name;
525                                                }}).sortedCopy(c));
526                                }
527                        }
528                }
529                return foreignKeysForListeners;
530        }
531        private volatile ImmutableMap<String, IndexMetaData>  uniqueIndeices;
532        public ImmutableMap<String, IndexMetaData> getUniqueIndices(){
533                // double check
534                if(uniqueIndeices == null){
535                        synchronized (this) {
536                                if(uniqueIndeices == null){
537                                        uniqueIndeices = ImmutableMap.copyOf(Maps.filterValues(indices, IndexMetaData.UNIQUE_FILTER));
538                                }
539                        }
540                }
541                return uniqueIndeices;
542        } 
543
544        public IndexMetaData getIndexChecked(String indexName){
545                return checkNotNull(getUniqueIndices().get(indexName),"INVALID indexName %s",indexName);
546        }
547        /**
548         * lazy load
549         */
550        private final LoadingCache<String,int[]> indexKeyIdArrayCache = CacheBuilder.newBuilder().build(
551                        new CacheLoader<String,int[]>(){
552                                @Override
553                                public int[] load(String indexName) throws Exception {
554                                        return getIndexChecked(indexName).getColumnIds(COLUMNID_FUN);
555                                }});
556        public int[] indexIdArray(String indexName){
557                try {
558                        return indexKeyIdArrayCache.get(indexName);
559                } catch (ExecutionException | UncheckedExecutionException e) {
560                if(null != e.getCause()){
561                        Throwables.throwIfUnchecked(e.getCause());
562                        throw new RuntimeException(e.getCause());
563                }
564                Throwables.throwIfUnchecked(e);
565                throw new RuntimeException(e);
566                }
567        }
568    private class RowComparator<B extends BaseBean> implements Comparator<B> {
569        /**
570         * Holds the column id on which the comparison is performed.
571         */
572        private final int columnId;
573        /**
574         * Value that will contain the information about the order of the sort: normal or reversal.
575         */
576        private final boolean bReverse;
577
578        private RowComparator(int columnId, boolean bReverse) {
579                checkArgument(columnTypeOf(columnId) != null,"INVALID column id %s",columnId);
580                checkArgument(Comparable.class.isAssignableFrom(columnTypeOf(columnId)),
581                                "type of column %s for the field is not supported Comparable",columnNames.get(columnId));
582                this.columnId = columnId;
583                this.bReverse = bReverse;
584        }
585
586        @SuppressWarnings("unchecked")
587        @Override
588        public int compare(B o1, B o2) {
589                int iReturn = 0;
590                Object v1= o1.getValue(columnId);
591                Object v2= o2.getValue(columnId);
592                if(v1 ==null && v2 !=null){
593                        iReturn = -1;
594                }else if(v1 ==null && v2 ==null){
595                iReturn = 0;
596            }else if(v1 !=null && v2 ==null){
597                iReturn = 1;
598            }else{
599                iReturn = ((Comparable<Object>)v1).compareTo(v2);
600            }
601                return bReverse ? (-1 * iReturn) : iReturn;
602        }
603    }
604    
605    public <B extends BaseBean> Comparator<B> comparatorOf(int columnId,boolean bReverse){
606        return new RowComparator<B>(columnId,bReverse);
607    }
608    
609        private static final ImmutableMap<String, RowMetaData> tableMetadata = loadRowMetaData(); 
610        private static final ImmutableMap<Class<?>, RowMetaData> beanTypeMetadata = Maps.uniqueIndex(tableMetadata.values(), 
611                        new Function<RowMetaData,Class<?>>(){
612                
613                        @Override
614                        public Class<?> apply(RowMetaData input) {
615                                return input.beanType;
616                        }});; 
617
618        /**
619         * SPI(Service Provider Interface)机制加载 {@link IRowMetaData}所有实例
620         * @return 表名和 {@link RowMetaData}实例的映射对象 
621         */
622        private static ImmutableMap<String, RowMetaData> loadRowMetaData() {              
623                ServiceLoader<IRowMetaData> providers = ServiceLoader.load(IRowMetaData.class);
624                Iterator<IRowMetaData> itor = providers.iterator();
625                IRowMetaData instance;
626                ImmutableMap.Builder<String, RowMetaData> builder = ImmutableMap.builder();
627                while(itor.hasNext()){
628                        instance = itor.next();
629                        if(instance instanceof RowMetaData){
630                                RowMetaData rowMetaData = (RowMetaData)instance;
631                                builder.put(rowMetaData.tablename, rowMetaData);
632                        }
633                }
634                return builder.build();
635        }
636        /**
637         * 根据表名返回对应的 {@link RowMetaData}实例
638         * @param tablename 表名
639         * @return {@link RowMetaData}实例,找不到时抛出异常
640         */
641        public static final RowMetaData getMedaData(String tablename) {
642                RowMetaData metaData =  tableMetadata.get(tablename);
643                return checkNotNull(metaData,"INVALID TABLE NAME %s",tablename);
644        }
645        /**
646         * 根据beanType返回对应的 {@link RowMetaData}实例
647         * @param beanType 表名
648         * @return {@link RowMetaData}实例,找不到时抛出异常
649         */
650        public static final RowMetaData getMedaData(Class<?> beanType) {
651                RowMetaData metaData =  beanTypeMetadata.get(beanType);
652                return checkNotNull(metaData,"INVALID bean type %s",beanType);
653        }
654        
655        
656        @Override
657        public int hashCode() {
658                final int prime = 31;
659                int result = 1;
660                result = prime * result + ((columnNames == null) ? 0 : columnNames.hashCode());
661                result = prime * result + ((tableType == null) ? 0 : tableType.hashCode());
662                result = prime * result + ((tablename == null) ? 0 : tablename.hashCode());
663                return result;
664        }
665
666        @Override
667        public boolean equals(Object obj) {
668                if (this == obj) {
669                        return true;
670                }
671                if (obj == null) {
672                        return false;
673                }
674                if (!(obj instanceof RowMetaData)) {
675                        return false;
676                }
677                RowMetaData other = (RowMetaData) obj;
678                if (columnNames == null) {
679                        if (other.columnNames != null) {
680                                return false;
681                        }
682                } else if (!columnNames.equals(other.columnNames)) {
683                        return false;
684                }
685                if (tableType == null) {
686                        if (other.tableType != null) {
687                                return false;
688                        }
689                } else if (!tableType.equals(other.tableType)) {
690                        return false;
691                }
692                if (tablename == null) {
693                        if (other.tablename != null) {
694                                return false;
695                        }
696                } else if (!tablename.equals(other.tablename)) {
697                        return false;
698                }
699                return true;
700        }
701        @Override
702        public String toString() {
703                StringBuilder builder = new StringBuilder();
704                builder.append("RowMetaData [tablename=");
705                builder.append(tablename);
706                builder.append(", tableType=");
707                builder.append(tableType);
708                builder.append(", columnNames=");
709                builder.append(columnNames);
710                builder.append("]");
711                return builder.toString();
712        }
713
714}