001package gu.sql2java;
002
003import java.lang.reflect.Array;
004import java.nio.ByteBuffer;
005import java.sql.Connection;
006import java.sql.PreparedStatement;
007import java.sql.ResultSet;
008import java.sql.SQLException;
009import java.sql.Statement;
010import java.util.ArrayList;
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.Iterator;
015import java.util.LinkedHashSet;
016import java.util.LinkedList;
017import java.util.List;
018import java.util.Map;
019import java.util.ServiceLoader;
020import java.util.Map.Entry;
021import java.util.NoSuchElementException;
022import java.util.concurrent.Callable;
023import java.util.concurrent.atomic.AtomicInteger;
024
025import com.google.common.base.Function;
026import com.google.common.base.Joiner;
027import com.google.common.base.MoreObjects;
028import com.google.common.base.Predicate;
029import com.google.common.base.Throwables;
030import com.google.common.collect.ImmutableBiMap;
031import com.google.common.collect.ImmutableList;
032import com.google.common.collect.ImmutableMap;
033import com.google.common.collect.Iterables;
034import com.google.common.collect.Lists;
035import com.google.common.collect.Maps;
036
037import gu.sql2java.ForeignKeyMetaData.ForeignKeyRule;
038import gu.sql2java.Manager.AutoKeyRetrieveType;
039import gu.sql2java.exception.DaoException;
040import gu.sql2java.exception.DataAccessException;
041import gu.sql2java.exception.DataRetrievalException;
042import gu.sql2java.exception.ObjectRetrievalException;
043import gu.sql2java.exception.OptimisticLockingException;
044import gu.sql2java.exception.RuntimeDaoException;
045
046import static com.google.common.base.Preconditions.*;
047import static gu.sql2java.SimpleLog.*;
048
049/**
050 * implementation of {@link TableManager} 
051 * @author guyadong
052 *
053 * @param <B>  java bean type
054 */
055public class BaseTableManager<B extends BaseBean> implements TableManager<B>,Constant{
056    protected final RowMetaData metaData;
057        private final List<DeleteRuleListener<BaseBean>> foreignKeyDeleteListeners;
058        private static boolean debug = false;
059    protected BaseTableManager(String tablename){
060        metaData = RowMetaData.getMedaData(tablename);
061        foreignKeyDeleteListeners = ImmutableList.copyOf(Iterables.transform(metaData.getForeignKeysForListener(),
062                        new Function<ForeignKeyMetaData,DeleteRuleListener<BaseBean>>(){
063
064                        @Override
065                        public DeleteRuleListener<BaseBean> apply(ForeignKeyMetaData input) {
066                                return new DeleteRuleListener<>(input.name);
067                        }}));
068    }
069
070        /**
071     * Creates a new B instance.
072     *
073     * @return the new B instance
074     */
075        @SuppressWarnings("unchecked")
076    protected final B createBean()
077    {
078        try {
079                        return (B) metaData.beanType.newInstance();
080                } catch (Exception e) {
081                        Throwables.throwIfUnchecked(e);
082                        throw new RuntimeException(e);
083                }
084    }
085        /**
086         * Creates a new B instance.
087         * @param primaryValues values of primary keys
088         * @return B instance
089         */
090        protected final B createBean(Object... primaryValues)
091    {
092                checkArgument(primaryValues != null && primaryValues.length== metaData.primaryKeyNames.length,"INVALID primaryValues");
093                B bean = createBean();
094                int[] pkIds = metaData.primaryKeyIds;
095                for(int i=0;i<primaryValues.length;++i){
096                        int columnId = pkIds[i];
097                        Object value = primaryValues[i];
098                        checkArgument(null == value || metaData.columnTypeOf(columnId).isInstance(value),
099                                        "INVALID primkey type for %s,%s required",metaData.columnNameOf(columnId),metaData.columnTypeOf(columnId).getName());
100                        bean.setValue(columnId, value);
101                }
102                return bean;
103    }
104    private static int indexOfFirstNull(Object...objects) {
105                if(objects != null){
106                        for(int i=0;i< objects.length;++i){
107                                if(null == objects[i]){
108                                        return i;
109                                }
110                        }
111                        return -1;
112                }
113                return -2;
114        }
115        /**
116         * @param objects 
117         * @return true if any one of object is null or objects is null 
118         */
119        private static boolean hasNull(Object...objects) {
120                return indexOfFirstNull(objects) != -1;
121        }
122        
123        /**
124         * @param bean
125         * @return true if any primary key of B is null or B is null
126         */
127        private static <T extends BaseBean >boolean hasNullPk(T bean){
128                return (null == bean || hasNull(bean.primaryValues()));
129        }
130        
131        private void prepareAutoincrement( Connection c,PreparedStatement ps, B bean) throws SQLException
132    {
133        if (!bean.isModified(metaData.autoincrementColumnId))
134        {
135            PreparedStatement ps2 = null;
136            ResultSet rs = null;
137            try {
138                if(AutoKeyRetrieveType.auto.equals(getManager().getGeneratedkeyRetrieveType(c))){
139                        rs = ps.getGeneratedKeys();
140                }else{
141                    ps2 = c.prepareStatement(metaData.getGeneratedkeyStatement(c));
142                        rs = ps2.executeQuery();
143                }
144                if(rs.next()) {
145                    setColumnValue(bean, 
146                                metaData.autoincrementColumnId, 
147                                Manager.getObject(rs,1,metaData.columnTypes.get(metaData.autoincrementColumnId)));
148                } else {
149                    throw new IllegalStateException("ATTENTION: Could not retrieve generated key!");
150                }
151            } finally {
152               getManager().close(ps2, rs);
153            }
154        }
155    }
156        
157    //13
158    /**
159     * Insert the B bean into the database.
160     * 
161     * @param bean the B bean to be saved
162     * @return the inserted bean
163     * @throws RuntimeDaoException
164     */
165    protected B insert(final B bean)
166    {
167        // mini checks
168        if (null == bean || !bean.isModified()) {
169            return bean; 
170        }
171        if (!bean.isNew()){
172            return this.update(bean);
173        }
174
175        Connection c = null;
176        PreparedStatement ps = null;
177
178                try
179        {
180            c = this.getConnection();
181            final AutoKeyRetrieveType retrieveType = getManager().getGeneratedkeyRetrieveType(c);
182            //-------------writePreInsert
183            if(metaData.autoincrementColumnId >= 0 
184                        && AutoKeyRetrieveType.before.equals(retrieveType)){
185                        prepareAutoincrement(c, null, bean);
186                }
187            //------------/writePreInsert
188            // listener callback
189            this.listenerContainer.beforeInsert(bean);
190            StringBuilder sql = new StringBuilder("INSERT into " + metaData.tablename + " (");
191            List<String> modifiedList = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate<String>() {
192
193                        @Override
194                        public boolean apply(String input) {
195                                return bean.isModified(input);
196                        }
197                }));
198            String fields=Joiner.on(",").join(modifiedList);
199            // fields
200            sql.append(fields);
201            sql.append(") values (");
202            // values
203            sql.append(fields.replaceAll("[^,]+", "?"));
204            sql.append(")");
205            if(debug){
206                log("insert : " + sql.toString());
207            }
208            if(metaData.autoincrementColumnId >= 0 && AutoKeyRetrieveType.auto.equals(retrieveType)){
209                ps = c.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
210            }else{
211                ps = c.prepareStatement(sql.toString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
212            }
213            
214            fillPreparedStatement(ps, bean, SEARCH_EXACT, true);
215
216            ps.executeUpdate();
217            //------------------writePostInsert
218            if(metaData.autoincrementColumnId >= 0 && !AutoKeyRetrieveType.before.equals(retrieveType)){
219                        prepareAutoincrement(c, ps, bean);
220                }
221            //-------------------/writePostInsert
222            bean.setNew(false);
223            bean.resetIsModified();
224            // listener callback
225            this.listenerContainer.afterInsert(bean);
226            return bean;
227        }
228        catch(SQLException e)
229        {
230            throw new RuntimeDaoException(new DataAccessException(e));
231        }
232        finally
233        {
234            // listener callback
235            this.listenerContainer.done();
236            this.getManager().close(ps);
237            this.freeConnection(c);
238        }
239    }
240    
241    private static final Function<String, String> SQL_FUN1 = new Function<String,String>(){
242
243                @Override
244                public String apply(String input) {
245                        return input + "=?";
246                }};
247    //14
248    /**
249     * Update the B bean record in the database according to the changes.
250     *
251     * @param bean the B bean to be updated
252     * @return the updated bean
253     * @throws RuntimeDaoException
254     */
255    protected B update(final B bean) throws RuntimeDaoException
256    {
257        // mini checks
258        if (null == bean || !bean.isModified()) {
259            return bean;
260        }
261        if (bean.isNew()){
262            return this.insert(bean);
263        }
264
265        Connection c = null;
266        PreparedStatement ps = null;
267
268        try
269        {
270            c = this.getConnection();
271
272            // listener callback
273            this.listenerContainer.beforeUpdate(bean); 
274            Object oldLockValue = null;
275            if(metaData.lockColumnType != null){
276                oldLockValue = bean.getValue(metaData.lockColumnName);
277                        // lockColumnType is String or Long
278                        if(String.class == metaData.lockColumnType){
279                                bean.setValue(metaData.lockColumnName,String.valueOf(System.currentTimeMillis())); 
280                        }else if(Long.class ==metaData.lockColumnType){
281                                bean.setValue(metaData.lockColumnName,System.currentTimeMillis()); 
282                        }else{
283                                throw new RuntimeException("INVALID LOCK COLUMN TYPE:String or Long required");
284                        }
285            }
286            StringBuilder sql = new StringBuilder("UPDATE " + metaData.tablename + " SET ");
287            List<String> modified = Lists.newArrayList(Iterables.filter(metaData.columnNames,new Predicate<String>() {
288
289                        @Override
290                        public boolean apply(String input) {
291                                return bean.isModified(input);
292                        }
293                }));
294            sql.append(Joiner.on(",").join(Lists.transform(modified,SQL_FUN1)));
295
296            sql.append(" WHERE ");
297            sql.append(Joiner.on(" AND ").join(
298                    Lists.transform(Arrays.asList(metaData.primaryKeyNames), SQL_FUN1)));
299            if(metaData.lockColumnType != null){
300                if(metaData.primaryKeyNames.length > 0){
301                        sql.append(" AND ");
302                }
303                checkArgument(metaData.lockColumnName != null, "NOT DEFINED lock column name");
304                sql.append(metaData.lockColumnName + "=?");
305            }
306            if(debug){
307                log("update : " + sql.toString());
308            }
309            ps = c.prepareStatement(sql.toString(),
310                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
311                                    ResultSet.CONCUR_READ_ONLY);
312
313            int dirtyCount = this.fillPreparedStatement(ps, bean, SEARCH_EXACT,true);
314
315            if (dirtyCount == 0) {
316                if(debug){
317                        log("The bean to look is not initialized... do not update.");
318                }
319                return bean;
320            }
321            int[] pkIds = metaData.primaryKeyIds;
322            for(int i = 0; i<pkIds.length; ++i){
323                setPreparedStatement(ps, ++dirtyCount, bean, pkIds[i]);
324            }
325            if(metaData.lockColumnType != null){
326                setPreparedStatement(ps, ++dirtyCount, oldLockValue, metaData.columnIDOf(metaData.lockColumnName));
327            }
328            int rowCount = ps.executeUpdate();
329            if (metaData.lockColumnType != null && rowCount==0) {
330                throw new OptimisticLockingException("sql2java.exception.optimisticlock");
331            }
332            // listener callback
333            this.listenerContainer.afterUpdate(bean); 
334            bean.resetIsModified();
335
336            return bean;
337        }
338        catch(SQLException e)
339        {
340            throw new RuntimeDaoException(new DataAccessException(e));
341        }
342        finally
343        {
344            // listener callback
345            this.listenerContainer.done();
346            this.getManager().close(ps);
347            this.freeConnection(c);
348        }
349    }
350    
351    class ListAction implements TableManager.Action<B> {
352        final List<B> list;
353        public ListAction() {
354            list=new LinkedList<B>();
355        }
356
357        public List<B> getList() {
358            return list;
359        }
360
361        @Override
362        public void call(B bean) {
363            list.add(bean);
364        }
365
366    }
367    
368    protected class CacheWrapper implements Action<B>{
369        private final Action<B> action;
370        private final TableCache<B> cache;
371        public CacheWrapper(Action<B>action,TableCache<B> cache){
372            this.action = action;
373            this.cache = cache;
374        }
375        @Override
376        public void call(B bean) {
377            if(null != action){
378                action.call(bean);
379            }
380            if(null != cache){
381                cache.updateAll(bean);
382            }
383        }
384    }
385    @Override
386    public int countAll()throws RuntimeDaoException{
387        return this.countWhere("");
388    }
389
390    @Override
391    public int countUsingTemplate(B bean)throws RuntimeDaoException{
392        return this.countUsingTemplate(bean, SEARCH_EXACT);
393    }
394
395    @Override
396    public int deleteAll()throws RuntimeDaoException{
397        return this.deleteByWhere("");
398    }
399
400    @Override
401    public B[] loadAll()throws RuntimeDaoException{
402        return this.loadByWhere(null);
403    }
404
405    @Override
406    public int loadAll(TableManager.Action<B> action)throws RuntimeDaoException{
407        return this.loadByWhere(null, action);
408    }
409
410    @SuppressWarnings("unchecked")
411        @Override
412    public B[] loadAll(int startRow, int numRows)throws RuntimeDaoException{
413        return this.loadByWhereAsList(null, null, startRow, numRows).toArray((B[]) Array.newInstance(metaData.beanType, 0));
414    }
415
416    @Override
417    public int loadAll(int startRow, int numRows, TableManager.Action<B> action)throws RuntimeDaoException{
418        return this.loadByWhereForAction(null, null, startRow, numRows, action);
419    }
420
421    @Override
422    public List<B> loadAllAsList()throws RuntimeDaoException{
423        return this.loadUsingTemplateAsList(null,1, -1, SEARCH_EXACT);
424    }
425
426    @Override
427    public List<B> loadAllAsList(int startRow, int numRows)throws RuntimeDaoException{
428        return this.loadUsingTemplateAsList(null, startRow, numRows, SEARCH_EXACT);
429    }
430
431    @Override
432        public B loadByPrimaryKey(B bean)throws RuntimeDaoException{
433                return bean==null ? null : loadByPrimaryKey(bean.primaryValues());
434        }
435
436        @Override
437        public B loadByPrimaryKeyChecked(B bean)throws RuntimeDaoException,ObjectRetrievalException{
438            return loadByPrimaryKeyChecked(checkNotNull(bean,"bean is null").primaryValues());
439        }
440        @Override
441        public final B loadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
442                // {{check parameters
443                if(metaData.primaryKeyNames.length == 0){
444                        throw new UnsupportedOperationException();
445                }
446            String[] pkNames = metaData.primaryKeyNames;
447            int[] pkIds = metaData.primaryKeyIds;
448                if(hasNull(keys)){
449                        throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
450                }
451                checkArgument(keys.length == pkNames.length,
452                                "INVALID ARGUMENT NUM, %s required",pkNames.length);
453            for(int i=0; i<pkNames.length; ++i){
454                Object key = keys[i];
455                Class<?> type = metaData.columnTypeOf(pkIds[i]);
456                checkArgument(type.isAssignableFrom(key.getClass()),
457                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
458            }
459         // }}check parameters
460            return doLoadByPrimaryKeyChecked(keys);
461        }
462        
463        protected B doLoadByPrimaryKeyChecked(Object ...keys)throws RuntimeDaoException,ObjectRetrievalException{
464                if(metaData.primaryKeyNames.length == 0){
465                        throw new UnsupportedOperationException();
466                }
467            String[] pkNames = metaData.primaryKeyNames;
468            int[] pkIds = metaData.primaryKeyIds;
469                if(hasNull(keys)){
470                        throw new ObjectRetrievalException(new NullPointerException("primary key must not be null"));
471                }
472                checkArgument(keys.length == pkNames.length,
473                                "INVALID ARGUMENT NUM, %s required",pkNames.length);
474            for(int i=0; i<pkNames.length; ++i){
475                Object key = keys[i];
476                Class<?> type = metaData.columnTypeOf(pkIds[i]);
477                checkArgument(type.isAssignableFrom(key.getClass()),
478                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
479            }
480            Connection c = null;
481            PreparedStatement ps = null;
482            try
483            {
484                c = this.getConnection();
485                StringBuilder sql = new StringBuilder("SELECT " + metaData.columnFields + " FROM " + metaData.tablename + " WHERE ");
486                sql.append(Joiner.on(" AND ").join(
487                            Lists.transform(Arrays.asList(pkNames), SQL_FUN1)));
488                if(debug){
489                        log("LOAD BY PK: " + sql.toString());
490                }
491                ps = c.prepareStatement(sql.toString(),
492                                        ResultSet.TYPE_SCROLL_INSENSITIVE,
493                                        ResultSet.CONCUR_READ_ONLY);
494                        for(int i = 0; i<pkNames.length; ++i){
495                                setPreparedStatement(ps,  i+1, keys[i], pkIds[i]);
496                        }
497                List<B> pReturn = this.loadByPreparedStatementAsList(ps,null,1,-1);
498                if (1 == pReturn.size()) {
499                    return pReturn.get(0);
500                } else {
501                    throw new ObjectRetrievalException();
502                }
503            }
504            catch(ObjectRetrievalException e)
505            {
506                throw e;
507            }
508            catch(SQLException e)
509            {
510                throw new RuntimeDaoException(new DataRetrievalException(e));
511            }
512            finally
513            {
514                this.getManager().close(ps);
515                this.freeConnection(c);
516            }
517        }
518
519        @Override
520        public B loadByPrimaryKey(Object ...keys)throws RuntimeDaoException{
521            try{
522                return loadByPrimaryKeyChecked(keys);
523            }catch(ObjectRetrievalException e){
524                // not found
525                return null;
526            }
527        }
528
529        protected <K> List<B> loadByPks(Collection<K> keys){
530                checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
531            if(null == keys){
532                return Collections.emptyList();
533            }
534            ArrayList<B> list = new ArrayList<B>(keys.size());
535            for(K key:keys){
536                list.add(loadByPrimaryKey(key));
537            }
538            return list;
539        }
540        
541        @SuppressWarnings("unchecked")
542        protected <K> List<B> loadByPks(K... keys){
543            if(null == keys){
544                return Collections.emptyList();
545            }
546                return loadByPks(Arrays.asList(keys));
547        }
548
549        @Override
550    public boolean existsByPrimaryKey(B bean)throws RuntimeDaoException{
551        return null == bean ? false : existsPrimaryKey(bean.primaryValues());
552    }
553    @Override
554    public B checkDuplicate(B bean)throws RuntimeDaoException,ObjectRetrievalException{
555        if(existsByPrimaryKey(bean)){
556            throw new ObjectRetrievalException("Duplicate entry ("+ bean.primaryValues() +") for key 'PRIMARY'");
557        }
558        return bean;   
559    }
560    @Override
561    public final boolean existsPrimaryKey(Object ...keys)throws RuntimeDaoException{
562        // {{check parameters
563        if(metaData.primaryKeyNames.length == 0){
564                throw new UnsupportedOperationException();
565        }
566        String[] pkNames = metaData.primaryKeyNames;
567        int[] pkIds = metaData.primaryKeyIds;
568        if(null == keys || hasNull(keys)){
569                return false;
570        }
571        checkArgument(keys.length == pkNames.length,
572                        "INVALID ARGUMENT NUM, %s required",pkNames.length);
573        for(int i=0; i<pkNames.length; ++i){
574                Object key = keys[i];
575                Class<?> type = metaData.columnTypeOf(pkIds[i]);
576                checkArgument(type.isInstance(key),
577                                "INVALID type for pk %s,%s required",pkNames[i],type.getName());
578        }
579        // }}check parameters
580        return doExistsPrimaryKey(keys);
581    }
582    protected boolean doExistsPrimaryKey(Object ...keys)throws RuntimeDaoException{
583        String[] pkNames = metaData.primaryKeyNames;
584        int[] pkIds = metaData.primaryKeyIds;
585        Connection c = null;
586        PreparedStatement ps = null;
587        try{
588            c = this.getConnection();
589            StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM "  + metaData.tablename + " WHERE ");
590            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pkNames),SQL_FUN1)));
591            if(debug){
592                log("ExistsPrimaryKey: " + sql);
593            }
594            ps = c.prepareStatement(sql.toString(),
595                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
596                                    ResultSet.CONCUR_READ_ONLY);
597
598                        for(int i = 0; i<pkNames.length; ++i){
599                                 setPreparedStatement(ps, i+1, keys[i], pkIds[i]);
600                        }
601            return 1 == getManager().runPreparedStatementForValue(Long.class, ps);
602        }catch(SQLException e){
603            throw new RuntimeDaoException(new ObjectRetrievalException(e));
604        }finally{
605            this.getManager().close(ps);
606            this.freeConnection(c);
607        }
608    }
609    protected <T>T checkDuplicateByPk(T primaryKeyValue)throws ObjectRetrievalException{
610        if(metaData.primaryKeyNames.length != 1){
611                throw new UnsupportedOperationException();
612        }
613            if(existsPrimaryKey(primaryKeyValue)){
614                throw new ObjectRetrievalException("Duplicate entry '"+ primaryKeyValue +"' for key 'PRIMARY'");
615            }
616            return primaryKeyValue;
617        }
618
619        @Override
620    public B[] loadByWhere(String where)throws RuntimeDaoException{
621        return this.loadByWhere(where, (int[])null);
622    }
623
624    @Override
625    public int loadByWhere(String where, TableManager.Action<B> action)throws RuntimeDaoException{
626        return this.loadByWhere(where, null, action);
627    }
628
629    @Override
630    public B[] loadByWhere(String where, int[] fieldList)throws RuntimeDaoException{
631        return this.loadByWhere(where, fieldList, 1, -1);
632    }
633
634    @Override
635    public int loadByWhere(String where, int[] fieldList, TableManager.Action<B> action)throws RuntimeDaoException{
636        return this.loadByWhere(where, fieldList, 1, -1, action);
637    }
638
639    @SuppressWarnings("unchecked")
640    @Override
641    public B[] loadByWhere(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
642        return this.loadByWhereAsList(where, fieldList, startRow, numRows).toArray((B[])Array.newInstance(metaData.beanType,0));
643    }
644
645    @Override
646    public int loadByWhere(String where, int[] fieldList, int startRow, int numRows,
647            TableManager.Action<B> action)throws RuntimeDaoException{
648        return this.loadByWhereForAction(where, fieldList, startRow, numRows, action);
649    }
650
651    @Override
652    public List<B> loadByWhereAsList(String where)throws RuntimeDaoException{
653        return this.loadByWhereAsList(where, null, 1, -1);
654    }
655
656    @Override
657    public List<B> loadByWhereAsList(String where, int[] fieldList)throws RuntimeDaoException{
658        return this.loadByWhereAsList(where, fieldList, 1, -1);
659    }
660
661    @Override
662    public List<B> loadByWhereAsList(String where, int[] fieldList, int startRow, int numRows)throws RuntimeDaoException{
663        ListAction action = new ListAction();
664        loadByWhereForAction(where, fieldList, startRow, numRows, action);              
665        return action.getList();
666    }
667
668    @Override
669    public int loadByWhereForAction(String where, int[] fieldList, int startRow, int numRows,TableManager.Action<B> action)throws RuntimeDaoException{
670        String sql=createSelectSql(fieldList, where);
671        return this.loadBySqlForAction(sql, null, fieldList, startRow, numRows, action);
672    }
673
674    @Override
675    public B[] loadUsingTemplate(B bean)throws RuntimeDaoException{
676        return this.loadUsingTemplate(bean, 1, -1, SEARCH_EXACT);
677    }
678
679    @Override
680    public int loadUsingTemplate(B bean, TableManager.Action<B> action)throws RuntimeDaoException{
681        return this.loadUsingTemplate(bean, null, 1, -1, SEARCH_EXACT, action);
682    }
683
684    @Override
685    public B[] loadUsingTemplate(B bean, int startRow, int numRows)throws RuntimeDaoException{
686        return this.loadUsingTemplate(bean, startRow, numRows, SEARCH_EXACT);
687    }
688
689    @Override
690    public int loadUsingTemplate(B bean, int startRow, int numRows,
691            TableManager.Action<B> action)throws RuntimeDaoException{
692        return this.loadUsingTemplate(bean, null, startRow, numRows,SEARCH_EXACT, action);
693    }
694
695    @SuppressWarnings("unchecked")
696    @Override
697    public B[] loadUsingTemplate(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
698        return this.loadUsingTemplateAsList(bean, startRow, numRows, searchType).toArray((B[])Array.newInstance(metaData.beanType,0));
699    }
700
701    @Override
702    public List<B> loadUsingTemplateAsList(B bean)throws RuntimeDaoException{
703        return this.loadUsingTemplateAsList(bean, 1, -1, SEARCH_EXACT);
704    }
705
706    @Override
707    public List<B> loadUsingTemplateAsList(B bean, int startRow, int numRows)throws RuntimeDaoException{
708        return this.loadUsingTemplateAsList(bean, startRow, numRows, SEARCH_EXACT);
709    }
710
711    @Override
712    public List<B> loadUsingTemplateAsList(B bean, int startRow, int numRows, int searchType)throws RuntimeDaoException{
713        ListAction action = new ListAction();
714        loadUsingTemplate(bean,null,startRow,numRows,searchType, action);
715        return action.getList();
716    }
717    //18
718    @Override
719    public B loadUniqueUsingTemplate(B bean)
720    {
721        try {
722                return loadUniqueUsingTemplateChecked(bean);    
723                } catch (ObjectRetrievalException e) {
724                        return null;
725                }
726     }
727    //18-1
728    @Override
729    public B loadUniqueUsingTemplateChecked(B bean) throws ObjectRetrievalException
730    {
731         List<B> beans = this.loadUsingTemplateAsList(bean);
732         switch(beans.size()){
733         case 0:
734             throw new ObjectRetrievalException("Not found element !!");
735         case 1:
736             return beans.get(0);
737         default:
738             throw new RuntimeDaoException(new ObjectRetrievalException("More than one element !!"));
739         }
740     }
741    
742    @Override
743    public int loadUsingTemplate(B bean, int[] fieldList, int startRow, int numRows,int searchType, Action<B> action)
744    {
745        StringBuilder sqlWhere = new StringBuilder("");
746        String sql=createSelectSql(fieldList,
747                        this.fillWhere(sqlWhere, bean, searchType) > 0 ? " WHERE "+ sqlWhere.toString() : null);
748        PreparedStatement ps = null;
749        Connection connection = null;
750        try {
751            connection = this.getConnection();
752            if(debug){
753                log("loadUsingTemplate:" + sql);
754            }
755            ps = connection.prepareStatement(sql,
756                    ResultSet.TYPE_FORWARD_ONLY,
757                    ResultSet.CONCUR_READ_ONLY);
758            this.fillPreparedStatement(ps, bean, searchType,false);
759            return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action);
760        } catch (DaoException e) {
761            throw new RuntimeDaoException(e);
762        }catch (SQLException e) {
763            throw new RuntimeDaoException(new DataAccessException(e));
764        } finally {
765            this.getManager().close(ps);
766            this.freeConnection(connection);
767        }
768    }
769    /**
770     * @param <F> bean type of foreign table 
771     * @param left
772     * @param columnsMap
773     * @param startRow
774     * @param numRows
775     * @return list of B or empty list
776     */
777    private <F extends BaseBean>
778        List<B> loadByForeignKeyAsList(F left,Map<Integer,Integer>columnsMap,int startRow, int numRows)
779        {
780            if(null == left){
781                return Collections.emptyList();
782            }
783                checkArgument(columnsMap != null && !columnsMap.isEmpty(),"columnsMap is null or empty");
784            B bean = createBean().copy(left, columnsMap);
785            return loadUsingTemplateAsList(bean,startRow,numRows);
786        }
787    /**
788     * @param <F> bean type of foreign table
789     * @param left
790     * @param fkName
791     * @param startRow
792     * @param numRows
793     * @return list of B or empty list
794     */
795    public <F extends BaseBean>
796        List<B> loadByForeignKeyAsList(F left,String fkName,int startRow, int numRows)
797        {
798            ImmutableBiMap<Integer, Integer> columnsMap = metaData.foreignKeyIdMapOf(fkName).inverse();
799            return loadByForeignKeyAsList(left,columnsMap,startRow,numRows);
800        }
801
802        @Override
803    public B save(B bean)throws RuntimeDaoException{
804        if(null != bean){
805            if (bean.isNew()) {
806                this.insert(bean);
807            } else {
808                this.update(bean);
809            }
810        }
811        return bean;
812    }
813    
814    @Override
815    public B[] save(B[] beans)throws RuntimeDaoException{
816        if(null != beans){
817            for (B bean : beans) 
818            {
819                this.save(bean);
820            }
821        }
822        return beans;
823    }
824
825    @Override
826    public <C extends Collection<B>> C save(C beans)throws RuntimeDaoException{
827        if(null != beans){
828            for (B bean : beans) 
829            {
830                this.save(bean);
831            }
832        }
833        return beans;
834    }
835    
836    @Override
837    public <C extends Collection<B>> C saveAsTransaction(final C beans)throws RuntimeDaoException{
838        return this.runAsTransaction(new Callable<C>(){
839            @Override
840            public C call() throws Exception {
841                return save(beans);
842            }});
843    }
844
845    @Override
846    public B[] saveAsTransaction(final B[] beans)throws RuntimeDaoException{
847        return this.runAsTransaction(new Callable<B[]>(){
848            @Override
849            public B[] call() throws Exception {
850                return save(beans);
851            }});
852    }
853
854    private String checkWhere(String where){
855        where = MoreObjects.firstNonNull(where, "").trim();
856        checkArgument(where.isEmpty() || where.toUpperCase().startsWith("WHERE "),
857                        "WHERE expression must start with 'WHERE'(case insensitive)");
858        return where;
859    }
860    
861    @SuppressWarnings("unchecked")
862        @Override
863    public <T> List<T> loadColumnAsList(String column,boolean distinct,String where,int startRow,int numRows)throws RuntimeDaoException{
864        int columnId = metaData.columnIDOf(column);
865        checkArgument(columnId>=0,"INVALID column name %s",column);
866        String sql = String.format("SELECT %s " + metaData.columnNameOf(columnId) + " FROM %s %s",
867                distinct ? "DISTINCT" : "",
868                metaData.tablename,
869                checkWhere(where));
870        return runSqlAsList((Class<T>)metaData.columnTypes.get(columnId), sql);
871    }
872    /**
873     * generate SQL query(SELECT) statement,such as: 'SELECT id,name from mytable WHERE id=1'
874     * @param fieldList
875     * @param where where condition expression statement that start with 'WHERE',or {@code null},or empty string
876     * @return SQL statement string
877     * @throws IllegalArgumentException where condition expression don't start with 'WHERE'
878     */
879    private String createSelectSql(int[] fieldList, String where){
880        StringBuffer sql = new StringBuffer(128);
881        String fullFields = metaData.columnFullFields;
882        if(null == fieldList || 0 == fieldList.length) {
883            sql.append("SELECT ").append(fullFields);
884        } else{
885            sql.append("SELECT ");
886            String[] names=fullFields.split(",");
887            for(int i = 0; i < fieldList.length; ++i){
888                if(i > 0) {
889                    sql.append(",");
890                }
891                sql.append(names[fieldList[i]]);
892            }      
893        }
894        sql.append(" FROM " + this.metaData.tablename + " ");
895        sql.append(checkWhere(where));
896        return sql.toString();
897    }
898
899    //2.2
900
901    @Override
902    public int delete(B bean){
903        if(metaData.primaryKeyNames.length==0){
904                throw new UnsupportedOperationException();
905        }
906        if(hasNullPk(bean)){
907                return 0;
908        }
909        Connection c = null;
910        PreparedStatement ps = null;
911        String [] pks = metaData.primaryKeyNames;
912        int[] pkIds = metaData.primaryKeyIds;
913        try
914        {
915            // listener callback
916            this.listenerContainer.beforeDelete(bean);
917            c = this.getConnection();
918            StringBuilder sql = new StringBuilder("DELETE  FROM " + metaData.tablename +" WHERE ");
919            sql.append(Joiner.on(" AND ").join(Lists.transform(Arrays.asList(pks),SQL_FUN1)));
920            if(debug){
921                log("deleteByPrimaryKey: " + sql);
922            }
923            ps = c.prepareStatement(sql.toString(),
924                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
925                                    ResultSet.CONCUR_READ_ONLY);
926            for(int i = 0; i<pkIds.length; ++i){
927                 setPreparedStatement(ps, i+1, bean, pkIds[i]);
928            }
929            int rows=ps.executeUpdate();
930            if(rows>0){
931                // listener callback
932                this.listenerContainer.afterDelete(bean);
933            }
934            return rows;
935        }
936        catch(SQLException e)
937        {
938            throw new RuntimeDaoException(new DataAccessException(e));
939        }
940        finally
941        {
942            // listener callback
943            this.listenerContainer.done();
944            this.getManager().close(ps);
945            this.freeConnection(c);
946        }
947    }
948
949    protected <K> int deleteByPks(Collection<K> keys){
950                checkState(metaData.primaryKeyCount == 1,"UNSUPPORTED OPERATION");
951            int count = 0;
952            if(null != keys){
953                for(K key :keys){
954                    count += deleteByPrimaryKey(key);
955                }
956            }
957            return count;
958        }
959
960        @SuppressWarnings("unchecked")
961        protected <K> int deleteByPks(K... keys){
962            if(null == keys){
963                return 0;
964            }
965            return deleteByPks(Arrays.asList(keys));
966        }
967
968        @Override
969        public int deleteByPrimaryKey(Object ...keys)throws RuntimeDaoException{
970            if(hasNull(keys)){
971                return 0;
972            }
973                checkArgument(keys.length == metaData.primaryKeyCount,"argument number mismatch with primary key number");
974            return delete(createBean(keys));
975        }
976
977        //2.4 
978
979        @SuppressWarnings("unchecked")
980        @Override
981        public int delete(B... beans)throws RuntimeDaoException{
982            int count = 0;
983            if(null != beans){
984                for(B bean :beans){
985                    count += delete(bean);
986                }
987            }
988            return count;
989        }
990
991        //2.5  
992
993        @Override
994        public int delete(Collection<B> beans)throws RuntimeDaoException{
995            int count = 0;
996            if(null != beans){
997                for(B bean :beans){
998                    count += delete(bean);
999                }
1000            }
1001            return count;
1002        }
1003
1004        @SuppressWarnings("unchecked")
1005    @Override
1006        public <T extends BaseBean> T getReferencedBean(B bean, String fkName) throws RuntimeDaoException{
1007        if(null == bean){
1008                return null;
1009        }
1010        ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName),
1011                        "INVALID fkName %s for table %s", fkName, metaData.tablename);
1012                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.foreignTable);
1013        T t = foreignManager.createBean().copy(bean, metaData.foreignKeyIdMapOf(fkName));
1014        return foreignManager.loadByPrimaryKey(t);
1015    }
1016    
1017    @SuppressWarnings("unchecked")
1018    @Override
1019        public <T extends BaseBean> T setReferencedBean(B bean, T beanToSet, String fkName) throws RuntimeDaoException{
1020        if(null != bean){
1021                ForeignKeyMetaData foreignkey = checkNotNull(metaData.foreignKeys.get(fkName),
1022                                "INVALID fkName %s for table %s", fkName, metaData.tablename);
1023                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.foreignTable);
1024                foreignManager.save(beanToSet);
1025                bean.copy(beanToSet, metaData.foreignKeyIdMapOf(fkName).inverse());
1026        }
1027        return beanToSet;
1028    }
1029    @SuppressWarnings("unchecked")
1030    @Override
1031        public <T extends BaseBean> T[] getImportedBeans(B bean,String fkName) throws RuntimeDaoException{
1032        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1033        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1034        return getImportedBeansAsList(bean,fkName).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0));
1035    }
1036
1037    @SuppressWarnings("unchecked")
1038    @Override
1039        public <T extends BaseBean> List<T> getImportedBeansAsList(B bean, String fkName)throws RuntimeDaoException{
1040        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1041                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.ownerTable);
1042                return foreignManager.loadByForeignKeyAsList(bean,fkName, 1, -1);
1043    }
1044    protected <T extends BaseBean> List<T> getImportedBeansAsList(String fkName,Object...keys) throws RuntimeDaoException{          
1045        return getImportedBeansAsList(createBean(keys),fkName);
1046    }
1047        @SuppressWarnings("unchecked")
1048        protected <T extends BaseBean> T[] getImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{              
1049        ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1050        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1051        return getImportedBeansAsList(fkName,keys).toArray((T[]) Array.newInstance(foreignMetaData.beanType, 0));
1052    }
1053    @SuppressWarnings("unchecked")
1054    @Override
1055        public <T extends BaseBean, C extends Collection<T>> C setImportedBeans(B bean, C importedBeans,
1056            String fkName)throws RuntimeDaoException{
1057        if(null != importedBeans){
1058                ForeignKeyMetaData foreignkey =metaData.getImportedKey(fkName);
1059                BaseTableManager<T> foreignManager = (BaseTableManager<T>)managerOf(foreignkey.ownerTable);
1060                for( T importBean : importedBeans ){
1061                        foreignManager.setReferencedBean(importBean , bean,fkName);
1062            }
1063        }
1064        return importedBeans;
1065    }
1066    
1067    @Override
1068    public <T extends BaseBean> T[] setImportedBeans(B bean, T[] importedBeans, String fkName) throws RuntimeDaoException{
1069        if(null != importedBeans){
1070                setImportedBeans(bean, Arrays.asList(importedBeans), fkName);
1071        }
1072        return importedBeans;
1073    }
1074    @SuppressWarnings("unchecked")
1075    @Override
1076        public int deleteImportedBeans(B bean,String fkName){
1077        if(bean == null){
1078                return 0;
1079        }
1080        ForeignKeyMetaData foreignkey = metaData.getImportedKey(fkName);
1081        RowMetaData foreignMetaData = RowMetaData.getMedaData(foreignkey.foreignTable);
1082                BaseTableManager<BaseBean> foreignManager = (BaseTableManager<BaseBean>)managerOf(foreignkey.ownerTable);
1083                BaseBean tmpl = foreignManager.createBean().copy(bean,foreignMetaData.foreignKeyIdMapOf(fkName));
1084                return foreignManager.deleteUsingTemplate(tmpl);
1085    }
1086    protected int deleteImportedBeans(Map<Integer, Object> idValueMap, String fkName) throws RuntimeDaoException{         
1087        B bean = createBean().copy(idValueMap);
1088        return deleteImportedBeans(bean,fkName);
1089    }
1090    
1091    protected int deleteImportedBeans(String fkName,Object...keys) throws RuntimeDaoException{          
1092        return deleteImportedBeans(createBean(keys),fkName);
1093    }
1094    private Map<Integer, Object> makeIndexValueMap(String indexName,Object ...indexValues){
1095        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1096        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1097        checkArgument(null != indexValues && indexValues.length == indexMetaData.columns.size(),"INVALID index value");
1098        ImmutableMap.Builder<Integer,Object> builder = ImmutableMap.<Integer,Object>builder();
1099        for(int i=0;i<indexValues.length;++i){
1100                Object value = indexValues[i];
1101                if(null != value){
1102                        String column = indexMetaData.columns.get(i);
1103                        Class<?> columnType = metaData.columnTypeOf(column);
1104                        checkArgument(columnType.isInstance(value),
1105                                        "INVALID value type %s for %s,%s required",value.getClass(),column,columnType);
1106                        builder.put(metaData.columnIDOf(column), value);
1107                }
1108        }
1109        return builder.build();
1110    }
1111    //2.4 override IDeviceManager
1112
1113    @SuppressWarnings("unchecked")
1114    @Override
1115    public B[] loadByIndex(String indexName,Object ...keys)throws RuntimeDaoException{
1116        return this.loadByIndexAsList(indexName,keys).toArray((B[])Array.newInstance(metaData.beanType,0));
1117    }
1118    @Override
1119    public List<B> loadByIndexAsList(String indexName,Object ...indexValues)throws RuntimeDaoException{
1120        Map<Integer, Object> map = makeIndexValueMap(indexName,indexValues);
1121        return loadUsingTemplateAsList(map);
1122    }
1123    @Override
1124    public final B loadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1125        return doLoadUniqueByIndex(indexName, indexValues);
1126    }
1127    protected B doLoadUniqueByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1128        Map<Integer, Object> keys = makeIndexValueMap(indexName,indexValues);
1129        B bean = createBean().copy(keys);
1130        return loadUniqueUsingTemplate(bean);
1131    }
1132    @Override
1133    public final B loadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{
1134                if(hasNull(indexValues)){
1135                        throw new ObjectRetrievalException(new NullPointerException("index keys must not be null"));
1136                }
1137        return doLoadUniqueByIndexChecked(indexName, indexValues);
1138    }
1139    protected B doLoadUniqueByIndexChecked(String indexName,Object ...indexValues)throws ObjectRetrievalException{
1140        Map<Integer, Object> keys = makeIndexValueMap(indexName,indexValues);
1141        B bean = createBean().copy(keys);
1142        return loadUniqueUsingTemplateChecked(bean);
1143    }
1144    @Override
1145    public int deleteByIndex(String indexName,Object ...indexValues)throws RuntimeDaoException{
1146        Map<Integer, Object> map = makeIndexValueMap(indexName,indexValues);
1147        return deleteUsingTemplate(map);
1148    }
1149    
1150    private List<B> loadUsingTemplateAsList(Map<Integer, Object>keys){
1151        B bean = createBean().copy(keys);
1152        return loadUsingTemplateAsList(bean);
1153    }
1154    
1155    private int deleteUsingTemplate(Map<Integer, Object>keys){
1156        B bean = createBean().copy(keys);
1157        return deleteUsingTemplate(bean);
1158    }
1159    protected <T>List<B> loadByIndexForIndices(String indexName,Collection<T> indexs)
1160    {
1161        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1162        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1163        checkArgument(1 == indexMetaData.columns.size(),"column count of  index must be 1");
1164        checkArgument(indexMetaData.unique,"index must be unique");
1165        if(null == indexs ){
1166            return Collections.emptyList();
1167        }
1168        List<B> list = new ArrayList<B>(indexs.size());
1169        for(T key: indexs){
1170            list.add(loadUniqueByIndex(indexName,key));
1171        }
1172        return list;
1173    }
1174    @SuppressWarnings("unchecked")
1175        protected <T>List<B> loadByIndexForIndices(String indexName,T... indexs){
1176        if(indexs == null || indexs.length == 0){
1177                return Collections.emptyList();
1178        }
1179        return loadByIndexForIndices(indexName,Arrays.asList(indexs));
1180    }
1181    protected <T>int deleteByIndexForIndices(String indexName,Collection<T> indexs)
1182    {
1183        IndexMetaData indexMetaData = metaData.indices.get(indexName);
1184        checkArgument(null != indexMetaData,"INVALID index name %s",indexName);
1185        checkArgument(1 == indexMetaData.columns.size(),"column count of  index must be 1");
1186
1187        if(null == indexs ){
1188            return 0;
1189        }
1190        int count = 0;
1191        for(T key: indexs){
1192                if(key != null){
1193                        count += deleteByIndex(indexName, key);
1194                }
1195        }
1196        return count;
1197    }
1198    @SuppressWarnings("unchecked")
1199        protected <T>int deleteByIndexForIndices(String indexName,T... indexs){
1200        if(indexs == null || indexs.length == 0){
1201                return 0;
1202        }
1203        return deleteByIndexForIndices(indexName,Arrays.asList(indexs));
1204    }
1205    private boolean checkPkValid(B bean)
1206        {
1207                if(metaData.primaryKeyNames.length ==0){
1208                        return false;
1209                }
1210        for(String name:metaData.primaryKeyNames){
1211                if(! (bean.isInitialized(name) && null != bean.getValue(name))){
1212                        return false;   
1213                }
1214        }       
1215        return true;
1216        }
1217    //21
1218
1219    @Override
1220    public int deleteUsingTemplate(B bean)
1221    {
1222        if(bean == null || !bean.isModified()){
1223                return 0;
1224        }
1225        if(checkPkValid(bean)){
1226            return this.deleteByPrimaryKey(bean);
1227        }
1228        if( !this.listenerContainer.isEmpty()){
1229            final DeleteBeanAction action=new DeleteBeanAction(); 
1230            this.loadUsingTemplate(bean,action);
1231            return action.getCount();
1232        }
1233        Connection c = null;
1234        PreparedStatement ps = null;
1235        StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename +" ");
1236        StringBuilder sqlWhere = new StringBuilder("");
1237
1238        try
1239        {
1240            fillWhere(sqlWhere, bean, SEARCH_EXACT);
1241            sql.append(" WHERE ").append(sqlWhere);
1242            c = this.getConnection();
1243            if(debug){
1244                log("deleteUsingTemplate: " + sql.toString());
1245            }
1246            ps = c.prepareStatement(sql.toString(),
1247                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1248                                    ResultSet.CONCUR_READ_ONLY);
1249            this.fillPreparedStatement(ps, bean, SEARCH_EXACT, false);
1250
1251            return ps.executeUpdate();
1252        }
1253        catch(SQLException e)
1254        {
1255            throw new RuntimeDaoException(new DataAccessException(e));
1256        }
1257        finally
1258        {
1259            this.getManager().close(ps);
1260            this.freeConnection(c);
1261        }
1262    }
1263    
1264    //11
1265
1266    @Override
1267    public int deleteByWhere(String where)
1268    {
1269        if( !this.listenerContainer.isEmpty()){
1270            final DeleteBeanAction action = new DeleteBeanAction(); 
1271            this.loadByWhere(where,action);
1272            return action.getCount();
1273        }
1274        Connection c = null;
1275        PreparedStatement ps = null;
1276
1277        try
1278        {
1279            c = this.getConnection();
1280            StringBuilder sql = new StringBuilder("DELETE FROM " + metaData.tablename + " " + where);
1281            if(debug){
1282                log("deleteByWhere: " + sql);
1283            }
1284            ps = c.prepareStatement(sql.toString());
1285            return ps.executeUpdate();
1286        }
1287        catch(SQLException e)
1288        {
1289            throw new RuntimeDaoException(new DataAccessException(e));
1290        }
1291        finally
1292        {
1293            this.getManager().close(ps);
1294            this.freeConnection(c);
1295        }
1296    }
1297    private void setPreparedStatement(PreparedStatement ps,int pos,Object value,int columnId)
1298                throws SQLException {
1299        Manager.setPreparedStatement(ps, pos, value, metaData.sqlTypes[columnId]);
1300    }
1301    private void setPreparedStatement(PreparedStatement ps,int pos,B bean,int columnId)
1302                throws SQLException {
1303        setPreparedStatement(ps,pos,bean.getValue(columnId),columnId);
1304    }
1305
1306    @Override
1307        public B save(B bean,Map<String, BaseBean> referenceBeans,Map<String, Collection<BaseBean>> importedBeans) throws RuntimeDaoException{
1308            if(null == bean){
1309                return null;
1310            }
1311            referenceBeans = MoreObjects.firstNonNull(referenceBeans, Collections.<String, BaseBean>emptyMap());
1312            importedBeans = MoreObjects.firstNonNull(importedBeans, Collections.<String, Collection<BaseBean>>emptyMap());
1313            for(Entry<String, BaseBean> entry:referenceBeans.entrySet()){
1314                BaseBean beanToSet = entry.getValue();
1315                if(beanToSet != null){
1316                        setReferencedBean(bean, beanToSet, entry.getKey());
1317                }
1318            }
1319            bean = this.save( bean );
1320            for(Entry<String, Collection<BaseBean>> entry:importedBeans.entrySet()){
1321                String ikName = entry.getKey();
1322                Collection<BaseBean> importeds = entry.getValue(); 
1323                if(null != importeds){
1324                        setImportedBeans(bean, importeds, ikName);
1325                        TableManager<BaseBean> impManager = managerOf(metaData.getImportedKey(ikName).ownerTable);
1326                        impManager.save(importeds);
1327                }
1328            }
1329                return bean;            
1330        }
1331    
1332    @Override
1333    public B saveAsTransaction(final B bean,final Map<String, BaseBean> referenceBeans,final Map<String, Collection<BaseBean>> importedBeans)throws RuntimeDaoException{
1334        return this.runAsTransaction(new Callable<B>(){
1335            @Override
1336            public B call() throws Exception {
1337                return save(bean , referenceBeans, importedBeans );
1338            }});
1339    }
1340    
1341    //_____________________________________________________________________
1342    //
1343    // COUNT
1344    //_____________________________________________________________________
1345    //25
1346
1347    @Override
1348    public int countWhere(String where)
1349    {
1350        String sql = new StringBuffer("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename + " ")
1351                    .append(null == where ? "" : where).toString();
1352        return runSqlForValue(Long.class, sql).intValue();
1353    }
1354    //20
1355    /**
1356     * count the number of elements of a specific B bean given the search type
1357     *
1358     * @param bean the B template to look for
1359     * @param searchType exact ?  like ? starting like ?
1360     * @return the number of rows returned
1361     */
1362    @Override
1363    public int countUsingTemplate(B bean, int searchType)
1364    {
1365        Connection c = null;
1366        PreparedStatement ps = null;
1367        StringBuilder sql = new StringBuilder("SELECT COUNT(*) AS MCOUNT FROM " + metaData.tablename);
1368        StringBuilder sqlWhere = new StringBuilder("");
1369
1370        try
1371        {
1372            if (this.fillWhere(sqlWhere, bean, SEARCH_EXACT) > 0) {
1373                sql.append(" WHERE ").append(sqlWhere);
1374            } else {
1375                if(debug){
1376                        log("The bean to look is not initialized... counting all...");
1377                }
1378            }
1379
1380            c = this.getConnection();
1381            if(debug){
1382                log("countUsingTemplate: " + sql.toString());
1383            }
1384            ps = c.prepareStatement(sql.toString(),
1385                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1386                                    ResultSet.CONCUR_READ_ONLY);
1387            this.fillPreparedStatement(ps, bean, searchType,false);
1388            return getManager().runPreparedStatementForValue(Long.class,ps).intValue();
1389        }
1390        catch(SQLException e)
1391        {
1392            throw new RuntimeDaoException(new DataAccessException(e));
1393        }
1394        finally
1395        {
1396            this.getManager().close(ps);
1397            this.freeConnection(c);
1398            sql = null;
1399            sqlWhere = null;
1400        }
1401    }
1402    /**
1403     * fills the given StringBuilder with the sql where clauses constructed using the bean and the search type
1404     * @param sqlWhere the StringBuilder that will be filled
1405     * @param bean the bean to use for creating the where clauses
1406     * @param searchType exact ?  like ? starting like ?
1407     * @return the number of clauses returned
1408     */
1409    private int fillWhere(StringBuilder sqlWhere, B bean, int searchType)
1410    {
1411        if (bean == null) {
1412                return 0;
1413        }
1414        int dirtyCount = 0;
1415        String sqlEqualsOperation = searchType == SEARCH_EXACT ? "=" : " like ";
1416        List<String> fields = metaData.columnNames;
1417        for(int i=0; i<fields.size(); ++i){
1418                if(bean.isModified(i)){
1419                        ++dirtyCount;
1420                        if(bean.getValue(i) == null){
1421                                sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" IS NULL");
1422                        }else{
1423                                if(metaData.columnTypeOf(i) == String.class){
1424                                        sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" ").append(sqlEqualsOperation).append("?");
1425                                } else{
1426                                        sqlWhere.append((sqlWhere.length() == 0) ? " " : " AND ").append(metaData.columnNameOf(i)).append(" = ?");
1427                                }
1428                        }
1429                }
1430        }
1431
1432        return dirtyCount;
1433    }
1434    /**
1435     * fill the given prepared statement with the bean values and a search type
1436     * @param ps the PreparedStatement that will be filled
1437     * @param bean the bean to use for creating the where clauses
1438     * @param searchType exact ?  like ? starting like ?
1439     * @param fillNull wether fill null for null field
1440     * @return the number of clauses returned
1441     */
1442    private int fillPreparedStatement(PreparedStatement ps, B bean, int searchType,boolean fillNull) throws DaoException
1443    {
1444        if (bean == null) {
1445            return 0;
1446        }
1447        int dirtyCount = 0;
1448        try
1449        {
1450                List<String> fields = metaData.columnNames;
1451                for(int columnId=0; columnId<fields.size(); ++columnId){
1452                        Object value = bean.getValue(columnId);
1453                        if(null != value || fillNull){
1454                                if(bean.isModified(columnId)){
1455                                        if(String.class == metaData.columnTypeOf(columnId)){
1456                                                switch (searchType) {
1457                                                case SEARCH_EXACT:
1458                                                        setPreparedStatement(ps, ++dirtyCount , value, columnId);
1459                                                        break;
1460                                                case SEARCH_LIKE:
1461                                                        setPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value + SQL_LIKE_WILDCARD, columnId);
1462                                                        break;
1463                                                case SEARCH_STARTING_LIKE:
1464                                                        setPreparedStatement(ps, ++dirtyCount , SQL_LIKE_WILDCARD + value, columnId);
1465                                                        break;
1466                                                case SEARCH_ENDING_LIKE:
1467                                                        setPreparedStatement(ps, ++dirtyCount , value + SQL_LIKE_WILDCARD, columnId);
1468                                                        break;
1469                                                default:
1470                                                        throw new DaoException("Unknown search type : " + searchType);
1471                                                }
1472                                        }else{
1473                                                setPreparedStatement(ps, ++dirtyCount , bean, columnId);
1474                                        } 
1475                                }                               
1476                        }
1477                }
1478        }
1479        catch(SQLException e)
1480        {
1481            throw new DataAccessException(e);
1482        }
1483        return dirtyCount;
1484    }
1485    
1486    /**
1487     * 检查指定的字段是否为{@code null}
1488     * @author guyadong
1489     *
1490     */
1491    private static class FieldNullChecker implements Predicate<String>{
1492
1493                private final BaseBean bean;
1494
1495                public FieldNullChecker(BaseBean bean) {
1496                        this.bean = checkNotNull(bean,"bean is null");
1497                }
1498
1499                @Override
1500                public boolean apply(String input) {
1501                        return null == bean.getValue(input);
1502                }
1503        
1504    }
1505    //_____________________________________________________________________
1506    //
1507    // MANY TO MANY: LOAD OTHER BEAN VIA JUNCTION TABLE
1508    //_____________________________________________________________________
1509
1510    /**
1511     * Retrieves an list of R using the junction table junction table(junctionTablename), given a linked table bean, 
1512     * specifying the start row and the number of rows.
1513     * @param <L>
1514     * @param <R>
1515     * @param left
1516     * @param rightType
1517     * @param startRow the start row to be used (first row = 1, last row = -1)
1518     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1519     * @return a list of R
1520     */
1521        @SuppressWarnings("unchecked")
1522        public <L extends BaseBean,R extends BaseBean>
1523    List<R> loadViaJunctionTableAsList( L left,Class<R> rightType, int startRow, int numRows)
1524    {
1525        checkState(!metaData.getJunctionTablePkMap().isEmpty(),"%s is not junction table",metaData.tablename);
1526        checkArgument(null != left,"left is null");
1527        checkArgument(null != rightType,"rightType is null");
1528        RowMetaData leftMetaData = RowMetaData.getMedaData(left.tableName());
1529        RowMetaData rightMetaData = RowMetaData.getMedaData(rightType);
1530        checkArgument(!leftMetaData.equals(rightMetaData),"same metadata  FOR left AND right type");
1531        checkArgument(metaData.isLinkedTable(leftMetaData.tablename),"INVALID left type %s",left.getClass());
1532        checkArgument(metaData.isLinkedTable(rightMetaData.tablename),"INVALID right type %s",rightType);
1533                Map<String, String> leftFields = metaData.junctionMapOf(leftMetaData.tablename);
1534        Map<String, String> rightFields = metaData.junctionMapOf(rightMetaData.tablename);
1535        if(Iterables.tryFind(leftFields.values(), new FieldNullChecker(left)).isPresent()){
1536                return Collections.emptyList();
1537        }
1538        Connection c = null;
1539        PreparedStatement ps = null;
1540        String sql = " SELECT " + rightMetaData.columnFullFields
1541                        + " FROM "+ metaData.tablename + ", " + rightMetaData.tablename
1542                        + " WHERE "
1543                        + Joiner.on(" AND ").join(Iterables.transform(leftFields.keySet(), 
1544                                        new Function<String,String>(){
1545                                                @Override
1546                                                public String apply(String input) {
1547                                                        return input + "=?";
1548                                                }}))
1549                        + " AND "
1550                        + Joiner.on(" AND ").join(Iterables.transform(rightFields.entrySet(), 
1551                                        new Function<Map.Entry<String,String>,String>(){
1552                
1553                                                                        @Override
1554                                                                        public String apply(Map.Entry<String, String> input) {
1555                                                                                return input.getKey() + "=" + input.getValue();
1556                                                                        }}));
1557        
1558        try
1559        {
1560            c = this.getConnection();
1561            if(debug){
1562                log("loadViaJunctionTableAsList" + sql);
1563            }
1564            ps = c.prepareStatement(sql,
1565                                    ResultSet.TYPE_SCROLL_INSENSITIVE,
1566                                    ResultSet.CONCUR_READ_ONLY);
1567            int pos =1;
1568            for(String key:leftFields.keySet()){
1569                String fullName = leftFields.get(key);
1570                int columnId = leftMetaData.columnIDOf(fullName);
1571                Manager.setPreparedStatement(ps, pos++, 
1572                                left.getValue(columnId), 
1573                                leftMetaData.sqlTypeOf(columnId));      
1574            }
1575            return ((BaseTableManager<R>) managerOf(rightMetaData.beanType))
1576                        .loadByPreparedStatementAsList(ps, null, startRow, numRows);
1577        }
1578        catch (SQLException e)
1579        {
1580            throw new RuntimeDaoException(new DaoException(e.getMessage(), e));
1581        }
1582        finally
1583        {
1584           this.getManager().close(ps);
1585           this.freeConnection(c);
1586        }
1587    }
1588
1589    private <L extends BaseBean,R extends BaseBean> B makeJunctionBean(L left,R right){
1590        Map<String, Object[]> map = metaData.getJunctionTablePkMap();
1591        checkState(!map.isEmpty(), "%s is not junction table",metaData.tablename);
1592        Object[] primaryValues = new Object[metaData.primaryKeyCount];
1593                for(int i=0; i<primaryValues.length; ++i){
1594                        String pk = metaData.columnNameOf(i);
1595                        Object[] data = map.get(pk);
1596                        checkArgument(data!=null,"PRIMARY KEY %s NOT DEFINED",pk);
1597                        String linkedTableName = (String)data[0];
1598                        int linkedColumnId = ((Integer)data[1]).intValue();
1599
1600                        if(left.tableName().equals(linkedTableName)){
1601                                primaryValues[i] = left.getValueChecked(linkedColumnId);
1602                        }else if (right.tableName().equals(linkedTableName)){
1603                                primaryValues[i] = right.getValueChecked(linkedColumnId);
1604                        } else {
1605                                throw new IllegalArgumentException(String.format("%s NOT linked table  %s", linkedTableName,metaData.tablename));
1606                        }
1607                }
1608                return createBean(primaryValues);
1609    }
1610
1611    /**
1612     * add junction between L and R if junction not exists
1613     * @param <L>
1614     * @param <R>
1615     * @param left
1616     * @param right
1617     */    
1618    public <L extends BaseBean,R extends BaseBean> 
1619    void addJunction(L left,R right){
1620        if(hasNullPk(left) || hasNullPk(right)){
1621            return ;
1622        }
1623        B junction = makeJunctionBean(left,right);
1624        if(!existsByPrimaryKey(junction)){
1625            save(junction);
1626        }
1627    }
1628
1629    /**
1630     * remove junction between L and R if junction not exists
1631     * @param <L>
1632     * @param <R>
1633     * @param left
1634     * @param right
1635     */    
1636    public <L extends BaseBean,R extends BaseBean> 
1637    int deleteJunction(L left,R right){
1638        if(hasNullPk(left) || hasNullPk(right)){
1639            return 0;
1640        }
1641        B junction = makeJunctionBean(left,right);
1642        return delete(junction);
1643    }
1644    
1645    @SuppressWarnings("unchecked")
1646    public <L extends BaseBean,R extends BaseBean>
1647    void addJunction(L left,R... rights){
1648        if(null != rights){
1649                addJunction(left, Arrays.asList(rights));
1650        }
1651    }
1652
1653    public <L extends BaseBean,R extends BaseBean> 
1654    void addJunction(L left,Collection<R> rights){
1655        if(null != rights){
1656            for(R linked:rights){
1657                addJunction(left,linked);
1658            }
1659        }
1660    }
1661    @SuppressWarnings("unchecked")
1662    public <L extends BaseBean,R extends BaseBean>
1663    int deleteJunction(L left,R... rights){
1664        if(null != rights){
1665                return deleteJunction(left, Arrays.asList(rights));
1666        }
1667        return 0;
1668    }
1669    public <L extends BaseBean,R extends BaseBean>
1670    int deleteJunction(L left,Collection<R> rights){
1671        int count = 0;
1672        if(null != rights){
1673            for(R right:rights){
1674                count += deleteJunction(left,right);
1675            }
1676        }
1677        return count;
1678    }
1679
1680    //28-2
1681    /** decode a resultset and call action
1682     * @param rs the resultset to decode
1683     * @param fieldList table of the field's associated constants
1684     * @param startRow the start row to be used (first row = 1, last row = -1)
1685     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1686     * @param action interface obj for do something
1687     * @return the count dealt by action  
1688     * @throws IllegalArgumentException
1689     */
1690    protected int actionOnResultSet(ResultSet rs, int[] fieldList, int startRow, int numRows, Action<B> action) throws DaoException{
1691        try{
1692            int count = 0;
1693            if(0!=numRows){
1694                checkArgument(startRow>=1,"invalid argument:startRow (must >=1)");
1695                checkArgument(null !=action && null != rs,"invalid argument:action OR rs (must not be null)");
1696                        rs.absolute(startRow - 1);
1697                if (fieldList == null) {
1698                    if(numRows<0){
1699                        for(;rs.next();++count){
1700                            action.call(decodeRow(rs));
1701                        }
1702                    }else{
1703                        for(;rs.next() && count<numRows;++count){
1704                            action.call(decodeRow(rs));
1705                        }
1706                    }
1707                }else {
1708                    if(numRows<0){
1709                        for(;rs.next();++count){
1710                            action.call(decodeRow(rs, fieldList));
1711                        }
1712                    }else{
1713                        for(;rs.next() && count<numRows;++count){
1714                            action.call(decodeRow(rs, fieldList));
1715                        }
1716                    }
1717                }
1718            }
1719            return count;
1720        }catch(DaoException e){
1721            throw e;
1722        }catch(SQLException e){
1723            throw new DataAccessException(e);
1724        }
1725    }
1726
1727        @SuppressWarnings("unchecked")
1728        protected <T> T getColumnValue(ResultSet resultSet, int columnId) throws SQLException {
1729                return (T) Manager.getObject(resultSet,columnId + 1, metaData.columnTypeOf(columnId));
1730        }
1731        protected void setColumnValue(B bean,int columnId, Object value ){
1732                if(value instanceof byte[] && ByteBuffer.class.equals(metaData.columnTypeOf(columnId))){
1733                        value = ByteBuffer.wrap((byte[])value);
1734                }
1735                bean.setValue(columnId, value);
1736        }
1737    //29
1738    /**    
1739     * Transforms a ResultSet iterating on a B bean.
1740     *
1741     * @param rs the ResultSet to be transformed
1742     * @return bean resulting B bean
1743     */
1744    private B decodeRow(ResultSet rs) throws DaoException
1745    {
1746        B   bean = createBean();
1747        try
1748        {
1749                for(int i=0; i<metaData.columnNames.size(); ++i){
1750                        setColumnValue(bean, i, getColumnValue(rs,i));
1751                }
1752        }
1753        catch(SQLException e)
1754        {
1755            throw new DataAccessException(e);
1756        }
1757        bean.setNew(false);
1758        bean.resetIsModified();
1759
1760        return bean;
1761    }
1762
1763    //30
1764    /**
1765     * Transforms a ResultSet iterating on a B bean according to a list of fields.
1766     *
1767     * @param rs the ResultSet to be transformed
1768     * @param fieldList table of the field's associated constants
1769     * @return bean resulting B bean
1770     */
1771    private B decodeRow(ResultSet rs, int[] fieldList) throws DaoException
1772    {
1773        fieldList = MoreObjects.firstNonNull(fieldList, metaData.defaultColumnIdList);
1774        B bean = createBean();
1775        try
1776        {
1777            for(int i = 0; i < fieldList.length; i++)
1778            {
1779                if(fieldList[i]>=0 && fieldList[i]<metaData.columnNames.size()){
1780                        int columnId = fieldList[i];
1781                        setColumnValue(bean, columnId, getColumnValue(rs, columnId) );
1782                }else{
1783                        throw new DaoException("Unknown field id " + fieldList[i]);
1784                }
1785            }
1786        }
1787        catch(SQLException e)
1788        {
1789            throw new DataAccessException(e);
1790        }
1791        bean.setNew(false);
1792        bean.resetIsModified();
1793
1794        return bean;
1795    }
1796    //////////////////////////////////////
1797    // PREPARED STATEMENT LOADER
1798    //////////////////////////////////////
1799
1800    //34-1
1801    /**
1802     * Loads all the elements using a prepared statement specifying a list of fields to be retrieved,
1803     * and specifying the start row and the number of rows.
1804     *
1805     * @param ps the PreparedStatement to be used
1806     * @param startRow the start row to be used (first row = 1, last row = -1)
1807     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1808     * @param fieldList table of the field's associated constants
1809     * @return an array of B
1810     */
1811    protected List<B> loadByPreparedStatementAsList(PreparedStatement ps, int[] fieldList, int startRow, int numRows) throws DaoException
1812    {
1813        ListAction action = new ListAction();
1814        loadByPreparedStatement(ps,fieldList,startRow,numRows,action);
1815        return action.getList();
1816    }
1817    //34-2
1818    /**
1819     * Loads each element using a prepared statement specifying a list of fields to be retrieved,
1820     * and specifying the start row and the number of rows 
1821     * and dealt by action.
1822     *
1823     * @param ps the PreparedStatement to be used
1824     * @param startRow the start row to be used (first row = 1, last row = -1)
1825     * @param numRows the number of rows to be retrieved (all rows = a negative number)
1826     * @param fieldList table of the field's associated constants
1827     * @param action Action object for do something(not null)
1828     * @return the count dealt by action
1829     */     
1830    private int loadByPreparedStatement(PreparedStatement ps, int[] fieldList, int startRow, int numRows,Action<B> action) throws DaoException
1831    {
1832        ResultSet rs =  null;
1833        try { 
1834            ps.setFetchSize(100);
1835            rs = ps.executeQuery();
1836            return this.actionOnResultSet(rs, fieldList, startRow, numRows, action);
1837        } catch (DaoException e) {
1838            throw e;
1839        } catch (SQLException e) {
1840            throw new DataAccessException(e);
1841        } finally {
1842            this.getManager().close(rs);
1843        }
1844    }
1845    //_____________________________________________________________________
1846    //
1847    // LISTENER
1848    //_____________________________________________________________________
1849
1850    private final ListenerContainer<B> listenerContainer = new ListenerContainer<B>();
1851
1852    /** foreign key listener for DEELTE RULE */
1853    private class  DeleteRuleListener<F extends BaseBean> extends BaseForeignKeyListener<F,B>{
1854        protected final String fkName;
1855        private final ForeignKeyRule deleteRule;
1856        private final int[] foreignKeyIds;
1857        public DeleteRuleListener(String fkName) {
1858                super();
1859                checkArgument(metaData.foreignKeys.containsKey(fkName),"INVALID foreign key name %s",fkName);
1860                this.fkName = fkName;
1861                this.deleteRule = metaData.foreignKeys.get(fkName).deleteRule;
1862                this.foreignKeyIds = metaData.foreignKeyIdArrayOf(fkName);
1863        }
1864        @Override
1865        protected List<B> getImportedBeans(F bean) throws DaoException {
1866                return listenerContainer.isEmpty() 
1867                                ? Collections.<B>emptyList()
1868                                                : loadByForeignKeyAsList(bean,fkName,1,-1);
1869        }
1870                @Override
1871                protected void onRemove(List<B> effectBeans) throws DaoException {
1872                        switch (deleteRule) {
1873                        case SET_NULL:
1874                                for(B bean : effectBeans){
1875                                        for(int i=0;i<foreignKeyIds.length;++i){
1876                                                bean.setValue(foreignKeyIds[i], null);
1877                                        }
1878                                        Event.UPDATE_BEFORE.fire(listenerContainer, bean);
1879                                        Event.UPDATE.fire(listenerContainer, bean);
1880                                        bean.resetIsModified();
1881                                }                               
1882                                break;
1883                        case SET_DEFAULT:
1884                                B tmpl = createBean();
1885                                for(B bean : effectBeans){
1886                                        for(int i=0;i<foreignKeyIds.length;++i){
1887                                                bean.setValue(foreignKeyIds[i], tmpl.getValue(foreignKeyIds[i]));
1888                                        }
1889                        Event.UPDATE_BEFORE.fire(listenerContainer, bean);
1890                        Event.UPDATE.fire(listenerContainer, bean);
1891                        bean.resetIsModified();
1892                                }
1893                                break;
1894                        default:
1895                                if(!deleteRule.isNoAction()){
1896                                        Event event = Event.valueOf(deleteRule.eventOfDeleteRule);
1897                                        for(B bean : effectBeans){
1898                                                event.fire(listenerContainer, bean);
1899                                        }                                       
1900                                }
1901                                break;
1902                        }
1903                }
1904
1905                @Override
1906                public String toString() {
1907                        StringBuilder builder = new StringBuilder();
1908                        builder.append("DeleteRuleListener [");
1909                        if (fkName != null) {
1910                                builder.append("fkName=");
1911                                builder.append(fkName);
1912                                builder.append(", ");
1913                        }
1914                        if (deleteRule != null) {
1915                                builder.append("deleteRule=");
1916                                builder.append(deleteRule);
1917                        }
1918                        builder.append("]");
1919                        return builder.toString();
1920                }       
1921    };
1922
1923    //35
1924
1925    @Override
1926    public TableListener<B> registerListener(TableListener<B> listener)
1927    {
1928        this.listenerContainer.add(listener);
1929        return listener;
1930    }
1931
1932    //36
1933
1934    @Override
1935    public void unregisterListener(TableListener<B> listener)
1936    {
1937        this.listenerContainer.remove(listener);
1938    }
1939    
1940    //37
1941
1942    @Override
1943    public void fire(TableListener.Event event, B bean){
1944        checkNotNull(event,"event is null").fire(listenerContainer, bean);
1945    }
1946    
1947    //37-1
1948
1949    @Override
1950    public void fire(int event, B bean){
1951        try{
1952            fire(TableListener.Event.values()[event],bean);
1953        }catch(ArrayIndexOutOfBoundsException e){
1954            throw new IllegalArgumentException("invalid event id " + event);
1955        }        
1956    }
1957    
1958    //37-2
1959    /**
1960     * bind foreign key listener to foreign table: <br>
1961     * DELETE RULE <br>
1962     */
1963    public void bindForeignKeyListenerForDeleteRule(){
1964        for(DeleteRuleListener<BaseBean> listener : foreignKeyDeleteListeners){
1965                TableManager<BaseBean> manager = managerOf(metaData.foreignKeys.get(listener.fkName).foreignTable);
1966                manager.registerListener(listener);
1967        }
1968    }
1969    
1970  //37-3
1971    /**
1972     * unbind foreign key listener from all of foreign tables <br>
1973     * @see #bindForeignKeyListenerForDeleteRule()
1974     */
1975    public void unbindForeignKeyListenerForDeleteRule(){
1976        for(DeleteRuleListener<BaseBean> listener : foreignKeyDeleteListeners){
1977                TableManager<BaseBean> manager = managerOf(metaData.foreignKeys.get(listener.fkName).foreignTable);
1978                manager.unregisterListener(listener);
1979        }
1980    }
1981    //_____________________________________________________________________
1982    //
1983    // UTILS
1984    //_____________________________________________________________________
1985    //40
1986    /**
1987     * Retrieves the manager object used to get connections.
1988     *
1989     * @return the manager used
1990     */
1991    protected Manager getManager()
1992    {
1993        return Manager.getInstance();
1994    }
1995
1996    //41
1997    /**
1998     * Frees the connection.
1999     *
2000     * @param c the connection to release
2001     */
2002    protected void freeConnection(Connection c)
2003    {
2004        // back to pool
2005        this.getManager().releaseConnection(c);
2006    }
2007
2008    //42
2009    /**
2010     * Gets the connection.
2011     */
2012    protected Connection getConnection() throws DaoException
2013    {
2014        try
2015        {
2016            return this.getManager().getConnection();
2017        }
2018        catch(SQLException e)
2019        {
2020            throw new DataAccessException(e);
2021        }
2022    }
2023    
2024    private int loadBySqlForAction(String sql, Object[] argList, int[] fieldList,int startRow, int numRows,Action<B> action){
2025        PreparedStatement ps = null;
2026        Connection connection = null;
2027        try {
2028            connection = this.getConnection();
2029            if(debug){
2030                log("loadBySqlForAction:" + sql );
2031            }
2032            ps = connection.prepareStatement(sql,
2033                    ResultSet.TYPE_FORWARD_ONLY,
2034                    ResultSet.CONCUR_READ_ONLY);
2035            Manager.fillPrepareStatement(ps, argList);
2036            return this.loadByPreparedStatement(ps, fieldList, startRow, numRows, action);
2037        } catch (DaoException e) {
2038            throw new RuntimeDaoException(e);
2039        }catch (SQLException e) {
2040            throw new RuntimeDaoException(new DataAccessException(e));
2041        } finally {
2042            this.getManager().close(ps);
2043            this.freeConnection(connection);
2044        }
2045    }
2046
2047    @Override
2048        public List<BaseBean> runSqlAsList(String sql, Object... argList) throws RuntimeDaoException{
2049        return getManager().runSqlAsList(sql, argList);
2050    }
2051    
2052    @Override
2053        public List<Map<String, Object>> runSqlForMap(Map<String,Class<?>> targetTypes, String sql,Object... argList) throws RuntimeDaoException{
2054        return getManager().runSqlForMap(targetTypes, sql, argList);
2055    }
2056    
2057    @Override
2058        public <T>List<T> runSqlAsList(Class<T> targetType, String sql,Object... argList) throws RuntimeDaoException{
2059        return getManager().runSqlAsList(targetType, sql, argList);
2060    }
2061    
2062    @Override
2063        public <T> T runSqlForValue(Class<T> targetType,String sql, Object... argList) throws RuntimeDaoException{
2064        return getManager().runSqlForValue(targetType, sql, argList);
2065    }
2066    
2067    @Override
2068    public <T>T runAsTransaction(Callable<T> fun) {
2069       return getManager().runAsTransaction(fun);
2070    }
2071    
2072    @Override
2073    public void runAsTransaction(Runnable fun) {
2074        getManager().runAsTransaction(fun);
2075
2076    }    
2077    protected class DeleteBeanAction implements Action<B>{
2078        private final AtomicInteger count=new AtomicInteger(0);
2079        public DeleteBeanAction() {
2080                }
2081                @Override
2082        public void call(B bean){
2083                delete(bean);
2084                count.incrementAndGet();
2085        }
2086        public int getCount(){
2087            return count.get();
2088        }
2089    }
2090    
2091    //45 
2092
2093    /**
2094     * @param <T> PK type
2095     * @param type
2096     * @param beans
2097     * @return return a primary key list from B array
2098     * @see #toPrimaryKeyList(Class,Collection)
2099     */
2100    @SuppressWarnings("unchecked")
2101        protected <T> List<T> toPrimaryKeyList(Class<T>type,B... beans){        
2102        if(null == beans || beans.length ==0){
2103            return Collections.emptyList();
2104        }
2105        return toPrimaryKeyList(type,Arrays.asList(beans));
2106    }
2107    
2108    private class ColumnTransformer<T> implements Function<B, T>{
2109        private int columnId;
2110                public ColumnTransformer(int columnId) {
2111                        checkArgument(columnId >=0 && columnId < metaData.columnCount,"INVALID column id %s",columnId);
2112                        this.columnId = columnId;
2113                }
2114                @Override
2115                public T apply(B input) {
2116                        return input == null ? null : input.<T>getValue(columnId);
2117                }       
2118    }
2119    //46 
2120    /**
2121     * return a primary key list from B collection<br>
2122     * throw {@link UnsupportedOperationException} if there is more than a primary key
2123     * @param <T> PK type
2124     * @param type PK type
2125     * @param beans input beans
2126     */
2127    protected <T> List<T> toPrimaryKeyList(Class<T>type,Collection<B> beans){        
2128        if(metaData.primaryKeyNames.length != 1){
2129                throw new UnsupportedOperationException();
2130        }
2131        if(null == beans){
2132            return Collections.emptyList();
2133        }
2134        checkArgument(metaData.columnTypeOf(metaData.primaryKeyIds[0]) != type,"INVALID primary key type: " + type);
2135        return ImmutableList.copyOf(Iterables.transform(beans, new ColumnTransformer<T>(metaData.primaryKeyIds[0])));
2136    }
2137    private Object[] toPkValues(B bean,int[] selfFkIds){
2138        checkArgument(selfFkIds.length == metaData.primaryKeyNames.length,"MISMATCH SIZE of primary keys");
2139        return bean.asValueArray(selfFkIds);
2140    }
2141    
2142    protected List<B> listOfSelfRef(String fkName,Object... primaryKeys){
2143        List<B> list = new ArrayList<>();
2144        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2145        for(B ref = loadByPrimaryKey(primaryKeys)
2146                ; null != ref
2147                ; ref = loadByPrimaryKey(toPkValues(ref,selfFks))){
2148            list.add(ref);
2149            Object[] refPk = ref.primaryValues();
2150            Object[] refSelf = toPkValues(ref,selfFks);
2151            
2152            if(Arrays.equals(refPk,refSelf)
2153                        || (list.size() > 1 && Arrays.equals(refPk,primaryKeys))){
2154                // cycle reference
2155                break;
2156            }
2157        }
2158        Collections.reverse(list);
2159        return list;
2160    }
2161    protected int levelOfSelfRef(String fkName,Object... primaryKeys){
2162        int count = 0 ;
2163        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2164        for(B ref = loadByPrimaryKey(primaryKeys)
2165           ; null != ref
2166           ; ++count,ref = loadByPrimaryKey(toPkValues(ref,selfFks))){
2167            Object[] refPk = ref.primaryValues();
2168            Object[] refSelf = toPkValues(ref,selfFks);
2169            if(    (Arrays.equals(refPk,refSelf))
2170                || (count > 0 && Arrays.equals(refPk,primaryKeys))){
2171                // cycle reference
2172                return -1;
2173            }
2174        }
2175        return count;
2176    }
2177    protected B topOfSelfRef(String fkName,Object... primaryKeys){
2178        checkArgument(!hasNull(primaryKeys),"primaryKeys has null element");
2179        int[] selfFks = metaData.foreignKeyIdArrayOf(fkName); 
2180
2181        B ref = loadByPrimaryKey(primaryKeys);
2182        int count = 0 ;
2183        Object[] refSelf = toPkValues(ref,selfFks);
2184
2185        for(;null != ref && !hasNull(refSelf);){
2186            Object[] refPk = ref.primaryValues();
2187            refSelf = toPkValues(ref,selfFks);
2188
2189                if(   (Arrays.equals(refPk,refSelf))
2190                || (++count > 1 && Arrays.equals(refPk,primaryKeys))){
2191                // cycle reference
2192                throw new IllegalStateException("cycle on field: " + "parent");
2193            }
2194                checkState(Arrays.equals(refPk,refSelf)
2195                || (++count > 1 && Arrays.equals(refPk,primaryKeys)));
2196            ref = loadByPrimaryKeyChecked(refSelf);
2197        }
2198        return ref;
2199    }
2200    protected LinkedHashSet<B> doListOfChild(B bean, LinkedHashSet<B> set,String fkName){
2201        if(existsByPrimaryKey(bean)){
2202            checkState(!set.contains(bean),"cycle on foreign key: " + fkName);
2203            set.add(bean);
2204            List<B> childs = loadByForeignKeyAsList(bean, fkName, 1, -1);
2205            for(B c:childs){
2206                doListOfChild(c,set,fkName);
2207            }
2208        }
2209        return set;
2210    }
2211        @Override
2212        public int hashCode() {
2213                final int prime = 31;
2214                int result = 1;
2215                result = prime * result + ((metaData == null) ? 0 : metaData.hashCode());
2216                return result;
2217        }
2218
2219        @Override
2220        public boolean equals(Object obj) {
2221                if (this == obj) {
2222                        return true;
2223                }
2224                if (obj == null) {
2225                        return false;
2226                }
2227                if (getClass() != obj.getClass()) {
2228                        return false;
2229                }
2230                BaseTableManager<?> other = (BaseTableManager<?>) obj;
2231                if (metaData == null) {
2232                        if (other.metaData != null) {
2233                                return false;
2234                        }
2235                } else if (!metaData.equals(other.metaData)) {
2236                        return false;
2237                }
2238                return true;
2239        }
2240
2241        @Override
2242        public String toString() {
2243                StringBuilder builder = new StringBuilder();
2244                builder.append(getClass().getSimpleName() + " [metaData=");
2245                builder.append(metaData);
2246                builder.append("]");
2247                return builder.toString();
2248        }
2249
2250        /**
2251         * set debug flag that determine if output log message,default : false
2252         * @param debug flag for debug message output
2253         */
2254        public static void setDebug(boolean debug) {
2255                BaseTableManager.debug = debug;
2256        }
2257    private static final ImmutableMap<String, TableManager<? extends BaseBean>> 
2258        tableManagerInstances = loadTableManager(); 
2259    private static final  ImmutableMap<Class<?>, TableManager<? extends BaseBean>> 
2260        tableManagerTypeMaps = asTypeMap(tableManagerInstances);
2261    private static final  ImmutableMap<Class<?>, TableManager<? extends BaseBean>> 
2262        tableManagerBeanTypeMaps = asBeanTypeMap(tableManagerInstances);
2263        /**
2264         * SPI(Service Provider Interface)机制加载 {@link TableManager}所有实例
2265         * @return 表名和 {@link TableManager}实例的映射对象 
2266         */
2267        @SuppressWarnings({ "rawtypes" })
2268        private static ImmutableMap<String, TableManager<? extends BaseBean>> loadTableManager() {          
2269                ServiceLoader<TableManager> providers = ServiceLoader.load(TableManager.class);
2270                Iterator<TableManager> itor = providers.iterator();
2271                TableManager<?> node;
2272                ImmutableMap.Builder<String, TableManager<? extends BaseBean>> builder = ImmutableMap.builder();
2273                while(itor.hasNext()){
2274                        node = itor.next();
2275                        if(node instanceof BaseTableManager){
2276                                BaseTableManager<?> manager = (BaseTableManager<?>)node;
2277                                builder.put(manager.metaData.tablename, manager);
2278                        }
2279                }
2280                return builder.build();
2281        }
2282        private static final ImmutableMap<Class<?>, TableManager<? extends BaseBean>> asTypeMap(Map<String, TableManager<? extends BaseBean>>input){
2283                return Maps.uniqueIndex(input.values(), new Function<TableManager<? extends BaseBean>,Class<?>>(){
2284
2285                        @Override
2286                        public Class<?> apply(TableManager<? extends BaseBean> input) {
2287                                return input.getClass();
2288                        }});
2289        }
2290        private static final ImmutableMap<Class<?>, TableManager<? extends BaseBean>> asBeanTypeMap(Map<String, TableManager<? extends BaseBean>>input){
2291                return Maps.uniqueIndex(input.values(), new Function<TableManager<? extends BaseBean>,Class<?>>(){
2292
2293                        @Override
2294                        public Class<?> apply(TableManager<? extends BaseBean> input) {
2295                                return ((BaseTableManager<?>)input).metaData.beanType;
2296                        }});
2297        }
2298        public static ImmutableMap<String, TableManager<? extends BaseBean>> getTableManagers() {
2299                return tableManagerInstances;
2300        }
2301
2302        @SuppressWarnings("unchecked")
2303        public static final <M extends TableManager<?>>M 
2304        getTableManager(Class<M>managerType) {
2305                TableManager<? extends BaseBean> manager = tableManagerTypeMaps.get(managerType);
2306                return checkNotNull((M) manager,"INVALID manager type %s",managerType);
2307        }
2308        @SuppressWarnings("unchecked")
2309        public static final <T extends BaseBean,M extends TableManager<T>>M 
2310        getTableManagerByBeanType(Class<T>beanType) {
2311                TableManager<? extends BaseBean> manager = tableManagerBeanTypeMaps.get(beanType);
2312                return checkNotNull((M) manager,"INVALID bean type %s",beanType);
2313        }
2314        @SuppressWarnings("unchecked")
2315        public static final <M extends TableManager<?>>M 
2316        getTableManager(String tablename) {
2317                TableManager<? extends BaseBean> manager = tableManagerInstances.get(tablename);
2318                return checkNotNull((M) manager,"INVALID tablename %s",tablename);
2319        }
2320        
2321    private static final  Map<Class<?>, TableManager<? extends BaseBean>> cacheManagers = Maps.newHashMap();
2322    private static final  Map<Class<?>, TableManager<? extends BaseBean>> cacheBeanTypeManagers = Maps.newHashMap();
2323    private static final  Map<String, TableManager<? extends BaseBean>> cacheNameManagers = Maps.newHashMap();
2324        /**
2325         * 注册cache manager
2326         * @param cacheManager
2327         */
2328        public synchronized static void registerCacheManager(ICacheManager cacheManager) {              
2329                if(cacheManager instanceof BaseTableManager<?>){
2330                        BaseTableManager<?> manager = (BaseTableManager<?>)cacheManager;
2331                        Class<? extends ICacheManager> clazz = cacheManager.getClass();
2332                        cacheManagers.put(clazz.getSuperclass(), manager);
2333                        cacheNameManagers.put(manager.metaData.tablename, manager);
2334                        cacheBeanTypeManagers.put(manager.metaData.beanType, manager);
2335                }
2336        }
2337
2338        public static Map<Class<?>, TableManager<? extends BaseBean>> getCacheManagers() {
2339                return Collections.unmodifiableMap(cacheManagers);
2340        }
2341
2342        /**
2343         * 根据目标类型返回对应的 {@link TableManager}实例
2344         * @param targetType 目标类型
2345         * @return {@link TableManager}实例
2346         * @throws NoSuchElementException 找不到时抛出异常
2347         */
2348        @SuppressWarnings("unchecked")
2349        public static final <M extends TableManager<? extends BaseBean>>M 
2350        getCacheManager(final Class<M>targetType) throws NoSuchElementException {
2351                checkArgument(targetType != null,"targetType is null");
2352                TableManager<? extends BaseBean> manager;
2353                if(null != (manager = cacheManagers.get(targetType))){
2354                        return (M) manager;
2355                }
2356                return (M) Iterables.find(cacheManagers.values(), new Predicate<TableManager<?>>() {
2357
2358                        @Override
2359                        public boolean apply(TableManager<?> input) {
2360                                return targetType.isInstance(input);
2361                        }
2362                });
2363        }
2364        @SuppressWarnings("unchecked")
2365        public static final <M extends TableManager<?>> M 
2366        getCacheManagerByBeanType(Class<?> beanType)  {
2367                TableManager<? extends BaseBean> manager = cacheBeanTypeManagers.get(beanType);
2368                return (M) checkNotNull(manager,"INVALID bean type %s",beanType);
2369        }
2370        @SuppressWarnings("unchecked")
2371        public static final <M extends TableManager<?>> M 
2372        getCacheManager(String tablename)  {
2373                TableManager<? extends BaseBean> manager = cacheNameManagers.get(tablename);
2374                return (M) checkNotNull(manager,"INVALID table name %s",tablename);
2375        }
2376
2377        public static <T extends BaseBean,M extends TableManager<? extends BaseBean>>M 
2378        instanceOf(Class<M>targetType) {
2379            try {
2380                return getCacheManager(targetType);
2381            } catch (Exception e) {
2382                return getTableManager(targetType);
2383            } 
2384        }
2385
2386        public static <M extends TableManager<? extends BaseBean>>M managerOf(String tablename) {
2387            try {
2388                return getCacheManager(tablename);
2389            } catch (Exception e) {
2390                return getTableManager(tablename);
2391            } 
2392        }
2393
2394        public static <T extends BaseBean,M extends TableManager<T>>M managerOf(Class<T> beanType) {
2395            try {
2396                return getCacheManagerByBeanType(beanType);
2397            } catch (Exception e) {
2398                return getTableManagerByBeanType(beanType);
2399            } 
2400        }
2401}