Revision: 374
Author: tfenne
Date: 2006-08-13 13:50:36 -0700 (Sun, 13 Aug 2006)
ViewCVS: http://svn.sourceforge.net/stripes/?rev=374&view=rev
Log Message:
-----------
Adding a sort attribute to options-collection and options-enumeration.
Refactored OptionsEnumerationTag to extend OptionsCollectionTag, and created a
new BeanComparator for sorting beans based on properties.
Modified Paths:
--------------
trunk/examples/Examples.iml
trunk/examples/web/bugzooky/AddEditBug.jsp
trunk/stripes/resources/stripes.tld
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsCollectionTag.java
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsEnumerationTag.java
Added Paths:
-----------
trunk/stripes/src/net/sourceforge/stripes/util/bean/BeanComparator.java
trunk/tests/src/net/sourceforge/stripes/util/bean/BeanComparatorTest.java
Modified: trunk/examples/Examples.iml
===================================================================
--- trunk/examples/Examples.iml 2006-08-12 14:17:48 UTC (rev 373)
+++ trunk/examples/Examples.iml 2006-08-13 20:50:36 UTC (rev 374)
@@ -76,11 +76,6 @@
</containerElement>
<containerElement type="library" level="module">
<attribute name="method" value="1" />
- <attribute name="URI" value="/WEB-INF/lib/ognl-2.6.7.jar" />
- <url>jar://$MODULE_DIR$/../stripes/lib/deploy/ognl-2.6.7.jar!/</url>
- </containerElement>
- <containerElement type="library" level="module">
- <attribute name="method" value="1" />
<attribute name="URI" value="/WEB-INF/lib/log4j-1.2.9.jar" />
<url>jar://$MODULE_DIR$/../stripes/lib/test/log4j-1.2.9.jar!/</url>
</containerElement>
Modified: trunk/examples/web/bugzooky/AddEditBug.jsp
===================================================================
--- trunk/examples/web/bugzooky/AddEditBug.jsp 2006-08-12 14:17:48 UTC (rev
373)
+++ trunk/examples/web/bugzooky/AddEditBug.jsp 2006-08-13 20:50:36 UTC (rev
374)
@@ -39,7 +39,7 @@
<td>
<stripes:select name="bug.owner.id">
<stripes:options-collection
collection="${personManager.allPeople}"
- label="username"
value="id"/>
+ label="username"
value="id" sort="label"/>
</stripes:select>
</td>
</tr>
Modified: trunk/stripes/resources/stripes.tld
===================================================================
--- trunk/stripes/resources/stripes.tld 2006-08-12 14:17:48 UTC (rev 373)
+++ trunk/stripes/resources/stripes.tld 2006-08-13 20:50:36 UTC (rev 374)
@@ -1092,6 +1092,15 @@
</attribute>
<attribute>
<description>
+ A comma separated list of bean properties by which the
collection
+ should be sorted before rendering the options. Special values
of
+ 'label' and 'value' indicate that the options should be sorted
by the
+ label and value respectively - even when using localized or
derived values.
+ </description>
+
<name>sort</name><required>false</required><rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <description>
The collection of beans to use to generate options. This
value must resolve to
a Collection, and therefore it will most often be an EL
expression
(or a scriptlet, if you must).
@@ -1180,7 +1189,17 @@
</description>
<name>label</name><required>false</required><rtexprvalue>true</rtexprvalue>
</attribute>
+ <attribute>
+ <description>
+ A comma separated list of bean properties by which the enum
values
+ should be sorted before rendering the options. Special values
of
+ 'label' and 'value' indicate that the options should be sorted
by the
+ label and value respectively - even when using localized or
derived values.
+ </description>
+
<name>sort</name><required>false</required><rtexprvalue>true</rtexprvalue>
+ </attribute>
+
<!-- Start: Standard HTML attributes -->
<attribute><description>@accesskey@</description><name>accesskey</name><required>false</required><rtexprvalue>true</rtexprvalue></attribute>
<attribute><description>@class@</description><name>class</name><required>false</required><rtexprvalue>true</rtexprvalue></attribute>
Modified:
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsCollectionTag.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsCollectionTag.java
2006-08-12 14:17:48 UTC (rev 373)
+++
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsCollectionTag.java
2006-08-13 20:50:36 UTC (rev 374)
@@ -18,11 +18,15 @@
import net.sourceforge.stripes.localization.LocalizationUtility;
import net.sourceforge.stripes.util.bean.BeanUtil;
import net.sourceforge.stripes.util.bean.ExpressionException;
+import net.sourceforge.stripes.util.bean.BeanComparator;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import java.util.Collection;
import java.util.Locale;
+import java.util.Collections;
+import java.util.List;
+import java.util.LinkedList;
/**
* <p>Writes a set of [EMAIL PROTECTED] <option value="foo">bar</option>} tags
to the page based on the
@@ -74,7 +78,25 @@
private Collection collection;
private String value;
private String label;
+ private String sort;
+ /**
+ * A little container class that holds an entry in the collection of items
being used
+ * to generate the options, along with the determined label and value
(either from a
+ * property, or a localized value).
+ */
+ public static class Entry {
+ public Object bean, label, value;
+ Entry(Object bean, Object label, Object value) {
+ this.bean = bean;
+ this.label = label;
+ this.value = value;
+ }
+ }
+
+ /** Internal list of entires that is assembled from the items in the
collection. */
+ private List<Entry> entries;
+
/** Sets the collection that will be used to generate options. */
public void setCollection(Collection collection) {
this.collection = collection;
@@ -115,27 +137,50 @@
return label;
}
+ /**
+ * Sets a comma separated list of properties by which the beans in the
collection will
+ * be sorted prior to rendering them as options. 'label' and 'value' are
special case
+ * properties that are used to indicate the generated label and value of
the option.
+ *
+ * @param sort the name of the attribute(s) used to sort the collection of
options
+ */
+ public void setSort(String sort) {
+ this.sort = sort;
+ }
+ /** Gets the comma separated list of properties by which the collection is
sorted. */
+ public String getSort() {
+ return sort;
+ }
+
/**
- * Iterates through the collection and uses an instance of InputOptionTag
to generate each
- * individual option with the correct state. It is assumed that each
element in the collection
+ * Adds an entry to the interal list of items being used to generate
options.
+ * @param item the object represented by the option
+ * @param label the actual label for the option
+ * @param value the actual value for the option
+ */
+ protected void addEntry(Object item, Object label, Object value) {
+ if (this.entries == null) this.entries = new LinkedList<Entry>();
+ this.entries.add(new Entry(item, label, value));
+ }
+
+ /**
+ * Iterates through the collection and generates the list of Entry objects
that can then
+ * be sorted and rendered into options. It is assumed that each element in
the collection
* has non-null values for the properties specified for generating the
label and value.
*
* @return SKIP_BODY in all cases
* @throws JspException if either the label or value attributes specify
properties that are
- * not present on the beans in the collection, or output cannot be
written.
+ * not present on the beans in the collection
*/
public int doStartTag() throws JspException {
String labelProperty = getLabel();
String valueProperty = getValue();
- Locale locale = getPageContext().getRequest().getLocale();
- InputOptionTag tag = new InputOptionTag();
- tag.setParent(this);
- tag.setPageContext(getPageContext());
- tag.getAttributes().putAll(getAttributes());
try {
+ Locale locale = getPageContext().getRequest().getLocale();
+
for (Object item : this.collection) {
Class clazz = item.getClass();
@@ -155,12 +200,7 @@
}
if (localizedLabel != null) label = localizedLabel;
- tag.setLabel(label.toString());
- tag.setValue(value);
- tag.doStartTag();
- tag.doInitBody();
- tag.doAfterBody();
- tag.doEndTag();
+ addEntry(item, label, value);
}
}
catch (ExpressionException ee) {
@@ -173,11 +213,43 @@
}
/**
- * Does nothing.
+ * Optionally sorts the assembled entries and then renders them into a
series of
+ * option tags using an instance of InputOptionTag to do the rendering
work.
*
* @return EVAL_PAGE in all cases.
*/
public int doEndTag() throws JspException {
+ // Determine if we're going to be sorting the collection
+ List<Entry> sortedEntries = new LinkedList<Entry>(this.entries);
+ if (this.sort != null) {
+ String[] props = this.sort.split(" *, *");
+ for (int i=0;i<props.length;++i) {
+ if (!props[i].equals("label") && !props[i].equals("value")) {
+ props[i] = "bean." + props[i];
+ }
+ }
+
+ Collections.sort(sortedEntries,
+ new
BeanComparator(getPageContext().getRequest().getLocale(), props));
+ }
+
+ InputOptionTag tag = new InputOptionTag();
+ tag.setParent(this);
+ tag.setPageContext(getPageContext());
+ tag.getAttributes().putAll(getAttributes());
+
+ for (Entry entry : sortedEntries) {
+ tag.setLabel(entry.label.toString());
+ tag.setValue(value);
+ tag.doStartTag();
+ tag.doInitBody();
+ tag.doAfterBody();
+ tag.doEndTag();
+ }
+
+ // Clean up any temporary state
+ this.entries.clear();
+
return EVAL_PAGE;
}
}
Modified:
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsEnumerationTag.java
===================================================================
---
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsEnumerationTag.java
2006-08-12 14:17:48 UTC (rev 373)
+++
trunk/stripes/src/net/sourceforge/stripes/tag/InputOptionsEnumerationTag.java
2006-08-13 20:50:36 UTC (rev 374)
@@ -21,7 +21,6 @@
import net.sourceforge.stripes.util.bean.ExpressionException;
import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.Tag;
import java.util.Locale;
/**
@@ -58,9 +57,8 @@
*
* @author Tim Fennell
*/
-public class InputOptionsEnumerationTag extends HtmlTagSupport implements Tag {
+public class InputOptionsEnumerationTag extends InputOptionsCollectionTag {
private String className;
- private String label;
/** Sets the fully qualified name of an enumeration class. */
public void setEnum(String name) {
@@ -72,16 +70,6 @@
return this.className;
}
- /** Sets the name of the property that will be used to generate the
option's label. */
- public void setLabel(String label) {
- this.label = label;
- }
-
- /** Gets the name of the property that will be used to generate the
option's label. */
- public String getLabel() {
- return this.label;
- }
-
/**
* Attempts to instantiate the Class object representing the enum and
fetch the values of the
* enum. Then generates an option per value using an instance of an
InputOptionTag.
@@ -110,12 +98,6 @@
Enum[] enums = clazz.getEnumConstants();
- InputOptionTag tag = new InputOptionTag();
- tag.setParent(this);
- tag.setPageContext(getPageContext());
- tag.getAttributes().putAll(getAttributes());
- tag.getAttributes().remove("enum");
-
try {
Locale locale = getPageContext().getRequest().getLocale();
@@ -128,37 +110,23 @@
clazz.getPackage().getName(),
locale);
if (label == null) {
- if (this.label != null) {
- label = BeanUtil.getPropertyValue(this.label, item);
+ if (getLabel() != null) {
+ label = BeanUtil.getPropertyValue(getLabel(), item);
}
else {
label = item.toString();
}
}
- tag.setLabel(label.toString());
- tag.setValue(value);
- tag.doStartTag();
- tag.doInitBody();
- tag.doAfterBody();
- tag.doEndTag();
+ addEntry(item, label, value);
}
}
catch (ExpressionException ee) {
throw new StripesJspException("A problem occurred generating an
options-enumeration. " +
"Most likely either the class [" + getEnum() + "] is not an
enum or, [" +
- this.label + "] is not a valid property of the enum.", ee);
+ getLabel() + "] is not a valid property of the enum.", ee);
}
return SKIP_BODY;
}
-
- /**
- * Does nothing.
- * @return EVAL_PAGE in all cases.
- */
- public int doEndTag() throws JspException {
- return EVAL_PAGE;
- }
-
}
Added: trunk/stripes/src/net/sourceforge/stripes/util/bean/BeanComparator.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/util/bean/BeanComparator.java
(rev 0)
+++ trunk/stripes/src/net/sourceforge/stripes/util/bean/BeanComparator.java
2006-08-13 20:50:36 UTC (rev 374)
@@ -0,0 +1,113 @@
+/* Copyright 2005-2006 Tim Fennell
+ *
+ * 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
+ *
+ * http://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.
+ */
+package net.sourceforge.stripes.util.bean;
+
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.Locale;
+
+/**
+ * <p>A comparator which compares objects based on one or more bean
properties. Nested properties
+ * are fully supported. If a property is non-String and implements [EMAIL
PROTECTED] Comparable} then the
+ * [EMAIL PROTECTED] compareTo()} method is delegated to. Otherwise the
property is converted to a String
+ * and a [EMAIL PROTECTED] Locale} aware [EMAIL PROTECTED] Collator} is used
to to compare property values.</p>
+ *
+ * @author Tim Fennell
+ * @since Stripes 1.5
+ */
+public class BeanComparator implements Comparator<Object> {
+ private Locale locale;
+ private Collator collator;
+ private String[] properties;
+ private PropertyExpression[] expressions;
+
+ /**
+ * Constructs a BeanComparator for comparing beans based on the supplied
set of properties,
+ * using the default system Locale to collate Strings.
+ *
+ * @param properties one or more property names to be used, in order, to
sort beans
+ */
+ public BeanComparator(String... properties) {
+ this(Locale.getDefault(), properties);
+ }
+
+ /**
+ * Constructs a BeanComparator for comparing beans based on the supplied
set of properties,
+ * using the supplied Locale to collate Strings.
+ *
+ * @param locale the Locale to be used for collating Strings
+ * @param properties one or more property names to be used, in order, to
sort beans
+ */
+ public BeanComparator(Locale locale, String... properties) {
+ this.locale = locale;
+ this.collator = Collator.getInstance(locale);
+ this.properties = properties;
+ this.expressions = new PropertyExpression[properties.length];
+
+ for (int i=0; i<properties.length; ++i) {
+ this.expressions[i] =
PropertyExpression.getExpression(properties[i]);
+ }
+ }
+
+ /**
+ * <p>Compares two JavaBeans for order. Returns a negative integer, zero,
or a positive
+ * integer as the first argument sorts earlier, equal to, or later than
the second.</p>
+ *
+ * <p>Iterates through the properties supplied in the constructor
comparing the values of
+ * each property for the two beans. As soon as a property is found that
supplied a non-equal
+ * ordering, the ordering is returned. If all properties are equal, will
return 0.</p>
+ *
+ * @param o1 the first object to be compared, must not be null.
+ * @param o2 the second object to be compared, must not be null.
+ * @return a negative integer, zero, or a positive integer as the first
argument is less than,
+ * equal to, or greater than the second.
+ * @throws ClassCastException if the arguments' types, or the types of the
properties,
+ * prevent them from being compared by this Comparator.
+ */
+ public int compare(Object o1, Object o2) {
+ int retval = 0;
+ Collator collator = Collator.getInstance(this.locale);
+
+ for (PropertyExpression expression : this.expressions) {
+ PropertyExpressionEvaluation e1 = new
PropertyExpressionEvaluation(expression, o1);
+ PropertyExpressionEvaluation e2 = new
PropertyExpressionEvaluation(expression, o2);
+
+ Object prop1 = e1.getValue();
+ Object prop2 = e2.getValue();
+
+ if (prop1 == null && prop2 == null) {
+ retval = 0;
+ }
+ else if (prop1 == null) {
+ retval = 1;
+ }
+ else if (prop2 == null) {
+ retval = -1;
+ }
+ else if ( !(prop1 instanceof String) && prop1 instanceof
Comparable) {
+ retval = ((Comparable) prop1).compareTo(prop2);
+ }
+ else {
+ String string1 = prop1.toString();
+ String string2 = prop2.toString();
+ retval = this.collator.compare(string1, string2);
+ }
+
+ if (retval != 0) break;
+ }
+
+ return retval;
+ }
+}
Added: trunk/tests/src/net/sourceforge/stripes/util/bean/BeanComparatorTest.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/util/bean/BeanComparatorTest.java
(rev 0)
+++ trunk/tests/src/net/sourceforge/stripes/util/bean/BeanComparatorTest.java
2006-08-13 20:50:36 UTC (rev 374)
@@ -0,0 +1,126 @@
+package net.sourceforge.stripes.util.bean;
+
+import org.testng.annotations.Test;
+import org.testng.Assert;
+import net.sourceforge.stripes.test.TestBean;
+import net.sourceforge.stripes.test.TestEnum;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Test cases for the BeanComparator class that sorts lists of JavaBeans based
on
+ * their properties.
+ *
+ * @author Tim Fennell
+ */
+public class BeanComparatorTest {
+ @Test(groups="fast")
+ public void testSimplePropertySort() throws Exception {
+ List<TestBean> beans = new ArrayList<TestBean>();
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("hello");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("goodbye");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("whatever");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("huh?");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("no way!");
+
+ Collections.sort(beans, new BeanComparator("stringProperty"));
+ Assert.assertEquals(beans.get(0).getStringProperty(), "goodbye");
+ Assert.assertEquals(beans.get(1).getStringProperty(), "hello");
+ Assert.assertEquals(beans.get(2).getStringProperty(), "huh?");
+ Assert.assertEquals(beans.get(3).getStringProperty(), "no way!");
+ Assert.assertEquals(beans.get(4).getStringProperty(), "whatever");
+ }
+
+ @Test(groups="fast")
+ public void testSimpleMultiPropertySort() throws Exception {
+ List<TestBean> beans = new ArrayList<TestBean>();
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setLongProperty(2l);
+ beans.get(beans.size()-1).setStringProperty("hello");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setLongProperty(2l);
+ beans.get(beans.size()-1).setStringProperty("goodbye");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setLongProperty(1l);
+ beans.get(beans.size()-1).setStringProperty("whatever");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setLongProperty(1l);
+ beans.get(beans.size()-1).setStringProperty("huh?");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setLongProperty(3l);
+ beans.get(beans.size()-1).setStringProperty("no way!");
+
+ Collections.sort(beans, new BeanComparator("longProperty",
"stringProperty"));
+ Assert.assertEquals(beans.get(0).getStringProperty(), "huh?");
+ Assert.assertEquals(beans.get(1).getStringProperty(), "whatever");
+ Assert.assertEquals(beans.get(2).getStringProperty(), "goodbye");
+ Assert.assertEquals(beans.get(3).getStringProperty(), "hello");
+ Assert.assertEquals(beans.get(4).getStringProperty(), "no way!");
+ }
+
+ @Test(groups="fast")
+ public void testNullPropertySort() throws Exception {
+ List<TestBean> beans = new ArrayList<TestBean>();
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("hello");
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty(null);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("whatever");
+
+ Collections.sort(beans, new BeanComparator("stringProperty"));
+ Assert.assertEquals(beans.get(0).getStringProperty(), "hello");
+ Assert.assertEquals(beans.get(1).getStringProperty(), "whatever");
+ Assert.assertEquals(beans.get(2).getStringProperty(), null);
+ }
+
+ @Test(groups="fast")
+ public void testNullPropertySort2() throws Exception {
+ List<TestBean> beans = new ArrayList<TestBean>();
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty(null);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty(null);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setStringProperty("whatever");
+
+ Collections.sort(beans, new BeanComparator("stringProperty"));
+ Assert.assertEquals(beans.get(0).getStringProperty(), "whatever");
+ Assert.assertEquals(beans.get(1).getStringProperty(), null);
+ Assert.assertEquals(beans.get(2).getStringProperty(), null);
+ }
+
+ @Test(groups="fast")
+ public void testNestedPropertySort() throws Exception {
+ List<TestBean> beans = new ArrayList<TestBean>();
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setNestedBean(new TestBean());
+
beans.get(beans.size()-1).getNestedBean().setEnumProperty(TestEnum.Fourth);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setNestedBean(new TestBean());
+
beans.get(beans.size()-1).getNestedBean().setEnumProperty(TestEnum.Second);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setNestedBean(new TestBean());
+
beans.get(beans.size()-1).getNestedBean().setEnumProperty(TestEnum.Ninth);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setNestedBean(new TestBean());
+
beans.get(beans.size()-1).getNestedBean().setEnumProperty(TestEnum.Eight);
+ beans.add(new TestBean());
+ beans.get(beans.size()-1).setNestedBean(new TestBean());
+
beans.get(beans.size()-1).getNestedBean().setEnumProperty(TestEnum.First);
+
+ Collections.sort(beans, new BeanComparator("nestedBean.enumProperty"));
+ Assert.assertEquals(beans.get(0).getNestedBean().getEnumProperty(),
TestEnum.First);
+ Assert.assertEquals(beans.get(1).getNestedBean().getEnumProperty(),
TestEnum.Second);
+ Assert.assertEquals(beans.get(2).getNestedBean().getEnumProperty(),
TestEnum.Fourth);
+ Assert.assertEquals(beans.get(3).getNestedBean().getEnumProperty(),
TestEnum.Eight);
+ Assert.assertEquals(beans.get(4).getNestedBean().getEnumProperty(),
TestEnum.Ninth);
+ }
+}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development