View Javadoc
1   /*
2    * Copyright 2004-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.ibatis.sqlmap.engine.mapping.parameter;
17  
18  import com.ibatis.common.beans.Probe;
19  import com.ibatis.common.beans.ProbeFactory;
20  import com.ibatis.common.resources.Resources;
21  import com.ibatis.sqlmap.client.SqlMapException;
22  import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;
23  import com.ibatis.sqlmap.engine.mapping.sql.SqlText;
24  import com.ibatis.sqlmap.engine.type.CustomTypeHandler;
25  import com.ibatis.sqlmap.engine.type.DomTypeMarker;
26  import com.ibatis.sqlmap.engine.type.TypeHandler;
27  import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.StringTokenizer;
32  
33  /**
34   * The Class InlineParameterMapParser.
35   */
36  public class InlineParameterMapParser {
37  
38    /** The Constant PROBE. */
39    private static final Probe PROBE = ProbeFactory.getProbe();
40  
41    /** The Constant PARAMETER_TOKEN. */
42    private static final String PARAMETER_TOKEN = "#";
43  
44    /** The Constant PARAM_DELIM. */
45    private static final String PARAM_DELIM = ":";
46  
47    /**
48     * Parses the inline parameter map.
49     *
50     * @param typeHandlerFactory
51     *          the type handler factory
52     * @param sqlStatement
53     *          the sql statement
54     *
55     * @return the sql text
56     */
57    public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement) {
58      return parseInlineParameterMap(typeHandlerFactory, sqlStatement, null);
59    }
60  
61    /**
62     * Parses the inline parameter map.
63     *
64     * @param typeHandlerFactory
65     *          the type handler factory
66     * @param sqlStatement
67     *          the sql statement
68     * @param parameterClass
69     *          the parameter class
70     *
71     * @return the sql text
72     */
73    public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement,
74        Class parameterClass) {
75  
76      List mappingList = new ArrayList<>();
77  
78      StringTokenizer parser = new StringTokenizer(sqlStatement, PARAMETER_TOKEN, true);
79      StringBuilder newSqlBuffer = new StringBuilder();
80  
81      String token = null;
82      String lastToken = null;
83      while (parser.hasMoreTokens()) {
84        token = parser.nextToken();
85        if (PARAMETER_TOKEN.equals(lastToken)) {
86          if (PARAMETER_TOKEN.equals(token)) {
87            newSqlBuffer.append(PARAMETER_TOKEN);
88          } else {
89            ParameterMapping mapping = null;
90            if (token.indexOf(PARAM_DELIM) > -1) {
91              mapping = oldParseMapping(token, parameterClass, typeHandlerFactory);
92            } else {
93              mapping = newParseMapping(token, parameterClass, typeHandlerFactory);
94            }
95  
96            mappingList.add(mapping);
97            newSqlBuffer.append("?");
98            boolean hasMoreTokens = parser.hasMoreTokens();
99            if (hasMoreTokens) {
100             token = parser.nextToken();
101           }
102           if (!hasMoreTokens || !PARAMETER_TOKEN.equals(token)) {
103             throw new SqlMapException(
104                 "Unterminated inline parameter in mapped statement near '" + newSqlBuffer.toString() + "'");
105           }
106         }
107         token = null;
108       } else if (!PARAMETER_TOKEN.equals(token)) {
109         newSqlBuffer.append(token);
110       }
111 
112       lastToken = token;
113     }
114 
115     String newSql = newSqlBuffer.toString();
116 
117     ParameterMapping[] mappingArray = (ParameterMapping[]) mappingList
118         .toArray(new ParameterMapping[mappingList.size()]);
119 
120     SqlText sqlText = new SqlText();
121     sqlText.setText(newSql);
122     sqlText.setParameterMappings(mappingArray);
123     return sqlText;
124   }
125 
126   /**
127    * New parse mapping.
128    *
129    * @param token
130    *          the token
131    * @param parameterClass
132    *          the parameter class
133    * @param typeHandlerFactory
134    *          the type handler factory
135    *
136    * @return the parameter mapping
137    */
138   private ParameterMapping newParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
139     ParameterMapping mapping = new ParameterMapping();
140 
141     // #propertyName,javaType=string,jdbcType=VARCHAR,mode=IN,nullValue=N/A,handler=string,numericScale=2#
142 
143     StringTokenizer paramParser = new StringTokenizer(token, "=,", false);
144     mapping.setPropertyName(paramParser.nextToken());
145 
146     while (paramParser.hasMoreTokens()) {
147       String field = paramParser.nextToken();
148       if (!paramParser.hasMoreTokens()) {
149         throw new SqlMapException("Incorrect inline parameter map format (missmatched name=value pairs): " + token);
150       }
151       String value = paramParser.nextToken();
152       if (field == null) {
153         throw new SqlMapException("Unrecognized parameter mapping field: '" + field + "' in " + token);
154       }
155       switch (field) {
156         case "javaType":
157           value = typeHandlerFactory.resolveAlias(value);
158           mapping.setJavaTypeName(value);
159           break;
160         case "jdbcType":
161           mapping.setJdbcTypeName(value);
162           break;
163         case "mode":
164           mapping.setMode(value);
165           break;
166         case "nullValue":
167           mapping.setNullValue(value);
168           break;
169         case "handler":
170           try {
171             value = typeHandlerFactory.resolveAlias(value);
172             Object impl = Resources.instantiate(value);
173             if (impl instanceof TypeHandlerCallback) {
174               mapping.setTypeHandler(new CustomTypeHandler((TypeHandlerCallback) impl));
175             } else if (impl instanceof TypeHandler) {
176               mapping.setTypeHandler((TypeHandler) impl);
177             } else {
178               throw new SqlMapException(
179                   "The class " + value + " is not a valid implementation of TypeHandler or TypeHandlerCallback");
180             }
181           } catch (Exception e) {
182             throw new SqlMapException("Error loading class specified by handler field in " + token + ".  Cause: " + e,
183                 e);
184           }
185           break;
186         case "numericScale":
187           try {
188             Integer numericScale = Integer.valueOf(value);
189             if (numericScale.intValue() < 0) {
190               throw new SqlMapException("Value specified for numericScale must be greater than or equal to zero");
191             }
192             mapping.setNumericScale(numericScale);
193           } catch (NumberFormatException e) {
194             throw new SqlMapException("Value specified for numericScale is not a valid Integer");
195           }
196           break;
197         default:
198           throw new SqlMapException("Unrecognized parameter mapping field: '" + field + "' in " + token);
199       }
200     }
201 
202     if (mapping.getTypeHandler() == null) {
203       TypeHandler handler;
204       if (parameterClass == null) {
205         handler = typeHandlerFactory.getUnkownTypeHandler();
206       } else {
207         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, mapping.getPropertyName(),
208             mapping.getJavaTypeName(), mapping.getJdbcTypeName());
209       }
210       mapping.setTypeHandler(handler);
211     }
212 
213     return mapping;
214   }
215 
216   /**
217    * Old parse mapping.
218    *
219    * @param token
220    *          the token
221    * @param parameterClass
222    *          the parameter class
223    * @param typeHandlerFactory
224    *          the type handler factory
225    *
226    * @return the parameter mapping
227    */
228   private ParameterMapping oldParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
229     ParameterMapping mapping = new ParameterMapping();
230     if (token.indexOf(PARAM_DELIM) <= -1) {
231       mapping.setPropertyName(token);
232       TypeHandler handler;
233       if (parameterClass == null) {
234         handler = typeHandlerFactory.getUnkownTypeHandler();
235       } else {
236         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, token, null, null);
237       }
238       mapping.setTypeHandler(handler);
239       return mapping;
240     }
241     StringTokenizer paramParser = new StringTokenizer(token, PARAM_DELIM, true);
242     int n1 = paramParser.countTokens();
243     if (n1 == 3) {
244       String name = paramParser.nextToken();
245       paramParser.nextToken(); // ignore ":"
246       String type = paramParser.nextToken();
247       mapping.setPropertyName(name);
248       mapping.setJdbcTypeName(type);
249       TypeHandler handler;
250       if (parameterClass == null) {
251         handler = typeHandlerFactory.getUnkownTypeHandler();
252       } else {
253         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, name, null, type);
254       }
255       mapping.setTypeHandler(handler);
256     } else {
257       if (n1 < 5) {
258         throw new SqlMapException("Incorrect inline parameter map format: " + token);
259       }
260       String name = paramParser.nextToken();
261       paramParser.nextToken(); // ignore ":"
262       String type = paramParser.nextToken();
263       paramParser.nextToken(); // ignore ":"
264       StringBuilder nullValue = new StringBuilder().append(paramParser.nextToken());
265       while (paramParser.hasMoreTokens()) {
266         nullValue.append(paramParser.nextToken());
267       }
268       mapping.setPropertyName(name);
269       mapping.setJdbcTypeName(type);
270       mapping.setNullValue(nullValue.toString());
271       TypeHandler handler;
272       if (parameterClass == null) {
273         handler = typeHandlerFactory.getUnkownTypeHandler();
274       } else {
275         handler = resolveTypeHandler(typeHandlerFactory, parameterClass, name, null, type);
276       }
277       mapping.setTypeHandler(handler);
278     }
279     return mapping;
280 
281   }
282 
283   /**
284    * Resolve type handler.
285    *
286    * @param typeHandlerFactory
287    *          the type handler factory
288    * @param clazz
289    *          the clazz
290    * @param propertyName
291    *          the property name
292    * @param javaType
293    *          the java type
294    * @param jdbcType
295    *          the jdbc type
296    *
297    * @return the type handler
298    */
299   private TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName,
300       String javaType, String jdbcType) {
301     TypeHandler handler = null;
302     if (clazz == null) {
303       // Unknown
304       handler = typeHandlerFactory.getUnkownTypeHandler();
305     } else if (DomTypeMarker.class.isAssignableFrom(clazz)) {
306       // DOM
307       handler = typeHandlerFactory.getTypeHandler(String.class, jdbcType);
308     } else if (java.util.Map.class.isAssignableFrom(clazz)) {
309       // Map
310       if (javaType == null) {
311         handler = typeHandlerFactory.getUnkownTypeHandler();
312         // BUG 1012591 -
313         // typeHandlerFactory.getTypeHandler(java.lang.Object.class,
314         // jdbcType);
315       } else {
316         try {
317           javaType = typeHandlerFactory.resolveAlias(javaType);
318           Class javaClass = Resources.classForName(javaType);
319           handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
320         } catch (Exception e) {
321           throw new SqlMapException("Error.  Could not set TypeHandler.  Cause: " + e, e);
322         }
323       }
324     } else if (typeHandlerFactory.getTypeHandler(clazz, jdbcType) != null) {
325       // Primitive
326       handler = typeHandlerFactory.getTypeHandler(clazz, jdbcType);
327     } else {
328       // JavaBean
329       if (javaType == null) {
330 
331         Class type = PROBE.getPropertyTypeForGetter(clazz, propertyName);
332         handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
333 
334       } else {
335         try {
336           javaType = typeHandlerFactory.resolveAlias(javaType);
337           Class javaClass = Resources.classForName(javaType);
338           handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
339         } catch (Exception e) {
340           throw new SqlMapException("Error.  Could not set TypeHandler.  Cause: " + e, e);
341         }
342       }
343     }
344     return handler;
345   }
346 
347 }