001package gu.sql2java;
002
003import java.io.PrintWriter;
004import java.io.StringWriter;
005import java.lang.reflect.Method;
006import java.nio.ByteBuffer;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Date;
010import java.util.Hashtable;
011import java.util.List;
012import java.util.Map;
013
014import com.google.common.primitives.Primitives;
015
016/**
017 * implementation of {@link IBeanConverter} by reflect<br>
018 * generic type converter between L and R <br>
019 * @author guyadong
020 * @param <L> left type extends BaseBean 
021 * @param <R> right type
022 *
023 */
024public class BeanConverter<L extends BaseBean,R> extends IBeanConverter.AbstractHandle<L,R>  implements Constant{
025        static final String GET_INITIALIZED = "getInitialized";
026        static final String SET_INITIALIZED = "setInitialized";
027        static final String GET_MODIFIED = "getModified";
028        static final String SET_MODIFIED = "setModified";
029        static final String SET_NEW = "setNew";
030        static final String IS_NEW = "isNew";
031        private final Map<String,Method> methods = new Hashtable<String,Method>();
032        private final Map<String,Integer> rightIndexs = new Hashtable<String,Integer>();
033        private final Map<String, Class<?>> setterParams = new Hashtable<String,Class<?>>();
034        private final RowMetaData metaData;
035        private boolean bitCheck(String name,int...bits){
036                Integer id = rightIndexs.get(name);
037                return (null == id)?false:bitCheck(id.intValue(),bits);
038        }
039        private int[] bitOR(String name,int... bits){
040                return bitOR(rightIndexs.get(name),bits);
041        }
042        private void getGetter(String name){
043                try{
044                        methods.put(name,rightType.getMethod(name));
045                }catch(NoSuchMethodException e){}
046        }
047        private void getSetter(String name, Class<?>...types) throws NoSuchMethodException{
048                for(Class<?>paramType:types){
049                        try{
050                                methods.put(name,rightType.getMethod(name,paramType));
051                                setterParams.put(name, paramType);
052                                return;
053                        }catch(NoSuchMethodException e){
054                                continue;
055                        }
056                }
057                throw new NoSuchMethodException();
058        }
059        private void getSetterNoThrow(String name, Class<?>...types){
060                try{
061                        getSetter(name,types);
062                }catch(NoSuchMethodException e){}
063        }
064        /**
065         * constructor
066         * @param leftClass
067         * @param rightClass
068         * @param javaFields a comma splice string,including all field name of R,<br>
069         *                   if null or empty, use default string:{@link RowMetaData#columnJavaNames}
070         */
071        public BeanConverter (Class<L> leftClass, Class<R> rightClass,String javaFields){
072                super(leftClass,rightClass);
073                this.metaData = RowMetaData.getMetaData(leftClass);
074                init(javaFields);
075        }
076        /** @see #BeanConverter(Class,Class,String) */
077        public BeanConverter (Class<L> leftClass, Class<R> rightClass){
078                this(leftClass,rightClass,null);
079        }
080        private void init(String javaFields){
081                List<String>rightFields;
082                if(null == javaFields || javaFields.isEmpty()){
083                        rightFields = metaData.columnJavaNames;
084                }else{
085                        rightFields = Arrays.asList(javaFields.split(","));
086                }
087                for(int i = 0 ; i < rightFields.size(); ++i){
088                        String field = rightFields.get(i).trim();
089                        if(!field.matches("\\w+")){
090                                throw new IllegalArgumentException("invalid 'javaFields':" + javaFields);
091                        }
092                        rightIndexs.put(field,i);
093                }
094                try{
095                        methods.put(IS_NEW,rightType.getMethod(IS_NEW));
096                        methods.put(GET_INITIALIZED,rightType.getMethod(GET_INITIALIZED));
097                        getSetter(SET_NEW,boolean.class);
098                        if(rightIndexs.size() > STATE_BIT_NUM){
099                                getSetter(SET_INITIALIZED,int[].class,List.class);
100                        }else{
101                                getSetter(SET_INITIALIZED,int.class);
102                        }
103                        getGetter(GET_MODIFIED);
104                        if(rightIndexs.size() > STATE_BIT_NUM){
105                                getSetter(SET_MODIFIED,int[].class,List.class);
106                        }else{
107                                getSetter(SET_MODIFIED,int.class);
108                        }
109                }catch(NoSuchMethodException e){
110                        throw new RuntimeException(e);
111                }
112
113                for(int i =0; i<metaData.columnCount; ++i){
114                        getGetter(metaData.getterMethods.get(i).getName());
115                        Method setter = metaData.setterMethods.get(i);
116                        Class<?> type = setter.getParameterTypes()[0];
117                        if(type.isPrimitive()){
118                                if(Date.class.isAssignableFrom(type)){
119                                        getSetterNoThrow(setter.getName(),type,long.class);           
120                                }
121                                else{
122                                        getSetterNoThrow(setter.getName(),type,Primitives.unwrap(type));
123                                }
124                        }else if(ByteBuffer.class.isAssignableFrom(type) || byte[].class.isAssignableFrom(type)){
125                                getSetterNoThrow(setter.getName(),type,ByteBuffer.class,byte[].class);
126                        } else {
127                                getSetterNoThrow(setter.getName(),type);                    
128                        }
129                }
130        }
131        @Override
132        protected void doFromRight(L left, R right) {
133                try{
134                        Method getterMethod;
135                        left.resetIsModified();
136                        int selfModified = 0;
137                        int[] initialized;
138                        int[] modified;
139                        if(rightIndexs.size() > STATE_BIT_NUM){
140                                initialized = (int[])methods.get(GET_INITIALIZED).invoke(right);
141                                modified = (int[])methods.get(GET_MODIFIED).invoke(right);
142                        }else{
143                                initialized = new int[]{(Integer)methods.get(GET_INITIALIZED).invoke(right)};
144                                modified = new int[]{(Integer)methods.get(GET_MODIFIED).invoke(right)};
145                        }
146
147                        for(int columnId = 0; columnId < metaData.columnCount; ++columnId){
148                                String columnName = metaData.columnNameOf(columnId);
149                                if(bitCheck(columnName,initialized)){
150                                        String getterName = metaData.getterMethods.get(columnId).getName();
151                                        if(null != (getterMethod = methods.get(getterName))){
152                                                left.setValue(columnId, getterMethod.invoke(right));
153                                                if(bitCheck(columnName,modified)){
154                                                        selfModified |= (1<<columnId);
155                                                }
156                                        }
157                                }
158                        }
159                        left.setNew((Boolean)methods.get(IS_NEW).invoke(right));
160                        left.setModified(selfModified);
161                }catch(RuntimeException e){
162                        throw e;
163                }catch(Exception e){
164                        throw new RuntimeException(e);
165                }
166        }
167
168        @Override
169        protected void doToRight(L left, R right) {
170                try{
171                        Method setterMethod;
172                        int[] initialized = new int[(rightIndexs.size() + STATE_BIT_NUM - 1)>>STATE_BIT_SHIFT];
173                        int[] modified = new int[(rightIndexs.size() + STATE_BIT_NUM - 1)>>STATE_BIT_SHIFT];
174                        Arrays.fill(initialized, 0);
175                        Arrays.fill(modified, 0);
176                        for(int columnId = 0; columnId < metaData.columnCount; ++columnId){
177                                String columnName = metaData.columnNameOf(columnId);
178                                String setterName = metaData.setterMethods.get(columnId).getName();
179                                if(null != (setterMethod = methods.get(setterName)) && left.isInitialized(columnId)){
180                                        try{
181                                                setterMethod.invoke(right,cast(setterParams.get(setterName),left.getValue(columnId)));
182                                                bitOR(columnName,initialized);
183                                                if(left.isModified(columnId)){
184                                                        bitOR(columnName,modified);
185                                                }
186                                        }catch(NullCastPrimitiveException e){}
187                                }
188                        }
189                        // IGNORE field fl_device.create_time , controlled by 'general.beanconverter.tonative.ignore' in properties file
190                        /*
191                if(null != (setterMethod = methods.get(Column.createTime.setter)) && left.checkCreateTimeInitialized()){
192                    try{
193                        setterMethod.invoke(right,cast(setterParams.get(Column.createTime.setter),left.getCreateTime()));
194                        bitOR(Column.createTime.name(),initialized);
195                        if(left.checkCreateTimeModified()){
196                            bitOR(Column.createTime.name(),modified);
197                        }
198                    }catch(NullCastPrimitiveException e){}
199                }
200                         */
201                        // IGNORE field fl_device.update_time , controlled by 'general.beanconverter.tonative.ignore' in properties file
202                        /*
203                if(null != (setterMethod = methods.get(Column.updateTime.setter)) && left.checkUpdateTimeInitialized()){
204                    try{
205                        setterMethod.invoke(right,cast(setterParams.get(Column.updateTime.setter),left.getUpdateTime()));
206                        bitOR(Column.updateTime.name(),initialized);
207                        if(left.checkUpdateTimeModified()){
208                            bitOR(Column.updateTime.name(),modified);
209                        }
210                    }catch(NullCastPrimitiveException e){}
211                }
212                         */
213                        if(null != (setterMethod = methods.get(SET_MODIFIED))){
214                                if( initialized.length > 1){
215                                        setterMethod.invoke(right,cast(setterParams.get(SET_MODIFIED),initialized));
216                                }else{
217                                        setterMethod.invoke(right,initialized[0]);
218                                }
219                        }
220                        methods.get(SET_NEW).invoke(right,left.isNew());
221                        if( initialized.length > 1){
222                                methods.get(SET_INITIALIZED).invoke(right,cast(setterParams.get(SET_INITIALIZED),initialized));
223                                methods.get(SET_MODIFIED).invoke(right,cast(setterParams.get(SET_MODIFIED),modified));
224                        }else{
225                                methods.get(SET_INITIALIZED).invoke(right,initialized[0]);
226                                methods.get(SET_MODIFIED).invoke(right,modified[0]);
227                        }
228                }catch(RuntimeException e){
229                        throw e;
230                }catch(Exception e){
231                        throw new RuntimeException(e);
232                }
233        }
234        /**
235         * 返回buffer中所有字节(position~limit),不改变buffer状态
236         * @param buffer
237         * @return byte array
238         */
239        private static final byte[] getBytesInBuffer(ByteBuffer buffer){
240                int pos = buffer.position();
241                try{
242                        byte[] bytes = new byte[buffer.remaining()];
243                        buffer.get(bytes);
244                        return bytes;
245                }finally{
246                        buffer.position(pos);
247                }
248        }
249        private static final List<Long> toList(long[] array) {
250                ArrayList<Long> result = new ArrayList<Long>(array.length);
251                for (int i = 0; i < array.length; i++) {
252                        result.add(new Long(array[i]));
253                }
254                return result;
255        }
256        private static final long[] toPrimitive(List<Long> list) {        
257                long[] dst = new long[list.size()];
258                Long element;
259                for (int i = 0; i < dst.length; i++) {
260                        if(null == (element = list.get(i))){
261                                throw new IllegalArgumentException("can't cast List<Long> to long[] because of null element");
262                        }
263                        dst[i] = element.longValue();
264                }
265                return dst;
266        }
267        /**
268         * {@code source}转为{@code type}指定的类型
269         * @param type destination type
270         * @param source  source object
271         * @return T instance
272         */
273        @SuppressWarnings({ "unchecked" })
274        static final <T> T cast(Class<T> type,Object source){
275                try{
276                        if(null ==source && type.isPrimitive()){
277                                throw new NullCastPrimitiveException(String.format("can't convert null to primitive type %s",type.getSimpleName()));
278                        }
279                        return (T) source;
280                }catch(ClassCastException cce){
281                        // long[] -> List  
282                        if(List.class.isAssignableFrom(type) && null != source && source instanceof long[]){
283                                return (T) toList((long[]) source);
284                        }
285                        // List -> long[]   
286                        if(long[].class == type && null != source && source instanceof List){
287                                return (T) toPrimitive( (List<Long>) source);
288                        }
289                        // Long -> Date
290                        if(java.util.Date.class.isAssignableFrom(type) && null != source && source instanceof Long){
291                                try {
292                                        // call constructor,such as  java.util.Date#Date(long), java.sql.Time.Time(long)
293                                        return type.getConstructor(long.class).newInstance(source);
294                                } catch (Exception e) {
295                                        StringWriter writer = new StringWriter();
296                                        e.printStackTrace(new PrintWriter(writer));
297                                        throw new ClassCastException(writer.toString());
298                                }
299                        }
300                        // Date -> Long,long
301                        if( long.class == type || Long.class == type){
302                                if(null != source && source instanceof java.util.Date){
303                                        Long time = ((java.util.Date)source).getTime();
304                                        return (T)time;
305                                }
306                        }
307                        // byte[] -> ByteBuffer
308                        if(ByteBuffer.class == type && null != source && source instanceof byte[]){
309                                return (T) ByteBuffer.wrap((byte[]) source);
310                        }
311                        // ByteBuffer -> byte[]
312                        if(byte[].class == type && null != source && source instanceof ByteBuffer){
313                                return (T) getBytesInBuffer((ByteBuffer) source);
314                        }
315                        throw cce;
316                }
317        }
318        static final boolean bitCheck(int index,int...bits){
319                return 0 != (bits[index>>STATE_BIT_SHIFT]&(1<<(index&0x1f)));
320        }
321        static final int[] bitOR(int index,int... bits){
322                bits[index>>STATE_BIT_SHIFT] |= (1<<(index&0x1f));
323                return bits;
324        }
325        private static class NullCastPrimitiveException extends ClassCastException {
326                private static final long serialVersionUID = 1L;
327                NullCastPrimitiveException(String message) {
328                        super(message);
329                }
330        }
331}
332