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 /*
17 * Created on Apr 17, 2005
18 */
19 package com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements;
20
21 import com.ibatis.sqlmap.client.SqlMapException;
22
23 import java.lang.reflect.Array;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31
32 /**
33 * The Class IterateContext.
34 *
35 * @author Brandon Goodin
36 */
37 public class IterateContext implements Iterator {
38
39 /** The Constant PROCESS_INDEX. */
40 private static final String PROCESS_INDEX = "ProcessIndex";
41
42 /** The Constant PROCESS_STRING. */
43 private static final String PROCESS_STRING = "ProcessString";
44
45 /** The iterator. */
46 private Iterator iterator;
47
48 /** The index. */
49 private int index = -1;
50
51 /** The property. */
52 private String property;
53
54 /** The allow next. */
55 private boolean allowNext = true;
56
57 /** The is final. */
58 private boolean isFinal = false;
59
60 /** The tag. */
61 private SqlTag tag;
62
63 /** The parent. */
64 private IterateContext parent;
65
66 /**
67 * This variable is true if some of the sub elements have actually produced content. This is used to test whether to
68 * add the open and conjunction text to the generated statement.
69 * <p>
70 * This variable is used to replace the deprecated and dangerous isFirst method.
71 */
72 private boolean someSubElementsHaveContent;
73
74 /**
75 * This variable is set by the doEndFragment method in IterateTagHandler to specify that the first content producing
76 * sub element has happened. The doPrepend method will test the value to know whether or not to process the prepend.
77 * This variable is used to replace the deprecated and dangerous isFirst method.
78 */
79 private boolean isPrependEnabled;
80
81 /**
82 * Instantiates a new iterate context.
83 *
84 * @param collection
85 * the collection
86 * @param tag
87 * the tag
88 * @param parent
89 * the parent
90 */
91 public IterateContext(Object collection, SqlTag tag, IterateContext parent) {
92 this.parent = parent;
93 this.tag = tag;
94 if (collection instanceof Collection) {
95 this.iterator = ((Collection) collection).iterator();
96 } else if (collection instanceof Iterator) {
97 this.iterator = (Iterator) collection;
98 } else if (collection.getClass().isArray()) {
99 List list = arrayToList(collection);
100 this.iterator = list.iterator();
101 } else {
102 throw new SqlMapException("ParameterObject or property was not a Collection, Array or Iterator.");
103 }
104 }
105
106 @Override
107 public boolean hasNext() {
108 return iterator != null && iterator.hasNext();
109 }
110
111 @Override
112 public Object next() {
113 index++;
114 return iterator.next();
115 }
116
117 @Override
118 public void remove() {
119 iterator.remove();
120 }
121
122 /**
123 * Gets the index.
124 *
125 * @return the index
126 */
127 public int getIndex() {
128 return index;
129 }
130
131 /**
132 * Checks if is first.
133 *
134 * @return true, if is first
135 *
136 * @deprecated This method should not be used to decide whether or not to add prepend and open text to the generated
137 * statement. Rather, use the methods isPrependEnabled() and someSubElementsHaveContent().
138 */
139 @Deprecated
140 public boolean isFirst() {
141 return index == 0;
142 }
143
144 /**
145 * Checks if is last.
146 *
147 * @return true, if is last
148 */
149 public boolean isLast() {
150 return iterator != null && !iterator.hasNext();
151 }
152
153 /**
154 * Array to list.
155 *
156 * @param array
157 * the array
158 *
159 * @return the list
160 */
161 private List arrayToList(Object array) {
162 List list = null;
163 if (array instanceof Object[]) {
164 list = Arrays.asList((Object[]) array);
165 } else {
166 list = new ArrayList<>();
167 for (int i = 0, n = Array.getLength(array); i < n; i++) {
168 list.add(Array.get(array, i));
169 }
170 }
171 return list;
172 }
173
174 /**
175 * Gets the property.
176 *
177 * @return Returns the property.
178 */
179 public String getProperty() {
180 return property;
181 }
182
183 /**
184 * This property specifies whether to increment the iterate in the doEndFragment. The ConditionalTagHandler has the
185 * ability to increment the IterateContext, so it is neccessary to avoid incrementing in both the ConditionalTag and
186 * the IterateTag.
187 *
188 * @param property
189 * The property to set.
190 */
191 public void setProperty(String property) {
192 this.property = property;
193 }
194
195 /**
196 * Checks if is allow next.
197 *
198 * @return Returns the allowNext.
199 */
200 public boolean isAllowNext() {
201 return allowNext;
202 }
203
204 /**
205 * Sets the allow next.
206 *
207 * @param performIterate
208 * The allowNext to set.
209 */
210 public void setAllowNext(boolean performIterate) {
211 this.allowNext = performIterate;
212 }
213
214 /**
215 * Gets the tag.
216 *
217 * @return Returns the tag.
218 */
219 public SqlTag getTag() {
220 return tag;
221 }
222
223 /**
224 * Sets the tag.
225 *
226 * @param tag
227 * The tag to set.
228 */
229 public void setTag(SqlTag tag) {
230 this.tag = tag;
231 }
232
233 /**
234 * Checks if is final.
235 *
236 * @return true, if is final
237 */
238 public boolean isFinal() {
239 return isFinal;
240 }
241
242 /**
243 * This attribute is used to mark whether an iterate tag is in it's final iteration. Since the ConditionalTagHandler
244 * can increment the iterate the final iterate in the doEndFragment of the IterateTagHandler needs to know it is in
245 * it's final iterate.
246 *
247 * @param aFinal
248 * the new final
249 */
250 public void setFinal(boolean aFinal) {
251 isFinal = aFinal;
252 }
253
254 /**
255 * Returns the last property of any bean specified in this IterateContext.
256 *
257 * @return The last property of any bean specified in this IterateContext.
258 */
259 public String getEndProperty() {
260 if (parent == null) {
261 return property;
262 }
263
264 int parentPropertyIndex = property.indexOf(parent.getProperty());
265 if (parentPropertyIndex > -1) {
266 int endPropertyIndex1 = property.indexOf(']', parentPropertyIndex);
267 int endPropertyIndex2 = property.indexOf('.', parentPropertyIndex);
268 return property.substring(parentPropertyIndex + Math.max(endPropertyIndex1, endPropertyIndex2) + 1);
269 }
270 return property;
271 }
272
273 /**
274 * Replaces value of a tag property to match it's value with current iteration and all other iterations.
275 *
276 * @param tagProperty
277 * the property of a TagHandler.
278 *
279 * @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
280 * occured in PROCESS_INDEX key.
281 */
282 protected Map processTagProperty(String tagProperty) {
283 if (parent != null) {
284 Map parentResult = parent.processTagProperty(tagProperty);
285 return this.addIndex((String) parentResult.get(PROCESS_STRING),
286 ((Integer) parentResult.get(PROCESS_INDEX)).intValue());
287 }
288 return this.addIndex(tagProperty, 0);
289 }
290
291 /**
292 * Replaces value of a tag property to match it's value with current iteration and all other iterations.
293 *
294 * @param tagProperty
295 * the property of a TagHandler.
296 *
297 * @return The tag property with all "[]" replaced with the correct iteration value.
298 */
299 public String addIndexToTagProperty(String tagProperty) {
300 Map map = this.processTagProperty(tagProperty);
301 return (String) map.get(PROCESS_STRING);
302 }
303
304 /**
305 * Adds index value to the first found property matching this Iteration starting at index startIndex.
306 *
307 * @param input
308 * The input String.
309 * @param startIndex
310 * The index where search for property begins.
311 *
312 * @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
313 * occured in PROCESS_INDEX key.
314 */
315 protected Map addIndex(String input, int startIndex) {
316 String endProperty = getEndProperty() + "[";
317 int propertyIndex = input.indexOf(endProperty, startIndex);
318 int modificationIndex = 0;
319 // Is the iterate property in the tag property at all?
320 // Make sure the tag property does not already have a number.
321 if (propertyIndex > -1 && input.charAt(propertyIndex + endProperty.length()) == ']') {
322 // Add iteration number to property.
323 input = input.substring(0, propertyIndex + endProperty.length()) + this.getIndex()
324 + input.substring(propertyIndex + endProperty.length());
325 modificationIndex = propertyIndex + endProperty.length();
326 }
327 Map ret = new HashMap<>();
328 ret.put(PROCESS_INDEX, Integer.valueOf(modificationIndex));
329 ret.put(PROCESS_STRING, input);
330 return ret;
331 }
332
333 /**
334 * Gets the parent.
335 *
336 * @return the parent
337 */
338 public IterateContext getParent() {
339 return parent;
340 }
341
342 /**
343 * Sets the parent.
344 *
345 * @param parent
346 * the new parent
347 */
348 public void setParent(IterateContext parent) {
349 this.parent = parent;
350 }
351
352 /**
353 * Some sub elements have content.
354 *
355 * @return true, if successful
356 */
357 public boolean someSubElementsHaveContent() {
358 return someSubElementsHaveContent;
359 }
360
361 /**
362 * Sets the some sub elements have content.
363 *
364 * @param someSubElementsHaveContent
365 * the new some sub elements have content
366 */
367 public void setSomeSubElementsHaveContent(boolean someSubElementsHaveContent) {
368 this.someSubElementsHaveContent = someSubElementsHaveContent;
369 }
370
371 /**
372 * Checks if is prepend enabled.
373 *
374 * @return true, if is prepend enabled
375 */
376 public boolean isPrependEnabled() {
377 return isPrependEnabled;
378 }
379
380 /**
381 * Sets the prepend enabled.
382 *
383 * @param isPrependEnabled
384 * the new prepend enabled
385 */
386 public void setPrependEnabled(boolean isPrependEnabled) {
387 this.isPrependEnabled = isPrependEnabled;
388 }
389 }