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