IterateContext.java
/*
* Copyright 2004-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Created on Apr 17, 2005
*/
package com.ibatis.sqlmap.engine.mapping.sql.dynamic.elements;
import com.ibatis.sqlmap.client.SqlMapException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* The Class IterateContext.
*
* @author Brandon Goodin
*/
public class IterateContext implements Iterator {
/** The Constant PROCESS_INDEX. */
private static final String PROCESS_INDEX = "ProcessIndex";
/** The Constant PROCESS_STRING. */
private static final String PROCESS_STRING = "ProcessString";
/** The iterator. */
private Iterator iterator;
/** The index. */
private int index = -1;
/** The property. */
private String property;
/** The allow next. */
private boolean allowNext = true;
/** The is final. */
private boolean isFinal = false;
/** The tag. */
private SqlTag tag;
/** The parent. */
private IterateContext parent;
/**
* This variable is true if some of the sub elements have actually produced content. This is used to test whether to
* add the open and conjunction text to the generated statement.
* <p>
* This variable is used to replace the deprecated and dangerous isFirst method.
*/
private boolean someSubElementsHaveContent;
/**
* This variable is set by the doEndFragment method in IterateTagHandler to specify that the first content producing
* sub element has happened. The doPrepend method will test the value to know whether or not to process the prepend.
* This variable is used to replace the deprecated and dangerous isFirst method.
*/
private boolean isPrependEnabled;
/**
* Instantiates a new iterate context.
*
* @param collection
* the collection
* @param tag
* the tag
* @param parent
* the parent
*/
public IterateContext(Object collection, SqlTag tag, IterateContext parent) {
this.parent = parent;
this.tag = tag;
if (collection instanceof Collection) {
this.iterator = ((Collection) collection).iterator();
} else if (collection instanceof Iterator) {
this.iterator = ((Iterator) collection);
} else if (collection.getClass().isArray()) {
List list = arrayToList(collection);
this.iterator = list.iterator();
} else {
throw new SqlMapException("ParameterObject or property was not a Collection, Array or Iterator.");
}
}
public boolean hasNext() {
return iterator != null && iterator.hasNext();
}
public Object next() {
index++;
return iterator.next();
}
public void remove() {
iterator.remove();
}
/**
* Gets the index.
*
* @return the index
*/
public int getIndex() {
return index;
}
/**
* Checks if is first.
*
* @return true, if is first
*
* @deprecated This method should not be used to decide whether or not to add prepend and open text to the generated
* statement. Rather, use the methods isPrependEnabled() and someSubElementsHaveContent().
*/
public boolean isFirst() {
return index == 0;
}
/**
* Checks if is last.
*
* @return true, if is last
*/
public boolean isLast() {
return iterator != null && !iterator.hasNext();
}
/**
* Array to list.
*
* @param array
* the array
*
* @return the list
*/
private List arrayToList(Object array) {
List list = null;
if (array instanceof Object[]) {
list = Arrays.asList((Object[]) array);
} else {
list = new ArrayList();
for (int i = 0, n = Array.getLength(array); i < n; i++) {
list.add(Array.get(array, i));
}
}
return list;
}
/**
* Gets the property.
*
* @return Returns the property.
*/
public String getProperty() {
return property;
}
/**
* This property specifies whether to increment the iterate in the doEndFragment. The ConditionalTagHandler has the
* ability to increment the IterateContext, so it is neccessary to avoid incrementing in both the ConditionalTag and
* the IterateTag.
*
* @param property
* The property to set.
*/
public void setProperty(String property) {
this.property = property;
}
/**
* Checks if is allow next.
*
* @return Returns the allowNext.
*/
public boolean isAllowNext() {
return allowNext;
}
/**
* Sets the allow next.
*
* @param performIterate
* The allowNext to set.
*/
public void setAllowNext(boolean performIterate) {
this.allowNext = performIterate;
}
/**
* Gets the tag.
*
* @return Returns the tag.
*/
public SqlTag getTag() {
return tag;
}
/**
* Sets the tag.
*
* @param tag
* The tag to set.
*/
public void setTag(SqlTag tag) {
this.tag = tag;
}
/**
* Checks if is final.
*
* @return true, if is final
*/
public boolean isFinal() {
return isFinal;
}
/**
* This attribute is used to mark whether an iterate tag is in it's final iteration. Since the ConditionalTagHandler
* can increment the iterate the final iterate in the doEndFragment of the IterateTagHandler needs to know it is in
* it's final iterate.
*
* @param aFinal
* the new final
*/
public void setFinal(boolean aFinal) {
isFinal = aFinal;
}
/**
* Returns the last property of any bean specified in this IterateContext.
*
* @return The last property of any bean specified in this IterateContext.
*/
public String getEndProperty() {
if (parent != null) {
int parentPropertyIndex = property.indexOf(parent.getProperty());
if (parentPropertyIndex > -1) {
int endPropertyIndex1 = property.indexOf(']', parentPropertyIndex);
int endPropertyIndex2 = property.indexOf('.', parentPropertyIndex);
return property.substring(parentPropertyIndex + Math.max(endPropertyIndex1, endPropertyIndex2) + 1,
property.length());
} else {
return property;
}
} else {
return property;
}
}
/**
* Replaces value of a tag property to match it's value with current iteration and all other iterations.
*
* @param tagProperty
* the property of a TagHandler.
*
* @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
* occured in PROCESS_INDEX key.
*/
protected Map processTagProperty(String tagProperty) {
if (parent != null) {
Map parentResult = parent.processTagProperty(tagProperty);
return this.addIndex((String) parentResult.get(PROCESS_STRING),
((Integer) parentResult.get(PROCESS_INDEX)).intValue());
} else {
return this.addIndex(tagProperty, 0);
}
}
/**
* Replaces value of a tag property to match it's value with current iteration and all other iterations.
*
* @param tagProperty
* the property of a TagHandler.
*
* @return The tag property with all "[]" replaced with the correct iteration value.
*/
public String addIndexToTagProperty(String tagProperty) {
Map map = this.processTagProperty(tagProperty);
return (String) map.get(PROCESS_STRING);
}
/**
* Adds index value to the first found property matching this Iteration starting at index startIndex.
*
* @param input
* The input String.
* @param startIndex
* The index where search for property begins.
*
* @return A Map containing the modified tag property in PROCESS_STRING key and the index where the modification
* occured in PROCESS_INDEX key.
*/
protected Map addIndex(String input, int startIndex) {
String endProperty = getEndProperty() + "[";
int propertyIndex = input.indexOf(endProperty, startIndex);
int modificationIndex = 0;
// Is the iterate property in the tag property at all?
if (propertyIndex > -1) {
// Make sure the tag property does not already have a number.
if (input.charAt(propertyIndex + endProperty.length()) == ']') {
// Add iteration number to property.
input = input.substring(0, propertyIndex + endProperty.length()) + this.getIndex()
+ input.substring(propertyIndex + endProperty.length());
modificationIndex = propertyIndex + endProperty.length();
}
}
Map ret = new HashMap();
ret.put(PROCESS_INDEX, Integer.valueOf(modificationIndex));
ret.put(PROCESS_STRING, input);
return ret;
}
/**
* Gets the parent.
*
* @return the parent
*/
public IterateContext getParent() {
return parent;
}
/**
* Sets the parent.
*
* @param parent
* the new parent
*/
public void setParent(IterateContext parent) {
this.parent = parent;
}
/**
* Some sub elements have content.
*
* @return true, if successful
*/
public boolean someSubElementsHaveContent() {
return someSubElementsHaveContent;
}
/**
* Sets the some sub elements have content.
*
* @param someSubElementsHaveContent
* the new some sub elements have content
*/
public void setSomeSubElementsHaveContent(boolean someSubElementsHaveContent) {
this.someSubElementsHaveContent = someSubElementsHaveContent;
}
/**
* Checks if is prepend enabled.
*
* @return true, if is prepend enabled
*/
public boolean isPrependEnabled() {
return isPrependEnabled;
}
/**
* Sets the prepend enabled.
*
* @param isPrependEnabled
* the new prepend enabled
*/
public void setPrependEnabled(boolean isPrependEnabled) {
this.isPrependEnabled = isPrependEnabled;
}
}