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