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