my dog learned polymorphism*
The moose likes Struts and the fly likes Struts logic:iterate weirdness Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login


Win a copy of Android Security Essentials Live Lessons this week in the Android forum!
JavaRanch » Java Forums » Frameworks » Struts
Bookmark "Struts logic:iterate weirdness" Watch "Struts logic:iterate weirdness" New topic
Author

Struts logic:iterate weirdness

Jon Baxter
Greenhorn

Joined: Dec 03, 2004
Posts: 9
<logic:iterate name="multiForm" id="foo" property="keys" indexId="index" >
<tr><td>
<html:text name="multiForm" property='<%="keysIndexed["+index+"]"%>' />
</td></tr>
</logic:iterate>


public class MultiFieldForm extends ValidatorForm {

private List keys = new ArrayList();

public List getKeys() {
return keys;
}
public void setKeys(List keys) {
this.keys = keys;
}
public String getKeysIndexed(int index) {
return (String) keys.get(index);
}

public void setKeysIndexed(int index, String value) {
keys.add(index,value);
}
}


The above code throws an ArrayIndexOutofBoundException after 3 entries
s penumudi
Ranch Hand

Joined: Nov 17, 2004
Posts: 113
Hi floyd,

I am trying something similar as of this.
I really don't understand the concept of iterating collection in form bean using logic:iterate.

In you logic:iterate you have used name="multiForm". Does this do not point you action form?

I would really appreciate if you could explain this concept.

Thank you for your time
[ December 08, 2004: Message edited by: s penumudi ]
Marc Peabody
pie sneak
Sheriff

Joined: Feb 05, 2003
Posts: 4727

The reason for the issue is because the submissions do not happen in order of index.

ArrayList's add method allows you to specify an index as high as one beyond the current highest index. If it tries an add with index of 5 on an ArrayList with size 2, an ArrayIndexOutOfBoundsException is thrown.

Add this output line in your code to see that the indexes are not done in order.
public void setKeysIndexed(int index, String value) {
System.out.println(index);
keys.add(index,value);
}

You'll have to do something like this for it to work:

[ December 08, 2004: Message edited by: Marc Peabody ]

A good workman is known by his tools.
Corey McGlone
Ranch Hand

Joined: Dec 20, 2001
Posts: 3271
Pink Floyd,

Welcome to the Ranch. I hope you enjoy it here. However, we have a standard naming policy here and we try to avoid obviously fictitious names (such as Pink Floyd). I'd appreciate it if you'd take a moment to read that naming policy and update your Publicly Displayed Name here.

Thanks.


SCJP Tipline, etc.
Junilu Lacar
Bartender

Joined: Feb 26, 2001
Posts: 4447
    
    5

MP: You'll have to do something like this for it to work:


To avoid the for loop and the call to remove() before add(), you could just use ensureCapacity(int) and set(int, Object).


Junilu - [How to Ask Questions] [How to Answer Questions]
Jon Baxter
Greenhorn

Joined: Dec 03, 2004
Posts: 9
It looks like the problem is deep within BeanUtils.populate(..) method which
is what is called ultimately for mapping request parameters to bean properties.

The BeanUtils.populate(..) method is called in the processPopulate() method
of the RequestProcessor class (See Struts src) and well before the execute(..) method of the action is called. This populate method is what calls your indexed getter/setter on your form.


Yes, the order of the parameters submitted is random, and this random order
throws off BeanUtils.populate(..). To illustrate..


<pre>

public class BeanUtilTest extends TestCase {

private int INPUT_NUM = 10;
public void testPopulateMethod() {

HashMap p = new HashMap();

for (int i = 0; i < INPUT_NUM; ++i) {
String key = "item[" + i + "].test";
String[] val = new String[] { String.valueOf(i) };
p.put(key, val);
}

System.out.println(p);

Bean bean = new Bean();
try {
BeanUtils.populate(bean, p);
} catch (Exception e) {
e.printStackTrace();
}

System.out.println(bean);
}

}


public class Bean {

private ArrayList items = new ArrayList();

public ArrayList getItems() {
return items;
}

public void setItems(ArrayList items) {
this.items = items;
}

public Item getItem(int index) {
if (index >= items.size()) {
Object o = new Item();
items.add(o);
return (Item)o;
}
return (Item) items.get(index);
}

public void setItem(int index, Object o) {
items.add(o);
}

public String toString() {
return items.toString();
}


public class Item {

private String test;

public Object getTest() {
return test;
}
public void setTest(Object test) {
this.test = (String)test;
}

public String toString() { return test; }
}

</pre>

When you run this the result is..[0, 1, 2, 7, 4, 5].

When you convert HashMap p = new HashMap() to

LinkedHashMap p = new LinkedHashMap() to

the result is..[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (as you would expect).

The thing is the Apache code uses java.util.HashMap for storing the request parameters.

At this point.. the behaviour of BeanUtils.populate(..) is erratic. I don't
see how modifying the indexed getter/setters in your form is going to make
any difference.

List based indexed properties in Struts are broken period.!
Jon Baxter
Greenhorn

Joined: Dec 03, 2004
Posts: 9
Modified the Bean class to maintain the order in a TreeMap.


public class Bean {

private TreeMap _items = new TreeMap();

public ArrayList getItems() {
ArrayList l = new ArrayList();
for (Iterator ii = _items.entrySet().iterator(); ii.hasNext() {
l.add(ii.next());
}
return l;
}

public void setItems(ArrayList items) {
for (int i = 0; i < items.size(); ++i) {
_items.put(new Integer(i), items.get(i));
}
}

public Item getItem(int index) {
System.out.println("Getter called " + index);

Integer ii = new Integer(index);
if (!_items.containsKey(ii)) {
_items.put(new Integer(index), new Item());
}
return (Item) _items.get(ii);
}


public String toString() {
return _items.toString();
}

}

This looks like it fixed it.
Junilu Lacar
Bartender

Joined: Feb 26, 2001
Posts: 4447
    
    5

Originally posted by Jon Baxter:
List based indexed properties in Struts are broken period.!


Hmmm...I wouldn't be too quick to jump to that conclusion. It really doesn't matter what the order of submission is, all parameters will eventually get processed anyway. The key is how you implement your getter and setter.

Try this version of your getter/setter:

private ArrayList items = new ArrayList();

public Item getItem(int index) {
return (Item) items.get(index);
}

public void setItem(int index, Object o) {
items.ensureCapacity(index);
items.set(index, o);
}
Joseph Hatton
Greenhorn

Joined: Dec 12, 2002
Posts: 20
First off, let break this down. From the first discussion thread you have this code:

1 <logic:iterate name="multiForm" id="foo" property="keys" indexId="index" >
2 <tr><td>
3 <html:text name="multiForm" property='<%="keysIndexed["+index+"]"%>' />
4 </td></tr>
5 </logic:iterate>

I see that you are trying to leverage Struts Forms.
First line looks good. Make sure that the form "name" specified above matches the name specifed in struts-config.xml.

Third line is completely wrong.
(1)You are using the Struts Form bean name as your input textfield name. Also, what line 3 is saying that I am creating input textfield(s) with property of "keysIndexed" name. Which the property "keyIndexed" name MUST be included in the MultiFieldForm as gettters and setters. Why? When you add the Struts tag "<html:text property="somename"/>", it binds the getter and setter method to that input textfield.

(2) Unless you use Scriptlets to access the form bean, in struts you cannot access a method that has PARAMETER VALUE. getBean1(), getBean2()... Getting a method with int parameter values will not be called.

(3) To access the Form bean, this scriptlet does nothing to access the Struts Form Bean. "<%="keysIndexed["+index+"]"%>" , this is not correct. This is the correct syntax: <bean:write name="foo" property="someMethod"/>
The "id" is specified in the first line to reference the object. The bean tags are used to access the methods in the form

(4)<logic:iterate> can be very frustrating if not used properly. Especially if you are looping throught a LIST. There is a special way to loop through a list of objects.

Hope that helps. Sorry for being short winded. Doing this at work.
Junilu Lacar
Bartender

Joined: Feb 26, 2001
Posts: 4447
    
    5

Originally posted by Joseph Hatton:
First off, let break this down. From the first discussion thread you have this code:

1 <logic:iterate name="multiForm" id="foo" property="keys" indexId="index" >
2 <tr><td>
3 <html:text name="multiForm" property='<%="keysIndexed["+index+"]"%>' />
4 </td></tr>
5 </logic:iterate>



>> Third line is completely wrong.

Agreed, there's something wrong but it's not in the way the attribute values are specified. It's in the way the getters and setters are implemented (see my reply above). Specifying name="multiForm" is redundant but there's nothing functionally/logically wrong with doing so.

You are using the Struts Form bean name as your input textfield name.

Actually, the "name" attribute indicates the attribute key of the bean that will be used to set the "value" attribute of the rendered HTML input tag. The "property" attribute renders an HTML input tag "name" attribute.

in struts you cannot access a method that has PARAMETER VALUE. getBean1(), getBean2()... Getting a method with int parameter values will not be called.

Not quite true. Struts tags can be used to access indexed and mapped properties. These properties can be backed by an array, List, or Map.
Joseph Hatton
Greenhorn

Joined: Dec 12, 2002
Posts: 20
Junilu Lacar,

Thanks for the correction. I should have mention that normally, you cannot access parameter values unless it is properly mappe in the Struts-config.xml form. However, Index parameter ONLY applies to String Arrays. You cannot do that to list of Person object, Address object or Any Bean objects. I should have stated clearly my intentions.
Marc Peabody
pie sneak
Sheriff

Joined: Feb 05, 2003
Posts: 4727


At this point.. the behaviour of BeanUtils.populate(..) is erratic. I don't see how modifying the indexed getter/setters in your form is going to make any difference.

List based indexed properties in Struts are broken period.!

Jon,
You would have seen it, had you tried it. Just because you gave up on it does not mean it is "broken period".
I don't understand what stopped you from inserting 2 lines of code to try what was recommended other than some presumptuous doubt. I'm further perplexed that you wrote an entire test case to prove what could have been proven with the single line of System.out code I recommended.
OK, I'm done venting now.
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Struts logic:iterate weirdness
 
Similar Threads
problem laying out element of ordered list with logic iterate
Checkboxes in iterate loop
Indexed Setting/properties
Exception thrown by getter for property
readio with index properties. need help