View Javadoc
1   /*
2    * Copyright 2004-2025 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.result;
17  
18  import com.ibatis.common.beans.Probe;
19  import com.ibatis.common.beans.ProbeFactory;
20  import com.ibatis.common.jdbc.exception.NestedSQLException;
21  import com.ibatis.sqlmap.client.SqlMapException;
22  import com.ibatis.sqlmap.engine.exchange.DataExchange;
23  import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
24  import com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate;
25  import com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader;
26  import com.ibatis.sqlmap.engine.mapping.sql.Sql;
27  import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
28  import com.ibatis.sqlmap.engine.scope.ErrorContext;
29  import com.ibatis.sqlmap.engine.scope.StatementScope;
30  import com.ibatis.sqlmap.engine.type.DomCollectionTypeMarker;
31  import com.ibatis.sqlmap.engine.type.DomTypeMarker;
32  import com.ibatis.sqlmap.engine.type.TypeHandler;
33  import com.ibatis.sqlmap.engine.type.TypeHandlerFactory;
34  
35  import java.sql.ResultSet;
36  import java.sql.SQLException;
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.HashMap;
40  import java.util.HashSet;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Set;
45  import java.util.StringTokenizer;
46  
47  import javax.xml.parsers.DocumentBuilderFactory;
48  import javax.xml.parsers.ParserConfigurationException;
49  
50  import org.w3c.dom.Document;
51  
52  /**
53   * Basic implementation of ResultMap interface.
54   */
55  public class ResultMap {
56  
57    /** The Constant PROBE. */
58    private static final Probe PROBE = ProbeFactory.getProbe();
59  
60    /** The Constant KEY_SEPARATOR. */
61    private static final String KEY_SEPARATOR = "\002";
62  
63    /** The id. */
64    private String id;
65  
66    /** The result class. */
67    private Class resultClass;
68  
69    /** The result mappings. */
70    // DO NOT ACCESS EITHER OF THESE OUTSIDE OF THEIR BEAN GETTER/SETTER
71    private ResultMapping[] resultMappings;
72  
73    /** The remappable result mappings. */
74    private ThreadLocal remappableResultMappings = new ThreadLocal();
75  
76    /** The data exchange. */
77    private DataExchange dataExchange;
78  
79    /** The nested result mappings. */
80    private List nestedResultMappings;
81  
82    /** The discriminator. */
83    private Discriminator discriminator;
84  
85    /** The group by props. */
86    private Set groupByProps;
87  
88    /** The xml name. */
89    private String xmlName;
90  
91    /** The resource. */
92    private String resource;
93  
94    /** The delegate. */
95    protected SqlMapExecutorDelegate delegate;
96  
97    /** The allow remapping. */
98    protected boolean allowRemapping = false;
99  
100   /** The Constant NO_VALUE. */
101   public static final Object NO_VALUE = new Object();
102 
103   /**
104    * Constructor to pass a SqlMapExecutorDelegate in.
105    *
106    * @param delegate
107    *          - the SqlMapExecutorDelegate
108    */
109   public ResultMap(SqlMapExecutorDelegate delegate) {
110     this.delegate = delegate;
111   }
112 
113   /**
114    * Getter for the SqlMapExecutorDelegate.
115    *
116    * @return - the delegate
117    */
118   public SqlMapExecutorDelegate getDelegate() {
119     return delegate;
120   }
121 
122   /**
123    * Gets the id.
124    *
125    * @return the id
126    */
127   public String getId() {
128     return id;
129   }
130 
131   /**
132    * Setter for the ID.
133    *
134    * @param id
135    *          - the new ID
136    */
137   public void setId(String id) {
138     this.id = id;
139   }
140 
141   /**
142    * Gets the result class.
143    *
144    * @return the result class
145    */
146   public Class getResultClass() {
147     return resultClass;
148   }
149 
150   /**
151    * Gets the unique key.
152    *
153    * @param keyPrefix
154    *          the key prefix
155    * @param values
156    *          the values
157    *
158    * @return the unique key
159    */
160   public Object getUniqueKey(String keyPrefix, Object[] values) {
161     if (groupByProps == null) {
162       return null;
163     }
164 
165     StringBuilder keyBuffer;
166     if (keyPrefix != null) {
167       keyBuffer = new StringBuilder(keyPrefix);
168     } else {
169       keyBuffer = new StringBuilder();
170     }
171     for (int i = 0; i < getResultMappings().length; i++) {
172       String propertyName = getResultMappings()[i].getPropertyName();
173       if (groupByProps.contains(propertyName)) {
174         keyBuffer.append(values[i]);
175         keyBuffer.append('-');
176       }
177     }
178     if (keyBuffer.length() < 1) {
179       return null;
180     }
181     // seperator value not likely to appear in a database
182     keyBuffer.append(KEY_SEPARATOR);
183     return keyBuffer.toString();
184   }
185 
186   /**
187    * Gets the unique key.
188    *
189    * @param values
190    *          the values
191    *
192    * @return the unique key
193    */
194   public Object getUniqueKey(Object[] values) {
195     return getUniqueKey(null, values);
196   }
197 
198   /**
199    * Setter for the result class (what the results will be mapped into).
200    *
201    * @param resultClass
202    *          - the result class
203    */
204   public void setResultClass(Class resultClass) {
205     this.resultClass = resultClass;
206   }
207 
208   /**
209    * Getter for the DataExchange object to be used.
210    *
211    * @return - the DataExchange object
212    */
213   public DataExchange getDataExchange() {
214     return dataExchange;
215   }
216 
217   /**
218    * Setter for the DataExchange object to be used.
219    *
220    * @param dataExchange
221    *          - the new DataExchange object
222    */
223   public void setDataExchange(DataExchange dataExchange) {
224     this.dataExchange = dataExchange;
225   }
226 
227   /**
228    * Getter (used by DomDataExchange) for the xml name of the results.
229    *
230    * @return - the name
231    */
232   public String getXmlName() {
233     return xmlName;
234   }
235 
236   /**
237    * Setter (used by the SqlMapBuilder) for the xml name of the results.
238    *
239    * @param xmlName
240    *          - the name
241    */
242   public void setXmlName(String xmlName) {
243     this.xmlName = xmlName;
244   }
245 
246   /**
247    * Getter for the resource (used to report errors).
248    *
249    * @return - the resource
250    */
251   public String getResource() {
252     return resource;
253   }
254 
255   /**
256    * Setter for the resource (used by the SqlMapBuilder).
257    *
258    * @param resource
259    *          - the resource name
260    */
261   public void setResource(String resource) {
262     this.resource = resource;
263   }
264 
265   /**
266    * Adds the group by property.
267    *
268    * @param name
269    *          the name
270    */
271   public void addGroupByProperty(String name) {
272     if (groupByProps == null) {
273       groupByProps = new HashSet<>();
274     }
275     groupByProps.add(name);
276   }
277 
278   /**
279    * Checks for group by.
280    *
281    * @return true, if successful
282    */
283   public boolean hasGroupBy() {
284     return groupByProps != null && groupByProps.size() > 0;
285   }
286 
287   /**
288    * Group by props.
289    *
290    * @return the iterator
291    */
292   public Iterator groupByProps() {
293     return groupByProps.iterator();
294   }
295 
296   /**
297    * Adds the nested result mappings.
298    *
299    * @param mapping
300    *          the mapping
301    */
302   public void addNestedResultMappings(ResultMapping mapping) {
303     if (nestedResultMappings == null) {
304       nestedResultMappings = new ArrayList<>();
305     }
306     nestedResultMappings.add(mapping);
307   }
308 
309   /**
310    * Gets the nested result mappings.
311    *
312    * @return the nested result mappings
313    */
314   public List getNestedResultMappings() {
315     return nestedResultMappings;
316   }
317 
318   /**
319    * Gets the result mappings.
320    *
321    * @return the result mappings
322    */
323   public ResultMapping[] getResultMappings() {
324     if (allowRemapping) {
325       return (ResultMapping[]) remappableResultMappings.get();
326     }
327     return resultMappings;
328   }
329 
330   /**
331    * Sets the discriminator.
332    *
333    * @param discriminator
334    *          the new discriminator
335    */
336   public void setDiscriminator(Discriminator discriminator) {
337     if (this.discriminator != null) {
338       throw new SqlMapException("A discriminator may only be set once per result map.");
339     }
340     this.discriminator = discriminator;
341   }
342 
343   /**
344    * Gets the discriminator.
345    *
346    * @return the discriminator
347    */
348   public Discriminator getDiscriminator() {
349     return discriminator;
350   }
351 
352   /**
353    * Resolve sub map.
354    *
355    * @param statementScope
356    *          the statement scope
357    * @param rs
358    *          the rs
359    *
360    * @return the result map
361    *
362    * @throws SQLException
363    *           the SQL exception
364    */
365   public ResultMap resolveSubMap(StatementScope statementScope, ResultSet rs) throws SQLException {
366     ResultMap subMap = this;
367     if (discriminator != null) {
368       ResultMapping mapping = discriminator.getResultMapping();
369       Object value = getPrimitiveResultMappingValue(rs, mapping);
370       if (value == null) {
371         value = doNullMapping(value, mapping);
372       }
373       subMap = discriminator.getSubMap(String.valueOf(value));
374       if (subMap == null) {
375         subMap = this;
376       } else if (subMap != this) {
377         subMap = subMap.resolveSubMap(statementScope, rs);
378       }
379     }
380     return subMap;
381   }
382 
383   /**
384    * Setter for a list of the individual ResultMapping objects.
385    *
386    * @param resultMappingList
387    *          - the list
388    */
389   public void setResultMappingList(List resultMappingList) {
390     if (allowRemapping) {
391       this.remappableResultMappings.set(resultMappingList.toArray(new ResultMapping[resultMappingList.size()]));
392     } else {
393       this.resultMappings = (ResultMapping[]) resultMappingList.toArray(new ResultMapping[resultMappingList.size()]);
394     }
395 
396     Map props = new HashMap<>();
397     props.put("map", this);
398     dataExchange = getDelegate().getDataExchangeFactory().getDataExchangeForClass(resultClass);
399     dataExchange.initialize(props);
400   }
401 
402   /**
403    * Getter for the number of ResultMapping objects.
404    *
405    * @return - the count
406    */
407   public int getResultCount() {
408     return this.getResultMappings().length;
409   }
410 
411   /**
412    * Read a row from a resultset and map results to an array.
413    *
414    * @param statementScope
415    *          scope of the request
416    * @param rs
417    *          ResultSet to read from
418    *
419    * @return row read as an array of column values.
420    *
421    * @throws SQLException
422    *           the SQL exception
423    */
424   public Object[] getResults(StatementScope statementScope, ResultSet rs) throws SQLException {
425     ErrorContext errorContext = statementScope.getErrorContext();
426     errorContext.setActivity("applying a result map");
427     errorContext.setObjectId(this.getId());
428     errorContext.setResource(this.getResource());
429     errorContext.setMoreInfo("Check the result map.");
430 
431     boolean foundData = false;
432     Object[] columnValues = new Object[getResultMappings().length];
433     for (int i = 0; i < getResultMappings().length; i++) {
434       ResultMapping mapping = getResultMappings()[i];
435       errorContext.setMoreInfo(mapping.getErrorString());
436       if (mapping.getStatementName() != null) {
437         if (resultClass == null) {
438           throw new SqlMapException(
439               "The result class was null when trying to get results for ResultMap named " + getId() + ".");
440         }
441         if (Map.class.isAssignableFrom(resultClass)) {
442           Class javaType = mapping.getJavaType();
443           if (javaType == null) {
444             javaType = Object.class;
445           }
446           columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
447         } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
448           Class javaType = mapping.getJavaType();
449           if (javaType == null) {
450             javaType = DomTypeMarker.class;
451           }
452           columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, javaType);
453         } else {
454           Probe p = ProbeFactory.getProbe(resultClass);
455           Class type = p.getPropertyTypeForSetter(resultClass, mapping.getPropertyName());
456           columnValues[i] = getNestedSelectMappingValue(statementScope, rs, mapping, type);
457         }
458         foundData = foundData || columnValues[i] != null;
459       } else if (mapping.getNestedResultMapName() == null) {
460         columnValues[i] = getPrimitiveResultMappingValue(rs, mapping);
461         if (columnValues[i] == null) {
462           columnValues[i] = doNullMapping(columnValues[i], mapping);
463         } else {
464           foundData = true;
465         }
466       }
467     }
468 
469     statementScope.setRowDataFound(foundData);
470 
471     return columnValues;
472   }
473 
474   /**
475    * Sets the result object values.
476    *
477    * @param statementScope
478    *          the statement scope
479    * @param resultObject
480    *          the result object
481    * @param values
482    *          the values
483    *
484    * @return the object
485    */
486   public Object setResultObjectValues(StatementScope statementScope, Object resultObject, Object[] values) {
487     final String previousNestedKey = statementScope.getCurrentNestedKey();
488     String ukey = (String) getUniqueKey(statementScope.getCurrentNestedKey(), values);
489     Map uniqueKeys = statementScope.getUniqueKeys(this);
490     statementScope.setCurrentNestedKey(ukey);
491     if (uniqueKeys != null && uniqueKeys.containsKey(ukey)) {
492       // Unique key is already known, so get the existing result object and process additional
493       // results.
494       resultObject = uniqueKeys.get(ukey);
495       applyNestedResultMap(statementScope, resultObject, values);
496       resultObject = NO_VALUE;
497     } else if (ukey == null || uniqueKeys == null || !uniqueKeys.containsKey(ukey)) {
498       // Unique key is NOT known, so create a new result object and then process additional
499       // results.
500       resultObject = dataExchange.setData(statementScope, this, resultObject, values);
501       // Lazy init key set, only if we're grouped by something (i.e. ukey != null)
502       if (ukey != null) {
503         if (uniqueKeys == null) {
504           uniqueKeys = new HashMap<>();
505           statementScope.setUniqueKeys(this, uniqueKeys);
506         }
507         uniqueKeys.put(ukey, resultObject);
508       }
509       applyNestedResultMap(statementScope, resultObject, values);
510     } else {
511       // Otherwise, we don't care about these results.
512       resultObject = NO_VALUE;
513     }
514 
515     statementScope.setCurrentNestedKey(previousNestedKey);
516     return resultObject;
517   }
518 
519   /**
520    * Apply nested result map.
521    *
522    * @param statementScope
523    *          the statement scope
524    * @param resultObject
525    *          the result object
526    * @param values
527    *          the values
528    */
529   private void applyNestedResultMap(StatementScope statementScope, Object resultObject, Object[] values) {
530     if (resultObject != null && resultObject != NO_VALUE && nestedResultMappings != null) {
531       for (Object nestedResultMapping : nestedResultMappings) {
532         ResultMapping resultMapping = (ResultMapping) nestedResultMapping;
533         setNestedResultMappingValue(resultMapping, statementScope, resultObject, values);
534       }
535     }
536   }
537 
538   /**
539    * Some changes in this method for IBATIS-225:
540    * <ul>
541    * <li>We no longer require the nested property to be a collection. This will allow reuses of resultMaps on 1:1
542    * relationships</li>
543    * <li>If the nested property is not a collection, then it will be created/replaced by the values generated from the
544    * current row.</li>
545    * </ul>
546    *
547    * @param mapping
548    *          the mapping
549    * @param statementScope
550    *          the statement scope
551    * @param resultObject
552    *          the result object
553    * @param values
554    *          the values
555    */
556   protected void setNestedResultMappingValue(ResultMapping mapping, StatementScope statementScope, Object resultObject,
557       Object[] values) {
558     try {
559 
560       String resultMapName = mapping.getNestedResultMapName();
561       ResultMap resultMap = getDelegate().getResultMap(resultMapName);
562       // get the discriminated submap if it exists
563       resultMap = resultMap.resolveSubMap(statementScope, statementScope.getResultSet());
564 
565       Class type = mapping.getJavaType();
566       String propertyName = mapping.getPropertyName();
567 
568       Object obj = PROBE.getObject(resultObject, propertyName);
569 
570       if (obj == null) {
571         if (type == null) {
572           type = PROBE.getPropertyTypeForSetter(resultObject, propertyName);
573         }
574 
575         try {
576           // create the object if is it a Collection. If not a Collection
577           // then we will just set the property to the object created
578           // in processing the nested result map
579           if (Collection.class.isAssignableFrom(type)) {
580             obj = ResultObjectFactoryUtil.createObjectThroughFactory(type);
581             PROBE.setObject(resultObject, propertyName, obj);
582           }
583         } catch (Exception e) {
584           throw new SqlMapException(
585               "Error instantiating collection property for mapping '" + mapping.getPropertyName() + "'.  Cause: " + e,
586               e);
587         }
588       }
589 
590       // JIRA 375
591       // "Provide a way for not creating items from nested ResultMaps when the items contain only null values"
592       boolean subResultObjectAbsent = false;
593       if (mapping.getNotNullColumn() != null
594           && statementScope.getResultSet().getObject(mapping.getNotNullColumn()) == null) {
595         subResultObjectAbsent = true;
596       }
597       if (!subResultObjectAbsent) {
598         values = resultMap.getResults(statementScope, statementScope.getResultSet());
599         if (statementScope.isRowDataFound()) {
600           Object o = resultMap.setResultObjectValues(statementScope, null, values);
601           if (o != NO_VALUE) {
602             if (obj instanceof Collection) {
603               ((Collection) obj).add(o);
604             } else {
605               PROBE.setObject(resultObject, propertyName, o);
606             }
607           }
608         }
609       }
610     } catch (SQLException e) {
611       throw new SqlMapException(
612           "Error getting nested result map values for '" + mapping.getPropertyName() + "'.  Cause: " + e, e);
613     }
614   }
615 
616   /**
617    * Gets the nested select mapping value.
618    *
619    * @param statementScope
620    *          the statement scope
621    * @param rs
622    *          the rs
623    * @param mapping
624    *          the mapping
625    * @param targetType
626    *          the target type
627    *
628    * @return the nested select mapping value
629    *
630    * @throws SQLException
631    *           the SQL exception
632    */
633   protected Object getNestedSelectMappingValue(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
634       Class targetType) throws SQLException {
635     try {
636       TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
637 
638       String statementName = mapping.getStatementName();
639       SqlMapClientImpl client = (SqlMapClientImpl) statementScope.getSession().getSqlMapClient();
640 
641       MappedStatement mappedStatement = client.getMappedStatement(statementName);
642       Class parameterType = mappedStatement.getParameterClass();
643       Object parameterObject = null;
644 
645       if (parameterType == null) {
646         parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
647       } else if (typeHandlerFactory.hasTypeHandler(parameterType)) {
648         parameterObject = preparePrimitiveParameterObject(rs, mapping, parameterType);
649       } else if (DomTypeMarker.class.isAssignableFrom(parameterType)) {
650         parameterObject = prepareDomParameterObject(rs, mapping);
651       } else {
652         parameterObject = prepareBeanParameterObject(statementScope, rs, mapping, parameterType);
653       }
654 
655       Object result = null;
656       if (parameterObject != null) {
657 
658         Sql sql = mappedStatement.getSql();
659         ResultMap resultMap = sql.getResultMap(statementScope, parameterObject);
660         Class resultClass = resultMap.getResultClass();
661 
662         if (resultClass != null && !DomTypeMarker.class.isAssignableFrom(targetType)) {
663           if (DomCollectionTypeMarker.class.isAssignableFrom(resultClass)) {
664             targetType = DomCollectionTypeMarker.class;
665           } else if (DomTypeMarker.class.isAssignableFrom(resultClass)) {
666             targetType = DomTypeMarker.class;
667           }
668         }
669 
670         result = ResultLoader.loadResult(client, statementName, parameterObject, targetType);
671 
672         String nullValue = mapping.getNullValue();
673         if (result == null && nullValue != null) {
674           TypeHandler typeHandler = typeHandlerFactory.getTypeHandler(targetType);
675           if (typeHandler != null) {
676             result = typeHandler.valueOf(nullValue);
677           }
678         }
679       }
680       return result;
681     } catch (InstantiationException | IllegalAccessException e) {
682       throw new NestedSQLException("Error setting nested bean property.  Cause: " + e, e);
683     }
684 
685   }
686 
687   /**
688    * Prepare primitive parameter object.
689    *
690    * @param rs
691    *          the rs
692    * @param mapping
693    *          the mapping
694    * @param parameterType
695    *          the parameter type
696    *
697    * @return the object
698    *
699    * @throws SQLException
700    *           the SQL exception
701    */
702   private Object preparePrimitiveParameterObject(ResultSet rs, ResultMapping mapping, Class parameterType)
703       throws SQLException {
704     TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
705     TypeHandler th = typeHandlerFactory.getTypeHandler(parameterType);
706     return th.getResult(rs, mapping.getColumnName());
707   }
708 
709   /**
710    * New document.
711    *
712    * @param root
713    *          the root
714    *
715    * @return the document
716    */
717   private Document newDocument(String root) {
718     try {
719       Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
720       doc.appendChild(doc.createElement(root));
721       return doc;
722     } catch (ParserConfigurationException e) {
723       throw new RuntimeException("Error creating XML document.  Cause: " + e);
724     }
725   }
726 
727   /**
728    * Prepare dom parameter object.
729    *
730    * @param rs
731    *          the rs
732    * @param mapping
733    *          the mapping
734    *
735    * @return the object
736    *
737    * @throws SQLException
738    *           the SQL exception
739    */
740   private Object prepareDomParameterObject(ResultSet rs, ResultMapping mapping) throws SQLException {
741     TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
742 
743     Document doc = newDocument("parameter");
744     Probe probe = ProbeFactory.getProbe(doc);
745 
746     String complexName = mapping.getColumnName();
747 
748     TypeHandler stringTypeHandler = typeHandlerFactory.getTypeHandler(String.class);
749     if (complexName.indexOf('=') > -1) {
750       // old 1.x style multiple params
751       StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
752       while (parser.hasMoreTokens()) {
753         String propName = parser.nextToken();
754         String colName = parser.nextToken();
755         Object propValue = stringTypeHandler.getResult(rs, colName);
756         probe.setObject(doc, propName, propValue.toString());
757       }
758     } else {
759       // single param
760       Object propValue = stringTypeHandler.getResult(rs, complexName);
761       probe.setObject(doc, "value", propValue.toString());
762     }
763 
764     return doc;
765   }
766 
767   /**
768    * Prepare bean parameter object.
769    *
770    * @param statementScope
771    *          the statement scope
772    * @param rs
773    *          the rs
774    * @param mapping
775    *          the mapping
776    * @param parameterType
777    *          the parameter type
778    *
779    * @return the object
780    *
781    * @throws InstantiationException
782    *           the instantiation exception
783    * @throws IllegalAccessException
784    *           the illegal access exception
785    * @throws SQLException
786    *           the SQL exception
787    */
788   private Object prepareBeanParameterObject(StatementScope statementScope, ResultSet rs, ResultMapping mapping,
789       Class parameterType) throws InstantiationException, IllegalAccessException, SQLException {
790     TypeHandlerFactory typeHandlerFactory = getDelegate().getTypeHandlerFactory();
791 
792     Object parameterObject;
793     if (parameterType == null) {
794       parameterObject = new HashMap<>();
795     } else {
796       parameterObject = ResultObjectFactoryUtil.createObjectThroughFactory(parameterType);
797     }
798     String complexName = mapping.getColumnName();
799 
800     if (complexName.indexOf('=') > -1 || complexName.indexOf(',') > -1) {
801       StringTokenizer parser = new StringTokenizer(complexName, "{}=, ", false);
802       while (parser.hasMoreTokens()) {
803         String propName = parser.nextToken();
804         String colName = parser.nextToken();
805         Class propType = PROBE.getPropertyTypeForSetter(parameterObject, propName);
806         TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(propType);
807         Object propValue = propTypeHandler.getResult(rs, colName);
808         PROBE.setObject(parameterObject, propName, propValue);
809       }
810     } else {
811       // single param
812       TypeHandler propTypeHandler = typeHandlerFactory.getTypeHandler(parameterType);
813       if (propTypeHandler == null) {
814         propTypeHandler = typeHandlerFactory.getUnkownTypeHandler();
815       }
816       parameterObject = propTypeHandler.getResult(rs, complexName);
817     }
818 
819     return parameterObject;
820   }
821 
822   /**
823    * Gets the primitive result mapping value.
824    *
825    * @param rs
826    *          the rs
827    * @param mapping
828    *          the mapping
829    *
830    * @return the primitive result mapping value
831    *
832    * @throws SQLException
833    *           the SQL exception
834    */
835   protected Object getPrimitiveResultMappingValue(ResultSet rs, ResultMapping mapping) throws SQLException {
836     Object value = null;
837     TypeHandler typeHandler = mapping.getTypeHandler();
838     if (typeHandler == null) {
839       throw new SqlMapException("No type handler could be found to map the property '" + mapping.getPropertyName()
840           + "' to the column '" + mapping.getColumnName()
841           + "'.  One or both of the types, or the combination of types is not supported.");
842     }
843     String columnName = mapping.getColumnName();
844     int columnIndex = mapping.getColumnIndex();
845     if (columnName == null) {
846       value = typeHandler.getResult(rs, columnIndex);
847     } else {
848       value = typeHandler.getResult(rs, columnName);
849     }
850     return value;
851   }
852 
853   /**
854    * Do null mapping.
855    *
856    * @param value
857    *          the value
858    * @param mapping
859    *          the mapping
860    *
861    * @return the object
862    *
863    * @throws SqlMapException
864    *           the sql map exception
865    */
866   protected Object doNullMapping(Object value, ResultMapping mapping) throws SqlMapException {
867     if (value != null) {
868       return value;
869     }
870 
871     TypeHandler typeHandler = mapping.getTypeHandler();
872     if (typeHandler != null) {
873       String nullValue = mapping.getNullValue();
874       if (nullValue != null) {
875         value = typeHandler.valueOf(nullValue);
876       }
877       return value;
878     }
879     throw new SqlMapException(
880         "No type handler could be found to map the property '" + mapping.getPropertyName() + "' to the column '"
881             + mapping.getColumnName() + "'.  One or both of the types, or the combination of types is not supported.");
882   }
883 }