View Javadoc
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 }