1 /*
2 * Copyright 2004-2023 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 public boolean hasNext() {
107 return iterator != null && iterator.hasNext();
108 }
109
110 public Object next() {
111 index++;
112 return iterator.next();
113 }
114
115 public void remove() {
116 iterator.remove();
117 }
118
119 /**
120 * Gets the index.
121 *
122 * @return the index
123 */
124 public int getIndex() {
125 return index;
126 }
127
128 /**
129 * Checks if is first.
130 *
131 * @return true, if is first
132 *
133 * @deprecated This method should not be used to decide whether or not to add prepend and open text to the generated
134 * statement. Rather, use the methods isPrependEnabled() and someSubElementsHaveContent().
135 */
136 public boolean isFirst() {
137 return index == 0;
138 }
139
140 /**
141 * Checks if is last.
142 *
143 * @return true, if is last
144 */
145 public boolean isLast() {
146 return iterator != null && !iterator.hasNext();
147 }
148
149 /**
150 * Array to list.
151 *
152 * @param array
153 * the array
154 *
155 * @return the list
156 */
157 private List arrayToList(Object array) {
158 List list = null;
159 if (array instanceof Object[]) {
160 list = Arrays.asList((Object[]) array);
161 } else {
162 list = new ArrayList();
163 for (int i = 0, n = Array.getLength(array); i < n; i++) {
164 list.add(Array.get(array, i));
165 }
166 }
167 return list;
168 }
169
170 /**
171 * Gets the property.
172 *
173 * @return Returns the property.
174 */
175 public String getProperty() {
176 return property;
177 }
178
179 /**
180 * This property specifies whether to increment the iterate in the doEndFragment. The ConditionalTagHandler has the
181 * ability to increment the IterateContext, so it is neccessary to avoid incrementing in both the ConditionalTag and
182 * the IterateTag.
183 *
184 * @param property
185 * The property to set.
186 */
187 public void setProperty(String property) {
188 this.property = property;
189 }
190
191 /**
192 * Checks if is allow next.
193 *
194 * @return Returns the allowNext.
195 */
196 public boolean isAllowNext() {
197 return allowNext;
198 }
199
200 /**
201 * Sets the allow next.
202 *
203 * @param performIterate
204 * The allowNext to set.
205 */
206 public void setAllowNext(boolean performIterate) {
207 this.allowNext = performIterate;
208 }
209
210 /**
211 * Gets the tag.
212 *
213 * @return Returns the tag.
214 */
215 public SqlTag getTag() {
216 return tag;
217 }
218
219 /**
220 * Sets the tag.
221 *
222 * @param tag
223 * The tag to set.
224 */
225 public void setTag(SqlTag tag) {
226 this.tag = tag;
227 }
228
229 /**
230 * Checks if is final.
231 *
232 * @return true, if is final
233 */
234 public boolean isFinal() {
235 return isFinal;
236 }
237
238 /**
239 * This attribute is used to mark whether an iterate tag is in it's final iteration. Since the ConditionalTagHandler
240 * can increment the iterate the final iterate in the doEndFragment of the IterateTagHandler needs to know it is in
241 * it's final iterate.
242 *
243 * @param aFinal
244 * the new final
245 */
246 public void setFinal(boolean aFinal) {
247 isFinal = aFinal;
248 }
249
250 /**
251 * Returns the last property of any bean specified in this IterateContext.
252 *
253 * @return The last property of any bean specified in this IterateContext.
254 */
255 public String getEndProperty() {
256 if (parent != null) {
257 int parentPropertyIndex = property.indexOf(parent.getProperty());
258 if (parentPropertyIndex > -1) {
259 int endPropertyIndex1 = property.indexOf(']', parentPropertyIndex);
260 int endPropertyIndex2 = property.indexOf('.', parentPropertyIndex);
261 return property.substring(parentPropertyIndex + Math.max(endPropertyIndex1, endPropertyIndex2) + 1,
262 property.length());
263 } else {
264 return property;
265 }
266 } else {
267 return property;
268 }
269 }
270
271 /**
272 * Replaces value of a tag property to match it's value with current iteration and all other iterations.
273 *
274 * @param tagProperty
275 * the property of a TagHandler.
276 *
277 * @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
278 * occured in PROCESS_INDEX key.
279 */
280 protected Map processTagProperty(String tagProperty) {
281 if (parent != null) {
282 Map parentResult = parent.processTagProperty(tagProperty);
283 return this.addIndex((String) parentResult.get(PROCESS_STRING),
284 ((Integer) parentResult.get(PROCESS_INDEX)).intValue());
285 } else {
286 return this.addIndex(tagProperty, 0);
287 }
288 }
289
290 /**
291 * Replaces value of a tag property to match it's value with current iteration and all other iterations.
292 *
293 * @param tagProperty
294 * the property of a TagHandler.
295 *
296 * @return The tag property with all "[]" replaced with the correct iteration value.
297 */
298 public String addIndexToTagProperty(String tagProperty) {
299 Map map = this.processTagProperty(tagProperty);
300 return (String) map.get(PROCESS_STRING);
301 }
302
303 /**
304 * Adds index value to the first found property matching this Iteration starting at index startIndex.
305 *
306 * @param input
307 * The input String.
308 * @param startIndex
309 * The index where search for property begins.
310 *
311 * @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
312 * occured in PROCESS_INDEX key.
313 */
314 protected Map addIndex(String input, int startIndex) {
315 String endProperty = getEndProperty() + "[";
316 int propertyIndex = input.indexOf(endProperty, startIndex);
317 int modificationIndex = 0;
318 // Is the iterate property in the tag property at all?
319 if (propertyIndex > -1) {
320 // Make sure the tag property does not already have a number.
321 if (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 }
328 Map ret = new HashMap();
329 ret.put(PROCESS_INDEX, Integer.valueOf(modificationIndex));
330 ret.put(PROCESS_STRING, input);
331 return ret;
332 }
333
334 /**
335 * Gets the parent.
336 *
337 * @return the parent
338 */
339 public IterateContext getParent() {
340 return parent;
341 }
342
343 /**
344 * Sets the parent.
345 *
346 * @param parent
347 * the new parent
348 */
349 public void setParent(IterateContext parent) {
350 this.parent = parent;
351 }
352
353 /**
354 * Some sub elements have content.
355 *
356 * @return true, if successful
357 */
358 public boolean someSubElementsHaveContent() {
359 return someSubElementsHaveContent;
360 }
361
362 /**
363 * Sets the some sub elements have content.
364 *
365 * @param someSubElementsHaveContent
366 * the new some sub elements have content
367 */
368 public void setSomeSubElementsHaveContent(boolean someSubElementsHaveContent) {
369 this.someSubElementsHaveContent = someSubElementsHaveContent;
370 }
371
372 /**
373 * Checks if is prepend enabled.
374 *
375 * @return true, if is prepend enabled
376 */
377 public boolean isPrependEnabled() {
378 return isPrependEnabled;
379 }
380
381 /**
382 * Sets the prepend enabled.
383 *
384 * @param isPrependEnabled
385 * the new prepend enabled
386 */
387 public void setPrependEnabled(boolean isPrependEnabled) {
388 this.isPrependEnabled = isPrependEnabled;
389 }
390 }