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  /*
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 }