001    /*
002     * The MIT License
003     *
004     * Copyright (c) 2012, Ninja Squad
005     *
006     * Permission is hereby granted, free of charge, to any person obtaining a copy
007     * of this software and associated documentation files (the "Software"), to deal
008     * in the Software without restriction, including without limitation the rights
009     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010     * copies of the Software, and to permit persons to whom the Software is
011     * furnished to do so, subject to the following conditions:
012     *
013     * The above copyright notice and this permission notice shall be included in
014     * all copies or substantial portions of the Software.
015     *
016     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022     * THE SOFTWARE.
023     */
024    
025    package com.ninja_squad.dbsetup.bind;
026    
027    import java.math.BigDecimal;
028    import java.math.BigInteger;
029    import java.sql.Date;
030    import java.sql.Time;
031    import java.sql.Timestamp;
032    import java.sql.Types;
033    import java.util.Calendar;
034    
035    /**
036     * Utility class allowing to get various kinds of binders. The {@link DefaultBinderConfiguration} uses binders
037     * returned by this class, based on the type of the parameter.
038     * @author JB Nizet
039     */
040    public final class Binders {
041    
042        private static final Binder DEFAULT_BINDER = new DefaultBinder();
043        private static final Binder DATE_BINDER = new DateBinder();
044        private static final Binder TIMESTAMP_BINDER = new TimestampBinder();
045        private static final Binder DECIMAL_BINDER = new DecimalBinder();
046        private static final Binder INTEGER_BINDER = new IntegerBinder();
047        private static final Binder TIME_BINDER = new TimeBinder();
048        private static final Binder STRING_BINDER = new StringBinder();
049    
050        private Binders() {
051        }
052    
053        /**
054         * Returns the default binder. This binder is normally used for columns of a type that is not handled by the other
055         * binders. It is also used when the metadata are not used and the Insert thus doesn't know the type of the column.
056         * It simply uses <code>stmt.setObject()</code> to bind the parameter, except if the value being bound is of some
057         * some well-known type not handled by JDBC:
058         * <ul>
059         *     <li><code>enum</code>: the name of the enum is bound</li>
060         *     <li><code>java.util.Date</code>: the date is transformed to a java.sql.Timestamp</li>
061         *     <li><code>java.util.Calendar</code>: the date is transformed to a java.sql.Timestamp</li>
062         * </ul>
063         */
064        public static Binder defaultBinder() {
065            return DEFAULT_BINDER;
066        }
067    
068        /**
069         * Returns a binder suitable for columns of type CHAR and VARCHAR. The returned binder supports values of type
070         * <ul>
071         *   <li><code>String</code></li>
072         *   <li><code>enum</code>: the name of the enum is used as bound value</li>
073         *   <li><code>Object</code>: the <code>toString()</code> of the object is used as bound value</li>
074         * </ul>
075         */
076        public static Binder stringBinder() {
077            return STRING_BINDER;
078        }
079    
080        /**
081         * Returns a binder suitable for columns of type DATE. The returned binder supports values of type
082         * <ul>
083         *   <li><code>java.sql.Date</code></li>
084         *   <li><code>java.util.Date</code>: the milliseconds of the date are used to construct a java.sql.Date</li>
085         *   <li><code>java.util.Calendar</code>: the milliseconds of the calendar are used to construct a java.sql.Date
086         *   </li>
087         *   <li><code>String</code>: the string is transformed to a java.sql.Date using the <code>Date.valueOf()</code>
088         *       method</li>
089         * </ul>
090         * If the value is none of these types, <code>stmt.setObject()</code> is used to bind the value.
091         */
092        public static Binder dateBinder() {
093            return DATE_BINDER;
094        }
095    
096        /**
097         * Returns a binder suitable for columns of type TIMESTAMP. The returned binder supports values of type
098         * <ul>
099         *   <li><code>java.sql.Timestamp</code></li>
100         *   <li><code>java.util.Date</code>: the milliseconds of the date are used to construct a java.sql.Timestamp</li>
101         *   <li><code>java.util.Calendar: the milliseconds of the calendar are used to construct a
102         *   java.sql.Timestamp</code></li>
103         *   <li><code>String</code>: the string is transformed to a <code>java.sql.Timestamp</code> using the
104         *       <code>Timestamp.valueOf()</code> method, or using the <code>java.sql.Date.valueOf()</code> method if the
105         *       string has less than 19 characters</li>
106         * </ul>
107         * If the value is none of these types, <code>stmt.setObject()</code> is used to bind the value.
108         */
109        public static Binder timestampBinder() {
110            return TIMESTAMP_BINDER;
111        }
112    
113        /**
114         * Returns a binder suitable for columns of type TIME. The returned binder supports values of type
115         * <ul>
116         *   <li><code>java.sql.Time</code></li>
117         *   <li><code>java.util.Date</code>: the milliseconds of the date are used to construct a
118         *      <code>java.sql.Time</code></li>
119         *   <li><code>java.util.Calendar</code>: the milliseconds of the calendar are used to construct a
120         *      <code>java.sql.Time</code>
121         *   </li>
122         *   <li><code>String</code>: the string is transformed to a java.sql.Time using the
123         *       <code>Time.valueOf()</code> method</li>
124         * </ul>
125         * If the value is none of these types, <code>stmt.setObject()</code> is used to bind the value.
126         */
127        public static Binder timeBinder() {
128            return TIME_BINDER;
129        }
130    
131        /**
132         * Returns a binder suitable for numeric, decimal columns. The returned binder supports values of type
133         * <ul>
134         *   <li><code>String</code>: the string is transformed to a java.math.BigDecimal using its constructor</li>
135         * </ul>
136         * If the value is none of these types, <code>stmt.setObject()</code> is used to bind the value.
137         */
138        public static Binder decimalBinder() {
139            return DECIMAL_BINDER;
140        }
141    
142        /**
143         * Returns a binder suitable for numeric, integer columns. The returned binder supports values of type
144         * <ul>
145         *   <li><code>BigInteger</code>: the object is transformed to a String and bound using
146         *       <code>stmt.setObject()</code>, with <code>BIGINT</code> as target type.
147         *   </li>
148         *   <li><code>enum</code>: the enum is transformed into an integer by taking its ordinal</li>
149         *   <li><code>String</code>: the string is bound using <code>stmt.setObject()</code>, with <code>BIGINT</code> as
150         *       target type.
151         *   </li>
152         * </ul>
153         * If the value is none of these types, <code>stmt.setObject()</code> is used to bind the value.
154         */
155        public static Binder integerBinder() {
156            return INTEGER_BINDER;
157        }
158    
159        /**
160         * The implementation for {@link Binders#stringBinder()}
161         * @author JB Nizet
162         */
163        private static final class StringBinder implements Binder {
164            @Override
165            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
166                if (value instanceof String) {
167                    stmt.setString(param, (String) value);
168                }
169                else if (value instanceof Enum<?>) {
170                    stmt.setString(param, ((Enum<?>) value).name());
171                }
172                else if (value == null) {
173                    stmt.setObject(param, null);
174                }
175                else {
176                    stmt.setString(param, value.toString());
177                }
178            }
179    
180            @Override
181            public String toString() {
182                return "Binders.stringBinder";
183            }
184        }
185    
186        /**
187         * The implementation for {@link Binders#timeBinder()}
188         * @author JB Nizet
189         */
190        private static final class TimeBinder implements Binder {
191            @Override
192            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
193                if (value instanceof Time) {
194                    stmt.setTime(param, (Time) value);
195                }
196                else if (value instanceof java.util.Date) {
197                    stmt.setTime(param, new Time(((java.util.Date) value).getTime()));
198                }
199                else if (value instanceof Calendar) {
200                    stmt.setTime(param, new Time(((Calendar) value).getTimeInMillis()));
201                }
202                else if (value instanceof String) {
203                    stmt.setTime(param, Time.valueOf((String) value));
204                }
205                else {
206                    stmt.setObject(param, value);
207                }
208            }
209    
210            @Override
211            public String toString() {
212                return "Binders.timeBinder";
213            }
214        }
215    
216        /**
217         * The implementation for {@link Binders#integerBinder()}
218         * @author JB Nizet
219         */
220        private static final class IntegerBinder implements Binder {
221            @Override
222            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
223                if (value instanceof BigInteger) {
224                    stmt.setObject(param, value.toString(), Types.BIGINT);
225                }
226                else if (value instanceof Enum<?>) {
227                    stmt.setInt(param, ((Enum<?>) value).ordinal());
228                }
229                else if (value instanceof String) {
230                    stmt.setObject(param, value, Types.BIGINT);
231                }
232                else {
233                    stmt.setObject(param, value);
234                }
235            }
236    
237            @Override
238            public String toString() {
239                return "Binders.integerBinder";
240            }
241        }
242    
243        /**
244         * The implementation for {@link Binders#decimalBinder()}
245         * @author JB Nizet
246         */
247        private static final class DecimalBinder implements Binder {
248            @Override
249            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
250                if (value instanceof String) {
251                    stmt.setBigDecimal(param, new BigDecimal((String) value));
252                }
253                else {
254                    stmt.setObject(param, value);
255                }
256            }
257    
258            @Override
259            public String toString() {
260                return "Binders.decimalBinder";
261            }
262        }
263    
264        /**
265         * The implementation for {@link Binders#timestampBinder()}
266         * @author JB Nizet
267         */
268        private static final class TimestampBinder implements Binder {
269            // the number of chars in yyyy-mm-dd hh:mm:ss
270            private static final int MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP = 19;
271    
272            @Override
273            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
274                if (value instanceof Timestamp) {
275                    stmt.setTimestamp(param, (Timestamp) value);
276                }
277                else if (value instanceof java.util.Date) {
278                    stmt.setTimestamp(param, new Timestamp(((java.util.Date) value).getTime()));
279                }
280                else if (value instanceof Calendar) {
281                    stmt.setTimestamp(param, new Timestamp(((Calendar) value).getTimeInMillis()));
282                }
283                else if (value instanceof String) {
284                    String valueAsString = (String) value;
285                    if (valueAsString.length() >= MIN_NUMBER_OF_CHARS_FOR_TIMESTAMP) {
286                        stmt.setTimestamp(param, Timestamp.valueOf(valueAsString));
287                    }
288                    else {
289                        Date valueAsDate = Date.valueOf(valueAsString);
290                        stmt.setTimestamp(param, new Timestamp(valueAsDate.getTime()));
291                    }
292                }
293                else {
294                    stmt.setObject(param, value);
295                }
296            }
297    
298            @Override
299            public String toString() {
300                return "Binders.timestampBinder";
301            }
302        }
303    
304        /**
305         * The implementation for {@link Binders#dateBinder()}
306         * @author JB Nizet
307         */
308        private static final class DateBinder implements Binder {
309            @Override
310            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
311                if (value instanceof Date) {
312                    stmt.setDate(param, (Date) value);
313                }
314                else if (value instanceof java.util.Date) {
315                    stmt.setDate(param, new Date(((java.util.Date) value).getTime()));
316                }
317                else if (value instanceof Calendar) {
318                    stmt.setDate(param, new Date(((Calendar) value).getTimeInMillis()));
319                }
320                else if (value instanceof String) {
321                    stmt.setDate(param, Date.valueOf((String) value));
322                }
323                else {
324                    stmt.setObject(param, value);
325                }
326            }
327    
328            @Override
329            public String toString() {
330                return "Binders.dateBinder";
331            }
332        }
333    
334        /**
335         * The implementation for {@link Binders#defaultBinder()}
336         * @author JB Nizet
337         */
338        private static final class DefaultBinder implements Binder {
339            @Override
340            public void bind(java.sql.PreparedStatement stmt, int param, Object value) throws java.sql.SQLException {
341                Object boundValue;
342                if (value instanceof Enum) {
343                    boundValue = ((Enum) value).name();
344                }
345                else if (value instanceof java.util.Date) {
346                    boundValue = new Timestamp(((java.util.Date) value).getTime());
347                }
348                else if (value instanceof Calendar) {
349                    boundValue = new Timestamp(((Calendar) value).getTime().getTime());
350                }
351                else {
352                    boundValue = value;
353                }
354                stmt.setObject(param, boundValue);
355            }
356    
357            @Override
358            public String toString() {
359                return "Binders.defaultBinder";
360            }
361        }
362    }