001    /*
002    @license.text@
003     */
004    package biz.hammurapi.sql;
005    
006    import java.lang.reflect.Constructor;
007    import java.sql.ResultSet;
008    import java.sql.SQLException;
009    import java.util.Map;
010    
011    /**
012     * This projector constructs objects using database field values
013     * @author Pavel Vlasov
014     * @version $Revision: 1.2 $
015     */
016    public class ConstructorProjector extends BaseReflectionProjector implements Projector {
017            public static class ColumnName {
018                    private String name;
019    
020                    public ColumnName(String name) {
021                            this.name=name;
022                    }
023            }
024            
025            private Object[] args;
026            private Constructor constructor;
027    
028            /**
029             * Use this constructor if target constructor shall take not only values from database fields as
030             * parameter, but other objects as well.
031             * @param constructor Constructor to instantiate object.
032             * @param args Constructor arguments. Arguments of type {@link ColumnName} will be replaced
033             * with values from corresponding fields from the database.
034             * @param typeMap See {@link java.sql.ResultSet#getObject(java.lang.String, java.util.Map)}. Can be null.
035             */
036            public ConstructorProjector(Constructor constructor, Object[] args, Map typeMap) {
037                    super(typeMap);
038                    if (constructor.getParameterTypes().length!=args.length) {
039                            throw new IllegalArgumentException("argNames length shall be equal to the number of constructor parameters");
040                    }
041                    this.constructor=constructor;
042                    this.args=args;
043            }
044            
045            /**
046             * Use this constructor if only database fields values are used as target constructor parameters.
047             * @param constructor Constructor to instantiate object
048             * @param columnNames Names of columns that shall be passed as parameters to constructor.
049             * @param typeMap See {@link java.sql.ResultSet#getObject(java.lang.String, java.util.Map)}. Can be null.
050             */
051            public ConstructorProjector(Constructor constructor, String[] columnNames, Map typeMap) {
052                    this(constructor, columnNamesToArgs(columnNames), typeMap);
053            }
054            
055            /**
056             * Use constructor for positioned projection. Column values will be passed to as constructor
057             * arguments according to their position.
058             * @param constructor
059             * @param typeMap
060             */
061            public ConstructorProjector(Constructor constructor, Map typeMap) {
062                    super(typeMap);
063                    this.constructor=constructor;
064            }
065    
066            /**
067             * 
068             * @param fieldNames
069             * @return
070             */
071            private static Object[] columnNamesToArgs(String[] fieldNames) {
072                    Object[] ret=new Object[fieldNames.length];
073                    for (int i=0; i<fieldNames.length; i++) {
074                            ret[i]=new ColumnName(fieldNames[i]);
075                    }
076                    return ret;
077            }
078    
079            public Object project(ResultSet rs) throws SQLException {
080                    Object[] params=new Object[constructor.getParameterTypes().length];
081                    for (int i=0; i<params.length; i++) {
082                            if (args==null) {
083                                    params[i]=getColumn(rs, i+1);
084                            } else if (args[i] instanceof ColumnName) {
085                                    params[i]=getColumn(rs, ((ColumnName) args[i]).name);   
086                            } else {
087                                    params[i]=args[i];
088                            }                       
089                    }
090                    
091                    try {
092                            if (!constructor.isAccessible()) {
093                                    constructor.setAccessible(true); // To use private nested classes.
094                            }
095                            return constructor.newInstance(params);
096                    } catch (Exception e) {
097                            throw new SQLExceptionEx(e);
098                    }
099            }
100    
101    }