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