This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 3038ee6f6 Refactor objecttools package.
3038ee6f6 is described below

commit 3038ee6f6bb943e78d07ecf53c0c4e6e4669f959
Author: JamesBognar <[email protected]>
AuthorDate: Sun Jun 12 03:12:18 2022 -0400

    Refactor objecttools package.
---
 .../main/java/org/apache/juneau/BeanSession.java   |   7 +-
 .../juneau/objecttools/NumberMatcherFactory.java   |  15 +-
 .../apache/juneau/objecttools/ObjectPaginator.java | 109 ++--
 .../apache/juneau/objecttools/ObjectSearcher.java  |  50 +-
 .../apache/juneau/objecttools/ObjectSorter.java    |  35 ++
 .../apache/juneau/objecttools/ObjectViewer.java    |  22 +
 .../org/apache/juneau/objecttools/PageArgs.java    |  45 +-
 .../org/apache/juneau/objecttools/SearchArgs.java  |  31 +-
 .../org/apache/juneau/objecttools/SortArgs.java    |  48 +-
 .../juneau/objecttools/StringMatcherFactory.java   |  16 +-
 .../juneau/objecttools/TimeMatcherFactory.java     |  18 +-
 .../org/apache/juneau/objecttools/ViewArgs.java    |  33 +-
 .../02.juneau-marshall/25.jm.ObjectTools.html      | 546 ++++++++++++++++++++-
 ...ectorTest.java => ObjectIntrospector_Test.java} |   4 +-
 ...bjectMergerTest.java => ObjectMerger_Test.java} |   4 +-
 .../juneau/objecttools/ObjectPaginator_Test.java   | 105 ++++
 .../{ObjectRestTest.java => ObjectRest_Test.java}  |  22 +-
 ...tSearcherTest.java => ObjectSearcher_Test.java} | 187 +++----
 ...bjectSorterTest.java => ObjectSorter_Test.java} | 190 +++----
 ...bjectViewerTest.java => ObjectViewer_Test.java} | 253 +++++-----
 20 files changed, 1347 insertions(+), 393 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index 14ab0c761..4a8846f59 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -759,9 +759,12 @@ public class BeanSession extends ContextSession {
                                        Collection l = 
to.canCreateNewInstance(outer) ? (Collection)to.newInstance(outer) : to.isSet() 
? set() : new JsonList(this);
                                        ClassMeta elementType = 
to.getElementType();
 
-                                       if (from.isArray())
-                                               for (Object o : (Object[])value)
+                                       if (from.isArray()) {
+                                               for (int i = 0; i < 
Array.getLength(value); i++) {
+                                                       Object o = 
Array.get(value, i);
                                                        
l.add(elementType.isObject() ? o : convertToMemberType(l, o, elementType));
+                                               }
+                                       }
                                        else if (from.isCollection())
                                                ((Collection)value).forEach(x 
-> l.add(elementType.isObject() ? x : convertToMemberType(l, x, elementType)));
                                        else if (from.isMap())
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/NumberMatcherFactory.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/NumberMatcherFactory.java
index 9b8b12cf9..4a431b93f 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/NumberMatcherFactory.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/NumberMatcherFactory.java
@@ -20,7 +20,20 @@ import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
- * Number matcher used by {@link ObjectSearcher}
+ * Provides number search capability for the {@link ObjectSearcher} class.
+ *
+ * <p>
+ *     The class provides searching based on the following patterns:
+ * </p>
+ * <ul>
+ *     <li><js>"property=1"</js> - A single number
+ *     <li><js>"property=1 2"</js> - Multiple OR'ed numbers
+ *     <li><js>"property=-1 -2"</js> - Multiple OR'ed negative numbers
+ *     <li><js>"property=1-2"</js>,<js>"property=-2--1"</js>  - A range of 
numbers (whitespace ignored)
+ *     <li><js>"property=1-2 4-5"</js> - Multiple OR'ed ranges
+ *     
<li><js>"property=&lt;1"</js>,<js>"property=&lt;=1"</js>,<js>"property=&gt;1"</js>,<js>"property=&gt;=1"</js>
 - Open-ended ranges
+ *     <li><js>"property=!1"</js>,<js>"property=!1-2"</js> - Negation
+ * </ul>
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectPaginator.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectPaginator.java
index 3424e94a6..f8fd6e17c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectPaginator.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectPaginator.java
@@ -12,6 +12,11 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.objecttools;
 
+import static java.util.Arrays.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+
 import org.apache.juneau.*;
 
 /**
@@ -25,7 +30,7 @@ import org.apache.juneau.*;
  *     <li class='extlink'>{@source}
  * </ul>
  */
-public final class ObjectPaginator implements ObjectTool<Object> {
+public final class ObjectPaginator implements ObjectTool<PageArgs> {
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Static
@@ -43,50 +48,66 @@ public final class ObjectPaginator implements 
ObjectTool<Object> {
        // Instance
        
//-----------------------------------------------------------------------------------------------------------------
 
+       /**
+        * Convenience method for executing the paginator.
+        *
+        * @param <R> The collection element type.
+        * @param input The input.  Must be a collection or array of objects.
+        * @param pos The zero-index position to start from.
+        * @param limit The max number of entries to retrieve.
+        * @return A sublist of representing the entries from the position with 
the specified limit.
+        */
+       @SuppressWarnings("unchecked")
+       public <R> List<R> run(Object input, int pos, int limit) {
+               BeanSession bs = BeanContext.DEFAULT_SESSION;
+               Object r = run(BeanContext.DEFAULT_SESSION, input, 
PageArgs.create(pos, limit));
+               if (r instanceof List)
+                       return (List<R>)r;
+               return bs.convertToType(r, List.class);
+       }
+
        @Override /* ObjectTool */
-       public Object run(BeanSession session, Object input, Object args) {
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       public Object run(BeanSession session, Object input, PageArgs args) {
+
+               if (input == null)
+                       return null;
+
+               ClassMeta type = session.getClassMetaForObject(input);
+
+               if (! type.isCollectionOrArray())
+                       return input;
+
+               int pos = args.getPosition();
+               int limit = args.getLimit();
+
+               if (type.isArray()) {
+                       int size = Array.getLength(input);
+                       int end = (limit+pos >= size) ? size : limit + pos;
+                       pos = Math.min(pos, size);
+                       ClassMeta<?> et = type.getElementType();
+                       if (! et.isPrimitive())
+                               return copyOfRange((Object[])input, pos, end);
+                       if (et.is(boolean.class))
+                               return copyOfRange((boolean[])input, pos, end);
+                       if (et.is(byte.class))
+                               return copyOfRange((byte[])input, pos, end);
+                       if (et.is(char.class))
+                               return copyOfRange((char[])input, pos, end);
+                       if (et.is(double.class))
+                               return copyOfRange((double[])input, pos, end);
+                       if (et.is(float.class))
+                               return copyOfRange((float[])input, pos, end);
+                       if (et.is(int.class))
+                               return copyOfRange((int[])input, pos, end);
+                       if (et.is(long.class))
+                               return copyOfRange((long[])input, pos, end);
+                       return copyOfRange((short[])input, pos, end);
+               }
 
-//             if (input == null)
-//                     return null;
-//
-//             ClassMeta type = session.getClassMetaForObject(input);
-//
-//             if (! type.isCollectionOrArray())
-//                     return input;
-//
-//             int pos = args.getPosition();
-//             int limit = args.getLimit();
-//
-//             if (type.isArray()) {
-//                     int size = Array.getLength(input);
-//                     int end = (limit+pos >= size) ? size : limit + pos;
-//                     pos = Math.min(pos, size);
-//                     ClassMeta<?> et = type.getElementType();
-//                     if (! et.isPrimitive())
-//                             return copyOfRange((Object[])input, pos, end);
-//                     if (et.isType(boolean.class))
-//                             return copyOfRange((boolean[])input, pos, end);
-//                     if (et.isType(byte.class))
-//                             return copyOfRange((byte[])input, pos, end);
-//                     if (et.isType(char.class))
-//                             return copyOfRange((char[])input, pos, end);
-//                     if (et.isType(double.class))
-//                             return copyOfRange((double[])input, pos, end);
-//                     if (et.isType(float.class))
-//                             return copyOfRange((float[])input, pos, end);
-//                     if (et.isType(int.class))
-//                             return copyOfRange((int[])input, pos, end);
-//                     if (et.isType(long.class))
-//                             return copyOfRange((long[])input, pos, end);
-//                     if (et.isType(short.class))
-//                             return copyOfRange((short[])input, pos, end);
-//                     return null;
-//             }
-//
-//             List l = type.isList() ? (List)input : new 
ArrayList((Collection)input);
-//             int end = (limit+pos >= l.size()) ? l.size() : limit + pos;
-//             pos = Math.min(pos, l.size());
-//             return l.subList(pos, end);
-               return null;
+               List l = type.isList() ? (List)input : new 
ArrayList((Collection)input);
+               int end = (limit+pos >= l.size()) ? l.size() : limit + pos;
+               pos = Math.min(pos, l.size());
+               return l.subList(pos, end);
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java
index 8943bdd86..205caa434 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSearcher.java
@@ -21,10 +21,35 @@ import java.util.*;
 import org.apache.juneau.*;
 
 /**
- * Designed to provide paging on POJOs consisting of arrays and collections.
+ * Provides searches against arrays and collections of maps and beans.
  *
  * <p>
- * Allows you to quickly return subsets of arrays and collections based on 
position/limit arguments.
+ *     The {@link ObjectSearcher} class is designed to provide searches across 
arrays and collections of beans and maps.
+ *     It allows you to quickly filter beans and maps using simple yet 
sophisticated search arguments.
+ * </p>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     MyBean[] <jv>arrayOfBeans</jv> = ...;
+ *     ObjectSearcher <jv>searcher</jv> = ObjectSearcher.<jsm>create</jsm>();
+ *
+ *     BeanSession <jv>beanSession</jv> = 
BeanContext.<jsf>DEFAULT</jsf>.createSession();
+ *
+ *     <jc>// Find beans whose 'foo' property is 'bar'.</jc>
+ *     SearchArgs <jv>searchArgs</jv> = SearchArgs.create("foo=X,bar=Y");
+ *
+ *     <jc>// Returns a list of beans whose 'foo' property is 'X' and 'bar' 
property is 'Y'.</jc>
+ *     Object <jv>result</jv> = searcher.run(<jv>beanSession</jv>, 
<jv>arrayOfBeans</jv>, <jv>searchArgs</jv>);
+ * </p>
+ * <p>
+ *     The default searcher is configured with the following matcher factories 
that provides the capabilities of matching
+ *     against various data types:
+ * </p>
+ * <ul class='javatreec'>
+ *     <li class='jc'>{@link StringMatcherFactory}
+ *     <li class='jc'>{@link NumberMatcherFactory}
+ *     <li class='jc'>{@link TimeMatcherFactory}
+ * </ul>
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
@@ -50,9 +75,9 @@ public final class ObjectSearcher implements 
ObjectTool<SearchArgs> {
         *      The matcher factories to use.
         *      <br>If not specified, uses the following:
         *      <ul>
+        *              <li>{@link StringMatcherFactory#DEFAULT}
         *              <li>{@link NumberMatcherFactory#DEFAULT}
         *              <li>{@link TimeMatcherFactory#DEFAULT}
-        *              <li>{@link StringMatcherFactory#DEFAULT}
         *      </ul>
         * @return A new {@link ObjectSearcher} object.
         */
@@ -82,6 +107,25 @@ public final class ObjectSearcher implements 
ObjectTool<SearchArgs> {
                this.factories = factories.length == 0 ? new 
MatcherFactory[]{NumberMatcherFactory.DEFAULT, TimeMatcherFactory.DEFAULT, 
StringMatcherFactory.DEFAULT} : factories;
        }
 
+       /**
+        * Convenience method for executing the searcher.
+        *
+        * @param input The input.
+        * @param searchArgs The search arguments.  See {@link SearchArgs} for 
format.
+        * @return A list of maps/beans matching the
+        */
+       @SuppressWarnings("unchecked")
+       public <R> List<R> run(Object input, String searchArgs) {
+               Object r = run(BeanContext.DEFAULT_SESSION, input, 
SearchArgs.create(searchArgs));
+               if (r instanceof List)
+                       return (List<R>)r;
+               if (r instanceof Collection)
+                       return new ArrayList<R>((Collection)r);
+               if (r.getClass().isArray())
+                       return Arrays.asList((R[])r);
+               return null;
+       }
+
        @Override /* ObjectTool */
        public Object run(BeanSession session, Object input, SearchArgs args) {
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSorter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSorter.java
index 33e40e866..e4774511b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSorter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectSorter.java
@@ -23,6 +23,23 @@ import org.apache.juneau.internal.*;
 /**
  * Sorts arrays and collections of maps and beans.
  *
+ * <p>
+ *     The {@link ObjectSorter} class is designed to sort arrays and 
collections of beans and maps.
+ * </p>
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     MyBean[] <jv>arrayOfBeans</jv> = ...;
+ *     ObjectSorter <jv>sorter</jv> = ObjectSorter.<jsm>create</jsm>();
+ *
+ *     BeanSession <jv>beanSession</jv> = 
BeanContext.<jsf>DEFAULT</jsf>.createSession();
+ *
+ *     <jc>// Sort beans by foo ascending then bar descending.</jc>
+ *     SortArgs <jv>sortArgs</jv> = SortArgs.create("foo,bar-");
+ *
+ *     <jc>// Returns a list of beans sorted accordingly.</jc>
+ *     Object <jv>result</jv> = sorter.run(<jv>beanSession</jv>, 
<jv>arrayOfBeans</jv>, <jv>sortArgs</jv>);
+ * </p>
+ *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
  *     <li class='extlink'>{@source}
@@ -53,6 +70,24 @@ public final class ObjectSorter implements 
ObjectTool<SortArgs> {
        // Instance
        
//-----------------------------------------------------------------------------------------------------------------
 
+       /**
+        * Convenience method for executing the sorter.
+        *
+        * @param input The input.
+        * @param sortArgs The sort arguments.  See {@link SortArgs} for format.
+        * @return A list of maps/beans matching the
+        */
+       public <R> List<R> run(Object input, String sortArgs) {
+               Object r = run(BeanContext.DEFAULT_SESSION, input, 
SortArgs.create(sortArgs));
+               if (r instanceof List)
+                       return (List<R>)r;
+               if (r instanceof Collection)
+                       return new ArrayList<R>((Collection)r);
+               if (r.getClass().isArray())
+                       return Arrays.asList((R[])r);
+               return null;
+       }
+
        @Override /* ObjectTool */
        public Object run(BeanSession session, Object input, SortArgs args) {
                if (input == null)
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectViewer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectViewer.java
index 95d2c6969..e102d8b32 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectViewer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectViewer.java
@@ -56,6 +56,28 @@ public final class ObjectViewer implements 
ObjectTool<ViewArgs> {
        // Instance
        
//-----------------------------------------------------------------------------------------------------------------
 
+       /**
+        * Runs this viewer on the specified collection or array of objects.
+        *
+        * @param input The input.  Must be an array or collection.
+        * @param args The view args.  See {@link ViewArgs} for format.
+        * @return The extracted properties from the collection of objects.
+        */
+       public List<Map> run(Object input, String args) {
+               return (List<Map>)run(BeanContext.DEFAULT_SESSION, input, 
ViewArgs.create(args));
+       }
+
+       /**
+        * Runs this viewer on a singleton object.
+        *
+        * @param input The input.  Must be a singleton object.
+        * @param args The view args.  See {@link ViewArgs} for format.
+        * @return The extracted properties from the object.
+        */
+       public Map runSingle(Object input, String args) {
+               return (Map)run(BeanContext.DEFAULT_SESSION, input, 
ViewArgs.create(args));
+       }
+
        @Override /* ObjectTool */
        public Object run(BeanSession session, Object input, ViewArgs args) {
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/PageArgs.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/PageArgs.java
index 47ad30827..96583d475 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/PageArgs.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/PageArgs.java
@@ -22,21 +22,54 @@ package org.apache.juneau.objecttools;
  */
 public class PageArgs {
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Static
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Static creator.
+        *
+        * @param position The zero-indexed position to start the page on.
+        * @param limit The number of rows to return.
+        *
+        * @return A new {@link PageArgs} object.
+        */
+       public static PageArgs create(int position, int limit) {
+               return new PageArgs(position, limit);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       final int position, limit;
+
+       /**
+        * Constructor.
+        *
+        * @param position The zero-indexed position to start the page on.
+        * @param limit The number of rows to return.
+        */
+       public PageArgs(int position, int limit) {
+               this.position = position;
+               this.limit = limit;
+       }
+
        /**
-        * TODO
+        * Returns the number of rows to return.
         *
-        * @return TODO
+        * @return The number of rows to return.
         */
        public int getLimit() {
-               return 0;
+               return limit;
        }
 
        /**
-        * TODO
+        * Returns the zero-indexed position to start the page on.
         *
-        * @return TODO
+        * @return The zero-indexed position to start the page on.
         */
        public int getPosition() {
-               return 0;
+               return position;
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SearchArgs.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SearchArgs.java
index c72ecc5e0..80d26c4ac 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SearchArgs.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SearchArgs.java
@@ -20,7 +20,7 @@ import java.util.*;
 import org.apache.juneau.internal.*;
 
 /**
- * Encapsulates arguments for the {@link ObjectSorter} class.
+ * Encapsulates arguments for the {@link ObjectSearcher} class.
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
@@ -29,8 +29,35 @@ import org.apache.juneau.internal.*;
  */
 public class SearchArgs {
 
-       private final Map<String,String> search = map();
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Static
+       
//-----------------------------------------------------------------------------------------------------------------
 
+       /**
+        * Static creator.
+        *
+        * @param searchArgs Comma-delimited list of search arguments.
+        * @return A new {@link SearchArgs} object.
+        */
+       public static SearchArgs create(String searchArgs) {
+               return new SearchArgs(searchArgs);
+       }
+
+       /**
+        * Static creator.
+        *
+        * @param searchArgs List of search arguments.
+        * @return A new {@link SearchArgs} object.
+        */
+       public static SearchArgs create(List<String> searchArgs) {
+               return new SearchArgs(searchArgs);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private final Map<String,String> search = map();
 
        /**
         * Constructor.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SortArgs.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SortArgs.java
index 2db172133..1d8f09913 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SortArgs.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/SortArgs.java
@@ -27,13 +27,55 @@ import java.util.*;
  */
 public class SortArgs {
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Static
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Static creator.
+        *
+        * @param sortArgs
+        *      Comma-delimited list of sort arguments.
+        *      <br>Values are of the following forms:
+        *      <ul>
+        *              <li><js>"column"</js> - Sort column ascending.
+        *              <li><js>"column+"</js> - Sort column ascending.
+        *              <li><js>"column-"</js> - Sort column descending.
+        *      </ul>
+        * @return A new {@link SortArgs} object.
+        */
+       public static SortArgs create(String sortArgs) {
+               return new SortArgs(sortArgs);
+       }
+
+       /**
+        * Static creator.
+        *
+        * @param sortArgs
+        *      Sort arguments.
+        *      <br>Values are of the following forms:
+        *      <ul>
+        *              <li><js>"column"</js> - Sort column ascending.
+        *              <li><js>"column+"</js> - Sort column ascending.
+        *              <li><js>"column-"</js> - Sort column descending.
+        *      </ul>
+        * @return A new {@link SortArgs} object.
+        */
+       public static SortArgs create(List<String> sortArgs) {
+               return new SortArgs(sortArgs);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
        private final Map<String,Boolean> sort;
 
        /**
         * Constructor.
         *
         * @param sortArgs
-        *      Sort arguments.
+        *      Comma-delimited list of sort arguments.
         *      <br>Values are of the following forms:
         *      <ul>
         *              <li><js>"column"</js> - Sort column ascending.
@@ -41,8 +83,8 @@ public class SortArgs {
         *              <li><js>"column-"</js> - Sort column descending.
         *      </ul>
         */
-       public SortArgs(String...sortArgs) {
-               this(alist(sortArgs));
+       public SortArgs(String sortArgs) {
+               this(alist(split(sortArgs)));
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
index 3d8735c77..c7f5a321c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/StringMatcherFactory.java
@@ -21,7 +21,21 @@ import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
- * TODO
+ * Provides string search capability for the {@link ObjectSearcher} class.
+ *
+ * <p>
+ *     The class provides searching based on the following patterns:
+ * </p>
+ * <ul>
+ *     <li><js>"property=foo"</js> - Simple full word match
+ *     <li><js>"property=fo*"</js>, <js>"property=?ar"</js> - Meta-character 
matching
+ *     <li><js>"property=foo bar"</js>(implicit), <js>"property=^foo 
^bar"</js>(explicit) - Multiple OR'ed patterns
+ *     <li><js>"property=+fo* +*ar"</js> - Multiple AND'ed patterns
+ *     <li><js>"property=fo* -bar"</js> - Negative patterns
+ *     <li><js>"property='foo bar'"</js> - Patterns with whitespace
+ *     <li><js>"property=foo\\'bar"</js> - Patterns with single-quotes
+ *     <li><js>"property=/foo\\s+bar"</js> - Regular expression match
+ * </ul>
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
index 596f18a96..5d32e9d13 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/TimeMatcherFactory.java
@@ -23,7 +23,23 @@ import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
- * TODO
+ * Provides date search capability for the {@link ObjectSearcher} class.
+ *
+ * <p>
+ *     The class provides searching based on the following patterns:
+ * </p>
+ * <ul>
+ *     <li><js>"property=2011"</js> - A single year
+ *     <li><js>"property=2011 2013 2015"</js> - Multiple years
+ *     <li><js>"property=2011-01"</js> - A single month
+ *     <li><js>"property=2011-01-01"</js> - A single day
+ *     <li><js>"property=2011-01-01T12"</js> - A single hour
+ *     <li><js>"property=2011-01-01T12:30"</js> - A single minute
+ *     <li><js>"property=2011-01-01T12:30:45"</js> - A single second
+ *     
<li><js>"property=&gt;2011"</js>,<js>"property=&gt;=2011"</js>,<js>"property=&lt;2011"</js>,<js>"property=&lt;=2011"</js>
 - Open-ended ranges
+ *     
<li><js>"property=&gt;2011"</js>,<js>"property=&gt;=2011"</js>,<js>"property=&lt;2011"</js>,<js>"property=&lt;=2011"</js>
 - Open-ended ranges
+ *     <li><js>"property=2011 - 2013-06-30"</js> - Closed ranges
+ * </ul>
  *
  * <ul class='seealso'>
  *     <li class='link'>{@doc jm.ObjectTools}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ViewArgs.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ViewArgs.java
index 40b6510e7..d41e3d704 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ViewArgs.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ViewArgs.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.objecttools;
 
 import static org.apache.juneau.internal.CollectionUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
 
 import java.util.*;
 
@@ -26,6 +27,34 @@ import java.util.*;
  */
 public class ViewArgs {
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Static
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Static creator.
+        *
+        * @param args Comma-delimited list of view arguments.
+        * @return A new {@link ViewArgs} object.
+        */
+       public static ViewArgs create(String args) {
+               return new ViewArgs(args);
+       }
+
+       /**
+        * Static creator.
+        *
+        * @param args List of view arguments.
+        * @return A new {@link ViewArgs} object.
+        */
+       public static ViewArgs create(List<String> args) {
+               return new ViewArgs(args);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-----------------------------------------------------------------------------------------------------------------
+
        private final List<String> view;
 
        /**
@@ -35,8 +64,8 @@ public class ViewArgs {
         *      View arguments.
         *      <br>Values are column names.
         */
-       public ViewArgs(String...viewArgs) {
-               this(alist(viewArgs));
+       public ViewArgs(String viewArgs) {
+               this(alist(split(viewArgs)));
        }
 
        /**
diff --git a/juneau-doc/docs/Topics/02.juneau-marshall/25.jm.ObjectTools.html 
b/juneau-doc/docs/Topics/02.juneau-marshall/25.jm.ObjectTools.html
index 8aa6e30e7..6e08ae193 100644
--- a/juneau-doc/docs/Topics/02.juneau-marshall/25.jm.ObjectTools.html
+++ b/juneau-doc/docs/Topics/02.juneau-marshall/25.jm.ObjectTools.html
@@ -21,12 +21,546 @@
                and manipulating POJOs.  It consists of the following classes:
        </p>
        <ul class='javatree'>
-               <li class='jc'>{@link oaj.encoders.EncoderSet}
-               <li class='jcc'>{@link oaj.encoders.Encoder}
-               <ul>
-                       <li class='jc'>{@link oaj.encoders.IdentityEncoder}
-                       <li class='jc'>{@link oaj.encoders.GzipEncoder}
-               </ul>
+               <li class='jc'>{@link oaj.objecttools.ObjectRest}
+               <li class='jc'>{@link oaj.objecttools.ObjectSearcher}
+               <li class='jc'>{@link oaj.objecttools.ObjectSorter}
+               <li class='jc'>{@link oaj.objecttools.ObjectViewer}
+               <li class='jc'>{@link oaj.objecttools.ObjectPaginator}
+               <li class='jc'>{@link oaj.objecttools.ObjectIntrospector}
+               <li class='jc'>{@link oaj.objecttools.ObjectMerger}
        </ul>
        
+       <h5 class='topic'>ObjectRest</h5>
+       <p>
+               The {@link oaj.objecttools.ObjectRest} class provides the 
ability to perform standard REST operations (GET, PUT, POST, DELETE) against 
nodes in a POJO model.
+               Nodes in the POJO model are addressed using URLs.
+       </p>
+       <p>
+               A POJO model is defined as a tree model where nodes consist of 
consisting of the following:
+       </p>
+       <ul class='spaced-list'>
+               <li>
+                       {@link Map Maps} and Java beans representing JSON 
objects.
+               <li>
+                       {@link Collection Collections} and arrays representing 
JSON arrays.
+               <li>
+                       Java beans.
+       </ul>
+       <p>
+               Leaves of the tree can be any type of object.
+       </p>
+       <p>
+               Use {@link #get(String) get()} to retrieve an element from a 
JSON tree.
+               <br>Use {@link #put(String,Object) put()} to create (or 
overwrite) an element in a JSON tree.
+               <br>Use {@link #post(String,Object) post()} to add an element 
to a list in a JSON tree.
+               <br>Use {@link #delete(String) delete()} to remove an element 
from a JSON tree.
+       </p>
+       <p>
+               Leading slashes in URLs are ignored.
+               So <js>"/xxx/yyy/zzz"</js> and <js>"xxx/yyy/zzz"</js> are 
considered identical.
+       </p>
+       <h5 class='section'>Example:</h5>
+       <p class='bjava'>
+       |       <jc>// Construct an unstructured POJO model</jc>
+       |       JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>""</js>
+       |               + <js>"{"</js>
+       |               + <js>" name:'John Smith', "</js>
+       |               + <js>" address:{ "</js>
+       |               + <js>"         streetAddress:'21 2nd Street', "</js>
+       |               + <js>"         city:'New York', "</js>
+       |               + <js>"         state:'NY', "</js>
+       |               + <js>"         postalCode:10021 "</js>
+       |               + <js>" }, "</js>
+       |               + <js>" phoneNumbers:[ "</js>
+       |               + <js>"         '212 555-1111', "</js>
+       |               + <js>"         '212 555-2222' "</js>
+       |               + <js>" ], "</js>
+       |               + <js>" additionalInfo:null, "</js>
+       |               + <js>" remote:false, "</js>
+       |               + <js>" height:62.4, "</js>
+       |               + <js>" 'fico score':' &gt; 640' "</js>
+       |               + <js>"} "</js>
+       |       );
+       |
+       |       <jc>// Wrap Map inside an ObjectRest object</jc>
+       |       ObjectRest <jv>johnSmith</jv> = 
ObjectRest.<jsf>create</jsf>(<jv>map</jv>);
+       |
+       |       <jc>// Get a simple value at the top level</jc>
+       |       <jc>// "John Smith"</jc>
+       |       String <jv>name</jv> = 
<jv>johnSmith</jv>.getString(<js>"name"</js>);
+       |
+       |       <jc>// Change a simple value at the top level</jc>
+       |       <jv>johnSmith</jv>.put(<js>"name"</js>, <js>"The late John 
Smith"</js>);
+       |
+       |       <jc>// Get a simple value at a deep level</jc>
+       |       <jc>// "21 2nd Street"</jc>
+       |       String <jv>streetAddress</jv> = 
<jv>johnSmith</jv>.getString(<js>"address/streetAddress"</js>);
+       |
+       |       <jc>// Set a simple value at a deep level</jc>
+       |       <jv>johnSmith</jv>.put(<js>"address/streetAddress"</js>, 
<js>"101 Cemetery Way"</js>);
+       |
+       |       <jc>// Get entries in a list</jc>
+       |       <jc>// "212 555-1111"</jc>
+       |       String <jv>firstPhoneNumber</jv> = 
<jv>johnSmith</jv>.getString(<js>"phoneNumbers/0"</js>);
+       |
+       |       <jc>// Add entries to a list</jc>
+       |       <jv>johnSmith</jv>.post(<js>"phoneNumbers"</js>, <js>"212 
555-3333"</js>);
+       |
+       |       <jc>// Delete entries from a model</jc>
+       |       <jv>johnSmith</jv>.delete(<js>"fico score"</js>);
+       |
+       |       <jc>// Add entirely new structures to the tree</jc>
+       |       JsonMap <jv>medicalInfo</jv> = 
JsonMap.<jsm>ofJson</jsm>(<js>""</js>
+       |               + <js>"{"</js>
+       |               + <js>" currentStatus: 'deceased',"</js>
+       |               + <js>" health: 'non-existent',"</js>
+       |               + <js>" creditWorthiness: 'not good'"</js>
+       |               + <js>"}"</js>
+       |       );
+       |       <jv>johnSmith</jv>.put(<js>"additionalInfo/medicalInfo"</js>, 
<jv>medicalInfo</jv>);
+       </p>
+
+       <p>
+               In the special case of collections/arrays of maps/beans, a 
special XPath-like selector notation can be used in lieu
+               of index numbers on GET requests to return a map/bean with a 
specified attribute value.
+               <br>The syntax is {@code @attr=val}, where attr is the 
attribute name on the child map, and val is the matching value.
+       </p>
+       
+       <h5 class='section'>Example:</h5>
+       <p class='bjava'>
+       |       <jc>// Get map/bean with name attribute value of 'foo' from a 
list of items</jc>
+       |       Map <jv>map</jv> = 
<jv>objectRest</jv>.getMap(<js>"/items/@name=foo"</js>);
+       </p>
+       
+       <h5 class='topic'>ObjectSearcher</h5>
+       <p>
+               The {@link ObjectSearcher} class is designed to provide 
searches across arrays and collections of beans and maps.
+               It allows you to quickly filter beans and maps using simple yet 
sophisticated search arguments.
+       </p>
+       
+       <h5 class='section'>Example:</h5>
+       <p class='bjava'> 
+       |       MyBean[] <jv>arrayOfBeans</jv> = ...;
+       |       ObjectSearcher <jv>searcher</jv> = 
ObjectSearcher.<jsm>create</jsm>();
+       |       BeanSession <jv>beanSession</jv> = 
BeanContext.<jsf>DEFAULT</jsf>.createSession();
+       |       
+       |       <jc>// Find beans whose 'foo' property is 'bar'.</jc>
+       |       SearchArgs <jv>args</jv> = SearchArgs.create("foo=X,bar=Y");  
+       |
+       |       <jc>// Returns a list of beans whose 'foo' property is 'X' and 
'bar' property is 'Y'.</jc>              
+       |       Object <jv>result</jv> = searcher.run(<jv>beanSession</jv>, 
<jv>arrayOfBeans</jv>, <jv>args</jv>);  
+       </p>
+       <p>
+               The default searcher is configured with the following matcher 
factories that provides the capabilities of matching
+               against various data types:
+       </p>
+       <ul class='javatreec'>
+               <li class='jc'>{@link oaj.objecttools.StringMatcherFactory}
+               <li class='jc'>{@link oaj.objecttools.NumberMatcherFactory}
+               <li class='jc'>{@link oaj.objecttools.TimeMatcherFactory}
+       </ul>
+       <p>
+               The {@link oaj.objectools.StringMatcherFactory} class provides 
searching based on the following patterns:
+       </p>
+       <ul>
+               <li><js>"property=foo"</js> - Simple full word match
+               <li><js>"property=fo*"</js>, <js>"property=?ar"</js> - 
Meta-character matching
+               <li><js>"property=foo bar"</js>(implicit), <js>"property=^foo 
^bar"</js>(explicit) - Multiple OR'ed patterns
+               <li><js>"property=+fo* +*ar"</js> - Multiple AND'ed patterns
+               <li><js>"property=fo* -bar"</js> - Negative patterns
+               <li><js>"property='foo bar'"</js> - Patterns with whitespace
+               <li><js>"property=foo\\'bar"</js> - Patterns with single-quotes
+               <li><js>"property=/foo\\s+bar"</js> - Regular expression match
+       </ul>
+       <p>
+               The {@link oaj.objectools.NumberMatcherFactory} class provides 
searching based on the following patterns:
+       </p>
+       <ul>
+               <li><js>"property=1"</js> - A single number
+               <li><js>"property=1 2"</js> - Multiple OR'ed numbers
+               <li><js>"property=-1 -2"</js> - Multiple OR'ed negative numbers
+               <li><js>"property=1-2"</js>,<js>"property=-2--1"</js>  - A 
range of numbers (whitespace ignored)
+               <li><js>"property=1-2 4-5"</js> - Multiple OR'ed ranges
+               
<li><js>"property=&lt;1"</js>,<js>"property=&lt;=1"</js>,<js>"property=&gt;1"</js>,<js>"property=&gt;=1"</js>
 - Open-ended ranges
+               <li><js>"property=!1"</js>,<js>"property=!1-2"</js> - Negation
+       </ul>
+       <p>
+               The {@link oaj.objectools.TimeMatcherFactory} class provides 
searching based on the following patterns:
+       </p>
+       <ul>
+               <li><js>"property=2011"</js> - A single year
+               <li><js>"property=2011 2013 2015"</js> - Multiple years
+               <li><js>"property=2011-01"</js> - A single month
+               <li><js>"property=2011-01-01"</js> - A single day
+               <li><js>"property=2011-01-01T12"</js> - A single hour
+               <li><js>"property=2011-01-01T12:30"</js> - A single minute
+               <li><js>"property=2011-01-01T12:30:45"</js> - A single second
+               
<li><js>"property=&gt;2011"</js>,<js>"property=&gt;=2011"</js>,<js>"property=&lt;2011"</js>,<js>"property=&lt;=2011"</js>
 - Open-ended ranges
+               
<li><js>"property=&gt;2011"</js>,<js>"property=&gt;=2011"</js>,<js>"property=&lt;2011"</js>,<js>"property=&lt;=2011"</js>
 - Open-ended ranges
+               <li><js>"property=2011 - 2013-06-30"</js> - Closed ranges
+       </ul>
+
+       <h5 class='topic'>ObjectSorter</h5>
+       <p>
+               The {@link ObjectSorter} class is designed to sort arrays and 
collections of beans and maps.
+       </p>
+       <h5 class='section'>Example:</h5>
+       <p class='bjava'> 
+       |       MyBean[] <jv>arrayOfBeans</jv> = ...;
+       |       ObjectSorter <jv>sorter</jv> = ObjectSorter.<jsm>create</jsm>();
+       |       BeanSession <jv>beanSession</jv> = 
BeanContext.<jsf>DEFAULT</jsf>.createSession();
+       |       
+       |       <jc>// Sort beans by foo ascending then bar descending.</jc>
+       |       SortArgs <jv>args</jv> = SortArgs.create("foo,bar-");  
+       |
+       |       <jc>// Returns a list of beans sorted accordingly.</jc>         
+       |       Object <jv>result</jv> = sorter.run(<jv>beanSession</jv>, 
<jv>arrayOfBeans</jv>, <jv>args</jv>);  
+       </p>
+       
+       <h5 class='topic'>ObjectViewer</h5>
+       <p>
+               The {@link ObjectViewer} class is designed to extract 
properties from collections of maps and beans.
+       </p>
+       <h5 class='section'>Example:</h5>
+       <p class='bjava'> 
+       |       MyBean[] <jv>arrayOfBeans</jv> = ...;
+       |       ObjectSorter <jv>sorter</jv> = ObjectSorter.<jsm>create</jsm>();
+       |       BeanSession <jv>beanSession</jv> = 
BeanContext.<jsf>DEFAULT</jsf>.createSession();
+       |       
+       |       <jc>// Extract properties 'foo' and 'bar' from the beans.</jc>
+       |       ViewArgs <jv>args</jv> = ViewArgs.create("foo,bar");  
+       |
+       |       <jc>// Returns list of maps with just .</jc>            
+       |       Object <jv>result</jv> = sorter.run(<jv>beanSession</jv>, 
<jv>arrayOfBeans</jv>, <jv>sortArgs</jv>);  
+       </p>
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Null input
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void nullInput() {
+               assertNull(p.run(bs, null, null));
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Simple bean
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class A {
+               public String f1,f2;
+
+               public static A create(String f1, String f2) {
+                       A a = new A();
+                       a.f1 = f1;
+                       a.f2 = f2;
+                       return a;
+               }
+       }
+
+       @Test
+       public void simpleBean() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       @Test
+       public void simpleBean_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2","f1");
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+       }
+
+       @Test
+       public void simpleBean_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1","f1");
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       @Test
+       public void simpleBean_nonExistentColumns() {
+               ViewArgs sa = new ViewArgs("fx");
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+       }
+
+       @Test
+       public void simpleBean_nullColumn() {
+               ViewArgs sa = new ViewArgs("f1",null);
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       @Test
+       public void simpleBean_emptyArgs() {
+               ViewArgs sa = new ViewArgs();
+               Object in = A.create("x1","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Simple BeanMap
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void simpleBeanMap() {
+               ViewArgs sa = new ViewArgs("f1");
+               Object in = bs.toBeanMap(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Simple map
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void simpleMap() {
+               ViewArgs sa = new ViewArgs("f1");
+               Object in = map("f1","x1","f2","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       @Test
+       public void simpleMap_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2","f1");
+               Object in = map("f1","x1","f2","x2");
+               assertObject(p.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+       }
+
+       @Test
+       public void simpleMap_nonExistentColumns() {
+               ViewArgs sa = new ViewArgs("fx");
+               Object in = map("f1","x1","f2","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+       }
+
+       @Test
+       public void simpleMap_nullColumn() {
+               ViewArgs sa = new ViewArgs("f1",null);
+               Object in = map("f1","x1","f2","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+       }
+
+       @Test
+       public void simpleMap_emptyView() {
+               ViewArgs sa = new ViewArgs();
+               Object in = map("f1","x1","f2","x2");
+               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Bean array
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void beanArray() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanArray_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2","f1");
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+       }
+
+       @Test
+       public void beanArray_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1","f1");
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanArray_nonExistentColumns() {
+               ViewArgs sa = new ViewArgs("fx");
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanArray_nullColumn() {
+               ViewArgs sa = new ViewArgs("f1",null);
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanArray_emptyArgs() {
+               ViewArgs sa = new ViewArgs();
+               Object in = new A[]{A.create("x1","x2")};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanArray_withNull() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = new A[]{A.create("x1","x2"),null};
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Bean list
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void beanList() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanList_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2","f1");
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+       }
+
+       @Test
+       public void beanList_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1","f1");
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanList_nonExistentColumns() {
+               ViewArgs sa = new ViewArgs("fx");
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanList_nullColumn() {
+               ViewArgs sa = new ViewArgs("f1",null);
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanList_emptyArgs() {
+               ViewArgs sa = new ViewArgs();
+               Object in = list(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanList_withNull() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = list(A.create("x1","x2"),null);
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Bean set
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void beanSet() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanSet_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2","f1");
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+       }
+
+       @Test
+       public void beanSet_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1","f1");
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanSet_nonExistentColumns() {
+               ViewArgs sa = new ViewArgs("fx");
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanSet_nullColumn() {
+               ViewArgs sa = new ViewArgs("f1",null);
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       @Test
+       public void beanSet_emptyArgs() {
+               ViewArgs sa = new ViewArgs();
+               Object in = set(A.create("x1","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+       }
+
+       @Test
+       public void beanSet_withNull() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = set(A.create("x1","x2"),null);
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Other object
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void otherObject() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = "foobar";
+               assertObject(p.run(bs, in, sa)).asJson().is("'foobar'");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Map list
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void mapList() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = list(map("f1","x1","f2","x2"));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // BeanMap list
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void beanMapList() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = list(bs.toBeanMap(A.create("x1","x2")));
+               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Other object list
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void otherObjectList() {
+               ViewArgs sa = new ViewArgs("f1");;
+               Object in = list("foobar");
+               assertObject(p.run(bs, in, sa)).asJson().is("['foobar']");
+       }
+       
+       
+        * Designed to provide paging on POJOs consisting of arrays and 
collections.
+ *
+ * <p>
+ * Allows you to quickly return subsets of arrays and collections based on 
position/limit arguments.
+       
+       
+       <h5 class='topic'>ObjectPaginator</h5>
+       <h5 class='topic'>ObjectIntrospector</h5>
+       <h5 class='topic'>ObjectMerger</h5>
 </div>
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospectorTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospector_Test.java
similarity index 95%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospectorTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospector_Test.java
index 87c00c2b0..bc28d7133 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospectorTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectIntrospector_Test.java
@@ -19,13 +19,13 @@ import static org.junit.runners.MethodSorters.*;
 import org.junit.*;
 
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectIntrospectorTest {
+public class ObjectIntrospector_Test {
 
        
//====================================================================================================
        // testBasic
        
//====================================================================================================
        @Test
-       public void testBasic() throws Exception {
+       public void a01_Basic() throws Exception {
                String in = null;
                Object r;
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMergerTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMerger_Test.java
similarity index 95%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMergerTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMerger_Test.java
index 3a0a62a80..f41679ebc 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMergerTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectMerger_Test.java
@@ -21,13 +21,13 @@ import org.junit.*;
  * Test the PojoMerge class.
  */
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectMergerTest {
+public class ObjectMerger_Test {
 
        
//====================================================================================================
        // Basic tests
        
//====================================================================================================
        @Test
-       public void basicTests() throws Exception {
+       public void a01_basic() throws Exception {
                IA a1, a2, am;
 
                a1 = new A("1"); a2 = new A("2");
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectPaginator_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectPaginator_Test.java
new file mode 100644
index 000000000..7198b51c7
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectPaginator_Test.java
@@ -0,0 +1,105 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you 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 org.apache.juneau.objecttools;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.*;
+
+/**
+ * Tests the PojoPaginator class.
+ */
+@FixMethodOrder(NAME_ASCENDING)
+public class ObjectPaginator_Test {
+
+       ObjectPaginator op = ObjectPaginator.create();
+       BeanSession bs = BeanContext.DEFAULT_SESSION;
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Null input
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void a01_nullInput() {
+               assertNull(op.run(bs, null, null));
+       }
+
+       @Test
+       public void a02_nonCollectionInput() {
+               assertObject(op.run(bs, "foo", PageArgs.create(1, 
3))).is("foo");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Arrays
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void b01_arrays_basic() {
+               Object in = new int[]{1,2,3};
+               assertList(op.run(in, 0, 3)).isHas(1,2,3);
+               assertList(op.run(in, 1, 3)).isHas(2,3);
+               assertList(op.run(in, 1, 1)).isHas(2);
+               assertList(op.run(in, 4, 1)).isHas();
+               assertList(op.run(in, 0, 0)).isHas();
+
+               in = new String[]{"1","2","3"};
+               assertList(op.run(in, 1, 1)).isHas("2");
+
+               in = new boolean[]{false,true,false};
+               assertList(op.run(in, 1, 1)).isHas(true);
+
+               in = new byte[]{1,2,3};
+               assertList(op.run(in, 1, 1)).isHas((byte)2);
+
+               in = new char[]{'1','2','3'};
+               assertList(op.run(in, 1, 1)).isHas('2');
+
+               in = new double[]{1,2,3};
+               assertList(op.run(in, 1, 1)).isHas((double)2);
+
+               in = new float[]{1,2,3};
+               assertList(op.run(in, 1, 1)).isHas((float)2);
+
+               in = new long[]{1,2,3};
+               assertList(op.run(in, 1, 1)).isHas((long)2);
+
+               in = new short[]{1,2,3};
+               assertList(op.run(in, 1, 1)).isHas((short)2);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Collections
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void c01_collections_basic() {
+               Object in = Arrays.asList(1,2,3);
+               assertList(op.run(in, 0, 3)).isHas(1,2,3);
+               assertList(op.run(in, 1, 3)).isHas(2,3);
+               assertList(op.run(in, 1, 1)).isHas(2);
+               assertList(op.run(in, 4, 1)).isHas();
+               assertList(op.run(in, 0, 0)).isHas();
+
+               in = new LinkedHashSet<>(Arrays.asList(1,2,3));
+               assertList(op.run(in, 0, 3)).isHas(1,2,3);
+               assertList(op.run(in, 1, 3)).isHas(2,3);
+               assertList(op.run(in, 1, 1)).isHas(2);
+               assertList(op.run(in, 4, 1)).isHas();
+               assertList(op.run(in, 0, 0)).isHas();
+}
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRestTest.java 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
similarity index 96%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRestTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
index 4f9af4bb5..17148c40a 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRestTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
@@ -27,13 +27,13 @@ import org.junit.*;
 
 @SuppressWarnings({"unchecked","rawtypes","serial"})
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectRestTest {
+public class ObjectRest_Test {
 
        
//====================================================================================================
        // testBasic
        
//====================================================================================================
        @Test
-       public void testBasic() {
+       public void a01_basic() {
 
                // TODO: Need to write some exhaustive tests here. Will open 
work item
                // to do that later.
@@ -63,7 +63,7 @@ public class ObjectRestTest {
        // testBeans
        
//====================================================================================================
        @Test
-       public void testBeans() throws Exception {
+       public void b01_beans() throws Exception {
                ObjectRest model;
 
                // Java beans.
@@ -195,7 +195,7 @@ public class ObjectRestTest {
        // testAddressBook
        
//====================================================================================================
        @Test
-       public void testAddressBook() {
+       public void b02_addressBook() {
                ObjectRest model;
 
                model = ObjectRest.create(new AddressBook());
@@ -271,7 +271,7 @@ public class ObjectRestTest {
        // PojoRest(Object,ReaderParser)
        
//====================================================================================================
        @Test
-       public void testConstructors() throws Exception {
+       public void c01_constructors() throws Exception {
                ObjectRest model = ObjectRest.create(new AddressBook(), 
JsonParser.DEFAULT);
 
                // Try adding a person to the address book.
@@ -289,7 +289,7 @@ public class ObjectRestTest {
        // setRootLocked()
        
//====================================================================================================
        @Test
-       public void testRootLocked() throws Exception {
+       public void d01_rootLocked() throws Exception {
                ObjectRest model = ObjectRest.create(new 
AddressBook()).setRootLocked();
                assertThrown(()->model.put("", new 
AddressBook())).asMessage().is("Cannot overwrite root object");
                assertThrown(()->model.put(null, new 
AddressBook())).asMessage().is("Cannot overwrite root object");
@@ -300,7 +300,7 @@ public class ObjectRestTest {
        // getRootObject()
        
//====================================================================================================
        @Test
-       public void testGetRootObject() throws Exception {
+       public void e01_getRootObject() throws Exception {
                ObjectRest model = ObjectRest.create(new AddressBook());
                assertTrue(model.getRootObject() instanceof AddressBook);
                model.put("", "foobar");
@@ -330,7 +330,7 @@ public class ObjectRestTest {
        // getJsonList(String url, JsonList defVal)
        
//====================================================================================================
        @Test
-       public void testGetMethods() throws Exception {
+       public void f01_getMethods() throws Exception {
                ObjectRest model = ObjectRest.create(new A());
                JsonList l = JsonList.ofJson("[{a:'b'}]");
                JsonMap m = JsonMap.ofJson("{a:'b'}");
@@ -807,7 +807,7 @@ public class ObjectRestTest {
        // invokeMethod(String url, String method, String args)
        
//====================================================================================================
        @Test
-       public void testInvokeMethod() throws Exception {
+       public void f02_invokeMethod() throws Exception {
 
                ObjectRest model = ObjectRest.create(new AddressBook().init());
                assertEquals("Person(name=Bill Clinton,age=65)", 
model.invokeMethod("0", "toString", ""));
@@ -822,7 +822,7 @@ public class ObjectRestTest {
        // getPublicMethods(String url)
        
//====================================================================================================
        @Test
-       public void testGetPublicMethods() throws Exception {
+       public void f03_getPublicMethods() throws Exception {
                ObjectRest model = ObjectRest.create(new AddressBook().init());
                
assertTrue(SimpleJsonSerializer.DEFAULT.toString(model.getPublicMethods("0")).contains("'toString'"));
                
assertTrue(SimpleJsonSerializer.DEFAULT.toString(model.getPublicMethods("0/addresses/0/state")).contains("'toString'"));
@@ -833,7 +833,7 @@ public class ObjectRestTest {
        // getClassMeta(String url)
        
//====================================================================================================
        @Test
-       public void testGetClassMeta() throws Exception {
+       public void f04_getClassMeta() throws Exception {
                ObjectRest model = ObjectRest.create(new AddressBook().init());
                assertEquals("Person", 
model.getClassMeta("0").getInnerClass().getSimpleName());
                assertEquals("String", 
model.getClassMeta("0/addresses/0/state").getInnerClass().getSimpleName());
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcherTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
similarity index 80%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcherTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
index 0b3c50d7d..d1e0676f4 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcherTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSearcher_Test.java
@@ -29,10 +29,10 @@ import org.junit.*;
  * Tests the PojoSearcher class.
  */
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectSearcherTest {
+public class ObjectSearcher_Test {
 
        private static BeanSession bs = BeanContext.DEFAULT_SESSION;
-       private static ObjectSearcher ps = ObjectSearcher.DEFAULT;
+       private static ObjectSearcher os = ObjectSearcher.DEFAULT;
        private static WriterSerializer ws = 
JsonSerializer.create().ssq().swaps(TemporalCalendarSwap.IsoLocalDateTime.class).build();
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -42,20 +42,20 @@ public class ObjectSearcherTest {
        static SearchArgs[] create(String...search) {
                SearchArgs[] sa = new SearchArgs[search.length];
                for (int i = 0; i < search.length; i++)
-                       sa[i] = new SearchArgs(search[i]);
+                       sa[i] = SearchArgs.create(search[i]);
                return sa;
        }
 
        static SearchArgs create(String search) {
-               return new SearchArgs(search);
+               return SearchArgs.create(search);
        }
 
        static Object run(Object in, String search) {
-               return ps.run(bs, in, create(search));
+               return os.run(bs, in, create(search));
        }
 
        static Object run(Object in, SearchArgs sa) {
-               return ps.run(bs, in, sa);
+               return os.run(bs, in, sa);
        }
 
        static String[] a(String...s) {
@@ -81,91 +81,94 @@ public class ObjectSearcherTest {
        public static A[] A_ARRAY = new A[]{A.create("foo"), A.create("bar"), 
A.create("baz"), A.create("q ux"), A.create("qu'ux"), null, A.create(null)};
 
        @Test
-       public void stringSearch_singleWord() throws Exception {
+       public void a01_stringSearch_singleWord() throws Exception {
                assertObject(run(A_LIST, "f=foo")).asJson().is("[{f:'foo'}]");
                assertObject(run(A_SET, "f=foo")).asJson().is("[{f:'foo'}]");
                assertObject(run(A_ARRAY, "f=foo")).asJson().is("[{f:'foo'}]");
+               assertObject(os.run(A_LIST, 
"f=foo")).asJson().is("[{f:'foo'}]");
+               assertObject(os.run(A_SET, "f=foo")).asJson().is("[{f:'foo'}]");
+               assertObject(os.run(A_ARRAY, 
"f=foo")).asJson().is("[{f:'foo'}]");
        }
 
        @Test
-       public void stringSearch_pattern1() throws Exception {
+       public void a02_stringSearch_pattern1() throws Exception {
                assertObject(run(A_LIST, "f=fo*")).asJson().is("[{f:'foo'}]");
                assertObject(run(A_SET, "f=fo*")).asJson().is("[{f:'foo'}]");
                assertObject(run(A_ARRAY, "f=fo*")).asJson().is("[{f:'foo'}]");
        }
 
        @Test
-       public void stringSearch_pattern2() throws Exception {
+       public void a03_stringSearch_pattern2() throws Exception {
                assertObject(run(A_LIST, "f=*ar")).asJson().is("[{f:'bar'}]");
                assertObject(run(A_SET, "f=*ar")).asJson().is("[{f:'bar'}]");
                assertObject(run(A_ARRAY, "f=*ar")).asJson().is("[{f:'bar'}]");
        }
 
        @Test
-       public void stringSearch_pattern3() throws Exception {
+       public void a04_stringSearch_pattern3() throws Exception {
                assertObject(run(A_LIST, "f=?ar")).asJson().is("[{f:'bar'}]");
                assertObject(run(A_SET, "f=?ar")).asJson().is("[{f:'bar'}]");
                assertObject(run(A_ARRAY, "f=?ar")).asJson().is("[{f:'bar'}]");
        }
 
        @Test
-       public void stringSearch_multiple() throws Exception {
+       public void a05_stringSearch_multiple() throws Exception {
                assertObject(run(A_LIST, "f=foo bar q 
ux")).asJson().is("[{f:'foo'},{f:'bar'}]");
                assertObject(run(A_SET, "f=foo bar q 
ux")).asJson().is("[{f:'foo'},{f:'bar'}]");
                assertObject(run(A_ARRAY, "f=foo bar q 
ux")).asJson().is("[{f:'foo'},{f:'bar'}]");
        }
 
        @Test
-       public void stringSearch_quoted() throws Exception {
+       public void a06_stringSearch_quoted() throws Exception {
                assertObject(run(A_LIST, "f='q ux'")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_SET, "f='q ux'")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_ARRAY, "f='q ux'")).asJson().is("[{f:'q 
ux'}]");
        }
 
        @Test
-       public void stringSearch_quotedWithPattern() throws Exception {
+       public void a07_stringSearch_quotedWithPattern() throws Exception {
                assertObject(run(A_LIST, "f='q *x'")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_SET, "f='q *x'")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_ARRAY, "f='q *x'")).asJson().is("[{f:'q 
ux'}]");
        }
 
        @Test
-       public void stringSearch_unquotedContainingQuote() throws Exception {
+       public void a08_stringSearch_unquotedContainingQuote() throws Exception 
{
                assertObject(run(A_LIST, 
"f=qu'ux")).asJson().is("[{f:'qu\\'ux'}]");
                assertObject(run(A_SET, 
"f=qu'ux")).asJson().is("[{f:'qu\\'ux'}]");
                assertObject(run(A_ARRAY, 
"f=qu'ux")).asJson().is("[{f:'qu\\'ux'}]");
        }
 
        @Test
-       public void stringSearch_quotedContainingQuote() throws Exception {
+       public void a09_stringSearch_quotedContainingQuote() throws Exception {
                assertObject(run(A_LIST, 
"f='qu\\'ux'")).asJson().is("[{f:'qu\\'ux'}]");
                assertObject(run(A_SET, 
"f='qu\\'ux'")).asJson().is("[{f:'qu\\'ux'}]");
                assertObject(run(A_ARRAY, 
"f='qu\\'ux'")).asJson().is("[{f:'qu\\'ux'}]");
        }
 
        @Test
-       public void stringSearch_regExp() throws Exception {
+       public void a10_stringSearch_regExp() throws Exception {
                assertObject(run(A_LIST, "f=/q\\sux/")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_SET, "f=/q\\sux/")).asJson().is("[{f:'q 
ux'}]");
                assertObject(run(A_ARRAY, "f=/q\\sux/")).asJson().is("[{f:'q 
ux'}]");
        }
 
        @Test
-       public void stringSearch_regExp_noEndSlash() throws Exception {
+       public void a11_stringSearch_regExp_noEndSlash() throws Exception {
                Object in = list(A.create("/foo"), A.create("bar"));
                for (String s : a("f=/foo","f='/foo'"))
                        assertObject(run(in, s)).asJson().is("[{f:'/foo'}]");
        }
 
        @Test
-       public void stringSearch_regExp_onlySlash() throws Exception {
+       public void a12_stringSearch_regExp_onlySlash() throws Exception {
                Object in = list(A.create("/"), A.create("bar"));
                for (String s : a("f=/", "f='/'"))
                        assertObject(run(in, s)).asJson().is("[{f:'/'}]");
        }
 
        @Test
-       public void stringSearch_or_pattern() throws Exception {
+       public void a13_stringSearch_or_pattern() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=f* 
*r")).asJson().is("[{f:'foo'},{f:'bar'}]");
                assertObject(run(in, "f='f* *r'")).asJson().is("[]");
@@ -173,7 +176,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void stringSearch_explicit_or_pattern() throws Exception {
+       public void a14_stringSearch_explicit_or_pattern() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=^f* 
^*r")).asJson().is("[{f:'foo'},{f:'bar'}]");
                assertObject(run(in, "f=^'f* *r'")).asJson().is("[]");
@@ -181,21 +184,21 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void stringSearch_and_pattern() throws Exception {
+       public void a15_stringSearch_and_pattern() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=+b* +*r")).asJson().is("[{f:'bar'}]");
                assertObject(run(in, "f=+'b*' 
+'*r'")).asJson().is("[{f:'bar'}]");
        }
 
        @Test
-       public void stringSearch_not_pattern() throws Exception {
+       public void a16_stringSearch_not_pattern() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=b* -*r")).asJson().is("[{f:'baz'}]");
                assertObject(run(in, "f=+'b*' 
-'*r'")).asJson().is("[{f:'baz'}]");
        }
 
        @Test
-       public void stringSearch_caseSensitive() throws Exception {
+       public void a17_stringSearch_caseSensitive() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=F*")).asJson().is("[]");
                assertObject(run(in, "f=\"F*\"")).asJson().is("[]");
@@ -203,7 +206,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void stringSearch_malformedQuotes() throws Exception {
+       public void a18_stringSearch_malformedQuotes() throws Exception {
                Object in = list(A.create("'foo"), A.create("\"bar"), 
A.create("baz"));
 
                assertThrown(()->run(in, 
"f='*")).asMessage().isContains("Unmatched string quotes");
@@ -216,7 +219,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void stringSearch_regexChars() throws Exception {
+       public void a19_stringSearch_regexChars() throws Exception {
                Object in = list(A.create("+\\[]{}()^$."), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, 
"f=*+*")).asJson().is("[{f:'+\\\\[]{}()^$.'}]");
                assertObject(run(in, 
"f='+\\\\[]{}()^$.'")).asJson().is("[{f:'+\\\\[]{}()^$.'}]");
@@ -224,20 +227,20 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void stringSearch_metaChars() throws Exception {
+       public void a20_stringSearch_metaChars() throws Exception {
                Object in = list(A.create("*?\\'\""), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, 
"f='\\*\\?\\\\\\'\"'")).asJson().is("[{f:'*?\\\\\\'\"'}]");
        }
 
        @Test
-       public void stringSearch_metaChars_escapedQuotes() throws Exception {
+       public void a21_stringSearch_metaChars_escapedQuotes() throws Exception 
{
                Object in = list(A.create("'"), A.create("\""), 
A.create("baz"));
                assertObject(run(in, "f=\\'")).asJson().is("[{f:'\\''}]");
                assertObject(run(in, "f=\\\"")).asJson().is("[{f:'\"'}]");
        }
 
        @Test
-       public void stringSearch_metaChars_falseEscape() throws Exception {
+       public void a22_stringSearch_metaChars_falseEscape() throws Exception {
                Object in = list(A.create("foo"), A.create("bar"), 
A.create("baz"));
                assertObject(run(in, "f=\\f\\o\\o")).asJson().is("[{f:'foo'}]");
        }
@@ -259,142 +262,142 @@ public class ObjectSearcherTest {
        C[] INT_BEAN_ARRAY = new C[]{C.create(-2), C.create(-1), C.create(0), 
C.create(1), C.create(2), C.create(3)};
 
        @Test
-       public void intSearch_oneNumber() throws Exception {
+       public void b01_intSearch_oneNumber() throws Exception {
                for (String s : a("f=1", "f = 1"))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:1}]");
        }
 
        @Test
-       public void intSearch_twoNumbers() throws Exception {
+       public void b02_intSearch_twoNumbers() throws Exception {
                for (String s : a("f=1 2", "f = 1  2 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:1},{f:2}]");
        }
 
        @Test
-       public void intSearch_oneNegativeNumber() throws Exception {
+       public void b03_intSearch_oneNegativeNumber() throws Exception {
                for (String s : a("f=-1", "f = -1 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-1}]");
        }
 
        @Test
-       public void intSearch_twoNegativeNumbers() throws Exception {
+       public void b04_intSearch_twoNegativeNumbers() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f=-1 
-2")).asJson().is("[{f:-2},{f:-1}]");
        }
 
        @Test
-       public void intSearch_simpleRange() throws Exception {
+       public void b05_intSearch_simpleRange() throws Exception {
                for (String s : a("f=1-2", "f = 1 - 2 ", "f = 1- 2 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:1},{f:2}]");
        }
 
        @Test
-       public void intSearch_simpleRange_invalid() throws Exception {
+       public void b06_intSearch_simpleRange_invalid() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f=2-1")).asJson().is("[]");
        }
 
        @Test
-       public void intSearch_twoNumbersThatLookLikeRange() throws Exception {
+       public void b07_intSearch_twoNumbersThatLookLikeRange() throws 
Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = 1 -2 
")).asJson().is("[{f:-2},{f:1}]");
        }
 
        @Test
-       public void intSearch_rangeWithNegativeNumbers() throws Exception {
+       public void b08_intSearch_rangeWithNegativeNumbers() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = -2--1 
")).asJson().is("[{f:-2},{f:-1}]");
        }
 
        @Test
-       public void intSearch_rangeWithNegativeNumbers_invalidRange() throws 
Exception {
+       public void b09_intSearch_rangeWithNegativeNumbers_invalidRange() 
throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = -1--2 
")).asJson().is("[]");
        }
 
        @Test
-       public void intSearch_multipleRanges() throws Exception {
+       public void b10_intSearch_multipleRanges() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = 0-1 
3-4")).asJson().is("[{f:0},{f:1},{f:3}]");
        }
 
        @Test
-       public void intSearch_overlappingRanges() throws Exception {
+       public void b11_intSearch_overlappingRanges() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = 0-0 
2-2")).asJson().is("[{f:0},{f:2}]");
        }
 
        @Test
-       public void intSearch_LT() throws Exception {
+       public void b12_intSearch_LT() throws Exception {
                for (String s : a("f = <0", "f<0", "f = < 0 ", "f < 0 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2},{f:-1}]");
        }
 
        @Test
-       public void intSearch_LT_negativeNumber() throws Exception {
+       public void b13_intSearch_LT_negativeNumber() throws Exception {
                for (String s : a("f = <-1", "f<-1", "f = < -1 ", "f < -1 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2}]");
        }
 
        @Test
-       public void intSearch_GT() throws Exception {
+       public void b14_intSearch_GT() throws Exception {
                for (String s : a("f = >1", "f>1", "f = > 1 ", "f > 1 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_GT_negativeNumber() throws Exception {
+       public void b15_intSearch_GT_negativeNumber() throws Exception {
                for (String s : a("f = >-1", "f>-1", "f = > -1 ", "f > -1 ", "f 
=  >  -1  ", "f >  -1  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:0},{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_LTE() throws Exception {
+       public void b16_intSearch_LTE() throws Exception {
                for (String s : a("f = <=0", "f<=0", "f = <= 0 ", "f <= 0 ", "f 
=  <=  0  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2},{f:-1},{f:0}]");
        }
 
        @Test
-       public void intSearch_LTE_negativeNumber() throws Exception {
+       public void b17_intSearch_LTE_negativeNumber() throws Exception {
                for (String s : a("f = <=-1", "f <=-1", "f = <= -1 ", "f =  <=  
-1  ", "f <=  -1  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2},{f:-1}]");
        }
 
        @Test
-       public void intSearch_GTE() throws Exception {
+       public void b18_intSearch_GTE() throws Exception {
                for (String s : a("f = >=1", "f >=1", "f = >= 1 ", "f >= 1 ", 
"f =  >=  1  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_GTE_negativeNumber() throws Exception {
+       public void b19_intSearch_GTE_negativeNumber() throws Exception {
                for (String s : a("f = >=-1", "f >=-1", "f = >= -1 ", "f >= -1 
", "f =  >=  -1  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-1},{f:0},{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_not_singleNumber() throws Exception {
+       public void b20_intSearch_not_singleNumber() throws Exception {
                for (String s : a("f = !1", "f = ! 1 ", "f =  !  1  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2},{f:-1},{f:0},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_not_range() throws Exception {
+       public void b21_intSearch_not_range() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = 
!1-2")).asJson().is("[{f:-2},{f:-1},{f:0},{f:3}]");
        }
 
        @Test
-       public void intSearch_not_range_negativeNumbers() throws Exception {
+       public void b22_intSearch_not_range_negativeNumbers() throws Exception {
                for (String s : a("f = !-2--1", "f = ! -2 - -1", "f =  !  -2  - 
 -1 "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:0},{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_not_looksLikeRange() throws Exception {
+       public void b23_intSearch_not_looksLikeRange() throws Exception {
                assertObject(run(INT_BEAN_ARRAY, "f = ! -2 
-2")).asJson().is("[{f:-2},{f:-1},{f:0},{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_empty() throws Exception {
+       public void b24_intSearch_empty() throws Exception {
                for (String s : a("f=", "f = ", "f =  "))
                        assertObject(run(INT_BEAN_ARRAY, 
s)).asJson().is("[{f:-2},{f:-1},{f:0},{f:1},{f:2},{f:3}]");
        }
 
        @Test
-       public void intSearch_badSearches() throws Exception {
+       public void b25_intSearch_badSearches() throws Exception {
                String[] ss = new String[] {
                        "f=x","(S01)",
                        "f=>x","(S02)",
@@ -438,7 +441,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_singleDate_y() throws Exception {
+       public void c01_dateSearch_singleDate_y() throws Exception {
                B[] in = B.create("2010-01-01", "2011-01-01", "2011-01-31", 
"2012-01-01");
                for (String s : a(
                                "f=2011",
@@ -450,7 +453,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_singleDate_ym() throws Exception {
+       public void c02_dateSearch_singleDate_ym() throws Exception {
                B[] in = B.create("2010-01-01", "2011-01-01", "2011-01-31", 
"2012-01-01");
                for (String s : a(
                                "f=2011-01",
@@ -462,32 +465,32 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_singleDate_ymd() throws Exception {
+       public void c03_dateSearch_singleDate_ymd() throws Exception {
                B[] in = B.create("2010-01-01", "2011-01-01", "2011-01-31", 
"2012-01-01");
                assertObject(run(in, 
"f=2011-01-01")).asString(ws).is("[{f:'2011-01-01T00:00:00'}]");
        }
 
 
        @Test
-       public void dateSearch_singleDate_ymdh() throws Exception {
+       public void c04_dateSearch_singleDate_ymdh() throws Exception {
                B[] in = B.create("2011-01-01T11:15:59", "2011-01-01T12:00:00", 
"2011-01-01T12:59:59", "2011-01-01T13:00:00");
                assertObject(run(in, 
"f=2011-01-01T12")).asString(ws).is("[{f:'2011-01-01T12:00:00'},{f:'2011-01-01T12:59:59'}]");
        }
 
        @Test
-       public void dateSearch_singleDate_ymdhm() throws Exception {
+       public void c05_dateSearch_singleDate_ymdhm() throws Exception {
                B[] in = B.create("2011-01-01T12:29:59", "2011-01-01T12:30:00", 
"2011-01-01T12:30:59", "2011-01-01T12:31:00");
                assertObject(run(in, 
"f=2011-01-01T12:30")).asString(ws).is("[{f:'2011-01-01T12:30:00'},{f:'2011-01-01T12:30:59'}]");
        }
 
        @Test
-       public void dateSearch_singleDate_ymdhms() throws Exception {
+       public void c06_dateSearch_singleDate_ymdhms() throws Exception {
                B[] in = B.create("2011-01-01T12:30:29", "2011-01-01T12:30:30", 
"2011-01-01T12:30:31");
                assertObject(run(in, 
"f=2011-01-01T12:30:30")).asString(ws).is("[{f:'2011-01-01T12:30:30'}]");
        }
 
        @Test
-       public void dateSearch_openEndedRanges_y() throws Exception {
+       public void c07_dateSearch_openEndedRanges_y() throws Exception {
                B[] in = B.create("2000-12-31", "2001-01-01");
                for (String s : a(
                                "f>2000",
@@ -522,21 +525,21 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_openEndedRanges_toMinute() throws Exception {
+       public void c08_dateSearch_openEndedRanges_toMinute() throws Exception {
                B[] in = B.create("2011-01-01T12:29:59", "2011-01-01T12:30:00");
                assertObject(run(in, 
"f>=2011-01-01T12:30")).asString(ws).is("[{f:'2011-01-01T12:30:00'}]");
                assertObject(run(in, 
"f<2011-01-01T12:30")).asString(ws).is("[{f:'2011-01-01T12:29:59'}]");
        }
 
        @Test
-       public void dateSearch_openEndedRanges_toSecond() throws Exception {
+       public void c09_dateSearch_openEndedRanges_toSecond() throws Exception {
                B[] in = B.create("2011-01-01T12:30:59", "2011-01-01T12:31:00");
                assertObject(run(in, 
"f>2011-01-01T12:30")).asString(ws).is("[{f:'2011-01-01T12:31:00'}]");
                assertObject(run(in, 
"f<=2011-01-01T12:30")).asString(ws).is("[{f:'2011-01-01T12:30:59'}]");
        }
 
        @Test
-       public void dateSearch_closedRanges() throws Exception {
+       public void c10_dateSearch_closedRanges() throws Exception {
                B[] in = B.create("2000-12-31T23:59:59", "2001-01-01T00:00:00", 
"2003-06-30T23:59:59", "2003-07-01T00:00:00");
 
                for (String s : a(
@@ -580,7 +583,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_or1() throws Exception {
+       public void c11_dateSearch_or1() throws Exception {
                B[] in = B.create("2000-12-31", "2001-01-01", "2001-12-31", 
"2002-01-01");
                for (String s : a(
                                "f=2001 2003 2005",
@@ -594,7 +597,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_or2() throws Exception {
+       public void c12_dateSearch_or2() throws Exception {
                B[] in = B.create("2002-12-31", "2003-01-01", "2003-12-31", 
"2004-01-01");
                for (String s : a(
                                "f=2001 2003 2005",
@@ -608,7 +611,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_or3() throws Exception {
+       public void c13_dateSearch_or3() throws Exception {
                B[] in = B.create("2004-12-31", "2005-01-01", "2005-12-31", 
"2006-01-01");
                for (String s : a(
                                "f=2001 2003 2005",
@@ -622,7 +625,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void dateSearch_or_singleAndRange() throws Exception {
+       public void c14_dateSearch_or_singleAndRange() throws Exception {
                B[] in = B.create("2000-12-31", "2001-01-01", "2002-12-31", 
"2003-01-01");
                for (String s : a(
                                "f=2001 >2002",
@@ -682,7 +685,7 @@ public class ObjectSearcherTest {
 
        @Test
        @Ignore /* TODO - Fix me */
-       public void dateSearch_badSearches() throws Exception {
+       public void c15_dateSearch_badSearches() throws Exception {
                B[] in = B.create("2000-12-31");
                String[] ss = new String[] {
                        "f=X","(S01)",
@@ -713,7 +716,7 @@ public class ObjectSearcherTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void d2ListOfMaps() throws Exception {
+       public void d01_d2ListOfMaps() throws Exception {
                List<Map<?,?>> in = list(
                        map("f","foo"),
                        map("f","bar"),
@@ -726,7 +729,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2SetOfMaps() throws Exception {
+       public void d02_d2SetOfMaps() throws Exception {
                Set<Map<?,?>> in = set(
                        map("f","foo"),
                        map("f","bar"),
@@ -740,7 +743,7 @@ public class ObjectSearcherTest {
 
 
        @Test
-       public void d2ArrayOfMaps() throws Exception {
+       public void d03_d2ArrayOfMaps() throws Exception {
                Map<?,?>[] in = new Map[]{
                        map("f","foo"),
                        map("f","bar"),
@@ -753,7 +756,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2ListOfObjects() throws Exception {
+       public void d04_d2ListOfObjects() throws Exception {
                List<Object> in = list(
                        map("f","foo"),
                        map("f","bar"),
@@ -768,7 +771,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2SetOfObjects() throws Exception {
+       public void d05_d2SetOfObjects() throws Exception {
                Set<Object> in = set(
                        map("f","foo"),
                        map("f","bar"),
@@ -783,7 +786,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2ArrayOfObjects() throws Exception {
+       public void d06_d2ArrayOfObjects() throws Exception {
                Object[] in = new Object[]{
                        map("f","foo"),
                        map("f","bar"),
@@ -798,7 +801,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2ListOfMapsWithLists() throws Exception {
+       public void d07_d2ListOfMapsWithLists() throws Exception {
                List<Map<?,?>> in = list(
                        map("f",list("foo")),
                        map("f",list("bar")),
@@ -811,7 +814,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2SetOfMapsWithSets() throws Exception {
+       public void d08_d2SetOfMapsWithSets() throws Exception {
                Set<Map<?,?>> in = set(
                        map("f",set("foo")),
                        map("f",set("bar")),
@@ -824,7 +827,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2ArrayOfMapsWithArrays() throws Exception {
+       public void d09_d2ArrayOfMapsWithArrays() throws Exception {
                Map<?,?>[] in = new Map[]{
                        map("f",new Object[]{"foo"}),
                        map("f",new Object[]{"bar"}),
@@ -837,7 +840,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d2ListOfBeans() throws Exception {
+       public void d10_d2ListOfBeans() throws Exception {
                List<A> in = list(
                        A.create("foo"),
                        A.create("bar"),
@@ -848,7 +851,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ListOfListOfMaps() throws Exception {
+       public void d11_d3ListOfListOfMaps() throws Exception {
                List<List<Map<?,?>>> in = list(
                        list(map("f","foo")),
                        list(map("f","bar")),
@@ -862,7 +865,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3SetOfSetOfMaps() throws Exception {
+       public void d12_d3SetOfSetOfMaps() throws Exception {
                Set<Set<Map<?,?>>> in = set(
                        set(map("f","foo")),
                        set(map("f","bar")),
@@ -877,7 +880,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ArrayOfArrayOfMaps() throws Exception {
+       public void d13_d3ArrayOfArrayOfMaps() throws Exception {
                Map<?,?>[][] in = new Map[][]{
                        new Map[]{map("f","foo")},
                        new Map[]{map("f","bar")},
@@ -892,7 +895,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ListOfListOfObjects() throws Exception {
+       public void d14_d3ListOfListOfObjects() throws Exception {
                List<List<Object>> in = list(
                        list(map("f","foo")),
                        list(map("f","bar")),
@@ -907,7 +910,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3SetOfSetOfObjects() throws Exception {
+       public void d15_d3SetOfSetOfObjects() throws Exception {
                Set<Set<Object>> in = set(
                        set(map("f","foo")),
                        set(map("f","bar")),
@@ -923,7 +926,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ArrayOfArrayOfObjects() throws Exception {
+       public void d16_d3ArrayOfArrayOfObjects() throws Exception {
                Object[][] in = new Object[][]{
                        new Object[]{map("f","foo")},
                        new Object[]{map("f","bar")},
@@ -939,7 +942,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ListOfListOfMapsWithCollections() throws Exception {
+       public void d17_d3ListOfListOfMapsWithCollections() throws Exception {
                List<List<Map<?,?>>> in = list(
                        list(map("f",list("foo"))),
                        list(map("f",list("bar"))),
@@ -953,7 +956,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3SetOfSetOfMapsWithCollections() throws Exception {
+       public void d18_d3SetOfSetOfMapsWithCollections() throws Exception {
                Set<Set<Map<?,?>>> in = set(
                        set(map("f",set("foo"))),
                        set(map("f",set("bar"))),
@@ -967,7 +970,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ArrayOfArrayOfMapsWithCollections() throws Exception {
+       public void d19_d3ArrayOfArrayOfMapsWithCollections() throws Exception {
                Map<?,?>[][] in = new Map[][]{
                        new Map[]{map("f",new Object[]{"foo"})},
                        new Map[]{map("f",new Object[]{"bar"})},
@@ -981,7 +984,7 @@ public class ObjectSearcherTest {
        }
 
        @Test
-       public void d3ArrayOfArrayOfBeans() throws Exception {
+       public void d20_d3ArrayOfArrayOfBeans() throws Exception {
                A[][] in = new A[][]{
                        new A[]{A.create("foo")},
                        new A[]{A.create("bar")},
@@ -998,7 +1001,7 @@ public class ObjectSearcherTest {
 
 //     @Test
 //     public void noSearchArgs() {
-//             SearchArgs sa = new SearchArgs();
+//             SearchArgs sa = SearchArgs.create();
 //             assertObjectEquals("'foo'", run("foo", sa));
 //     }
 //
@@ -1012,7 +1015,7 @@ public class ObjectSearcherTest {
 //                             
assertTrue(e.getLocalizedMessage().contains("Invalid search terms"));
 //                     }
 //             }
-//             SearchArgs sa = new SearchArgs();
+//             SearchArgs sa = SearchArgs.create();
 //             assertObjectEquals("'foo'", run("foo", sa));
 //     }
 //
@@ -1028,13 +1031,13 @@ public class ObjectSearcherTest {
 //
 //     @Test
 //     public void searchArgsEmptyKey() {
-//             SearchArgs sa = new SearchArgs().append(null, "foo");
+//             SearchArgs sa = SearchArgs.create().append(null, "foo");
 //             assertObjectEquals("'foo'", run("foo", sa));
 //     }
 //
 //     @Test
 //     public void searchArgsEmptyValue() {
-//             SearchArgs sa = new SearchArgs().append("foo", null);
+//             SearchArgs sa = SearchArgs.create().append("foo", null);
 //             assertObjectEquals("'foo'", run("foo", sa));
 //     }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorterTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorter_Test.java
similarity index 51%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorterTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorter_Test.java
index fbd9fd919..574a19dab 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorterTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectSorter_Test.java
@@ -24,9 +24,9 @@ import org.junit.*;
  * Tests the PojoPaginator class.
  */
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectSorterTest {
+public class ObjectSorter_Test {
 
-       ObjectSorter p = new ObjectSorter();
+       ObjectSorter os = new ObjectSorter();
        BeanSession bs = BeanContext.DEFAULT_SESSION;
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -34,8 +34,24 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void nullInput() {
-               assertNull(p.run(bs, null, null));
+       public void a01_nullInput() {
+               assertNull(os.run(bs, null, null));
+       }
+
+       @Test
+       public void a02_emptySort() {
+               Object in = set(A.create("c"),A.create("a"),A.create("b"));
+               SortArgs sa = SortArgs.create("");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'c'},{f:'a'},{f:'b'}]");
+               assertObject(os.run(in, 
"")).asJson().is("[{f:'c'},{f:'a'},{f:'b'}]");
+       }
+
+       @Test
+       public void a03_invalidDataType() {
+               Object in = map("a","b");
+               SortArgs sa = SortArgs.create("x");
+               assertObject(os.run(bs, in, sa)).asJson().is("{a:'b'}");
+               assertObject(os.run(in, "x")).isNull();
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -53,31 +69,35 @@ public class ObjectSorterTest {
        }
 
        @Test
-       public void beanArray() {
+       public void b01_beanArray() {
                Object in = new 
A[]{A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d")};
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
        }
 
        @Test
-       public void beanArray_reverse() {
+       public void b02_beanArray_reverse() {
                Object in = new 
A[]{A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d")};
-               SortArgs sa = new SortArgs("f-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               SortArgs sa = SortArgs.create("f-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               assertObject(os.run(in, 
"f-")).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
        }
 
        @Test
-       public void beanArrayContainingNulls() {
+       public void b03_beanArrayContainingNulls() {
                Object in = new 
A[]{A.create("c"),A.create("a"),null,null,A.create("b")};;
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        @Test
-       public void beanArrayContainingDups() {
+       public void b04_beanArrayContainingDups() {
                Object in = new 
A[]{A.create("c"),A.create("a"),null,A.create("a"),A.create("b")};
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -85,31 +105,35 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanList() {
+       public void c01_beanList() {
                Object in = 
list(A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
        }
 
        @Test
-       public void beanList_reverse() {
+       public void c02_beanList_reverse() {
                Object in = 
list(A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d"));
-               SortArgs sa = new SortArgs("f-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               SortArgs sa = SortArgs.create("f-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               assertObject(os.run(in, 
"f-")).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
        }
 
        @Test
-       public void beanListContainingNull() {
+       public void c03_beanListContainingNull() {
                Object in = 
list(A.create("c"),A.create("a"),null,null,A.create("b"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,null,{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        @Test
-       public void beanListContainingDups() {
+       public void c04_beanListContainingDups() {
                Object in = 
list(A.create("c"),A.create("a"),null,A.create("a"),A.create("b"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -117,50 +141,35 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanSet() {
+       public void d01_beanSet() {
                Object in = 
set(A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
        }
 
        @Test
-       public void betSet_reverse() {
+       public void d02_beanSet_reverse() {
                Object in = 
set(A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d"));
-               SortArgs sa = new SortArgs("f-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               SortArgs sa = SortArgs.create("f-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               assertObject(os.run(in, 
"f-")).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
        }
 
        @Test
-       public void beanSetContainingNull() {
+       public void d03_beanSetContainingNull() {
                Object in = 
set(A.create("c"),A.create("a"),null,null,A.create("b"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'b'},{f:'c'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        @Test
-       public void beanSetContainingDups() {
+       public void d04_beanSetContainingDups() {
                Object in = 
set(A.create("c"),A.create("a"),null,A.create("a"),A.create("b"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
-       }
-
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Other
-       
//-----------------------------------------------------------------------------------------------------------------
-
-       @Test
-       public void emptySort() {
-               Object in = set(A.create("c"),A.create("a"),A.create("b"));
-               SortArgs sa = new SortArgs();
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'c'},{f:'a'},{f:'b'}]");
-       }
-
-       @Test
-       public void invalidDataType() {
-               Object in = map("a","b");
-               SortArgs sa = new SortArgs("x");
-               in = p.run(bs, in, sa);
-               assertObject(in).asJson().is("{a:'b'}");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[null,{f:'a'},{f:'a'},{f:'b'},{f:'c'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -168,17 +177,19 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void listOfMaps() {
+       public void e01_listOfMaps() {
                Object in = 
list(map("f","c"),map("f","a"),map("f","b"),map("f","e"),map("f","d"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[{f:'a'},{f:'b'},{f:'c'},{f:'d'},{f:'e'}]");
        }
 
        @Test
-       public void listOfMaps_reverse() {
+       public void e02_listOfMaps_reverse() {
                Object in = 
list(map("f","c"),map("f","a"),map("f","b"),map("f","e"),map("f","d"));
-               SortArgs sa = new SortArgs("f-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               SortArgs sa = SortArgs.create("f-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
+               assertObject(os.run(in, 
"f-")).asJson().is("[{f:'e'},{f:'d'},{f:'c'},{f:'b'},{f:'a'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -186,17 +197,19 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void listOfOther() {
+       public void f01_listOfOther() {
                Object in = list(list("c"),list("a"),list("b"));
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[['c'],['a'],['b']]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[['c'],['a'],['b']]");
+               assertObject(os.run(in, 
"f")).asJson().is("[['c'],['a'],['b']]");
        }
 
        @Test
-       public void listOfOther_reverse() {
+       public void f02_listOfOther_reverse() {
                Object in = list(list("c"),list("a"),list("b"));
-               SortArgs sa = new SortArgs("f-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[['c'],['a'],['b']]");
+               SortArgs sa = SortArgs.create("f-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[['c'],['a'],['b']]");
+               assertObject(os.run(in, 
"f-")).asJson().is("[['c'],['a'],['b']]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -204,10 +217,11 @@ public class ObjectSorterTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void nonExistentField() {
+       public void g01_nonExistentField() {
                Object in = new 
A[]{A.create("c"),A.create("a"),A.create("b"),A.create("e"),A.create("d")};
-               SortArgs sa = new SortArgs("fx");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:'c'},{f:'a'},{f:'b'},{f:'e'},{f:'d'}]");
+               SortArgs sa = SortArgs.create("fx");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:'c'},{f:'a'},{f:'b'},{f:'e'},{f:'d'}]");
+               assertObject(os.run(in, 
"fx")).asJson().is("[{f:'c'},{f:'a'},{f:'b'},{f:'e'},{f:'d'}]");
        }
 
        public static class B {
@@ -222,10 +236,11 @@ public class ObjectSorterTest {
 
        // Should gracefully handle different sorting data types.
        @Test
-       public void mixtureOfTypes() {
+       public void g02_mixtureOfTypes() {
                Object in = new B[]{B.create(1),B.create(true),B.create("a")};
-               SortArgs sa = new SortArgs("f");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f:1},{f:true},{f:'a'}]");
+               SortArgs sa = SortArgs.create("f");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f:1},{f:true},{f:'a'}]");
+               assertObject(os.run(in, 
"f")).asJson().is("[{f:1},{f:true},{f:'a'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -245,23 +260,26 @@ public class ObjectSorterTest {
        }
 
        @Test
-       public void sortMultipleColumns() {
+       public void h01_sortMultipleColumns() {
                Object in = new 
C[]{C.create(1,1),C.create(3,2),C.create(3,1),C.create(2,1),C.create(2,2)};
-               SortArgs sa = new SortArgs("f1","f2");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f1:1,f2:1.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:3,f2:1.0},{f1:3,f2:2.0}]");
+               SortArgs sa = SortArgs.create("f1,f2");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f1:1,f2:1.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:3,f2:1.0},{f1:3,f2:2.0}]");
+               assertObject(os.run(in, 
"f1,f2")).asJson().is("[{f1:1,f2:1.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:3,f2:1.0},{f1:3,f2:2.0}]");
        }
 
        @Test
-       public void sortMultipleColumns_descending() {
+       public void h02_sortMultipleColumns_descending() {
                Object in = new 
C[]{C.create(1,1),C.create(3,2),C.create(3,1),C.create(2,1),C.create(2,2)};
-               SortArgs sa = new SortArgs("f1-","f2-");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f1:3,f2:2.0},{f1:3,f2:1.0},{f1:2,f2:2.0},{f1:2,f2:1.0},{f1:1,f2:1.0}]");
+               SortArgs sa = SortArgs.create("f1-,f2-");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f1:3,f2:2.0},{f1:3,f2:1.0},{f1:2,f2:2.0},{f1:2,f2:1.0},{f1:1,f2:1.0}]");
+               assertObject(os.run(in, 
"f1-,f2-")).asJson().is("[{f1:3,f2:2.0},{f1:3,f2:1.0},{f1:2,f2:2.0},{f1:2,f2:1.0},{f1:1,f2:1.0}]");
        }
 
        @Test
-       public void sortMultipleColumns_ascendingAndDescending() {
+       public void h03_sortMultipleColumns_ascendingAndDescending() {
                Object in = new 
C[]{C.create(1,1),C.create(3,2),C.create(3,1),C.create(2,1),C.create(2,2)};
-               SortArgs sa = new SortArgs("f1-","f2+");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f1:3,f2:1.0},{f1:3,f2:2.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:1,f2:1.0}]");
+               SortArgs sa = SortArgs.create("f1-,f2+");
+               assertObject(os.run(bs, in, 
sa)).asJson().is("[{f1:3,f2:1.0},{f1:3,f2:2.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:1,f2:1.0}]");
+               assertObject(os.run(in, 
"f1-,f2+")).asJson().is("[{f1:3,f2:1.0},{f1:3,f2:2.0},{f1:2,f2:1.0},{f1:2,f2:2.0},{f1:1,f2:1.0}]");
        }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewerTest.java
 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewer_Test.java
similarity index 52%
rename from 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewerTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewer_Test.java
index b949e6ab9..43ec898df 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewerTest.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectViewer_Test.java
@@ -24,9 +24,9 @@ import org.junit.*;
  * Tests the PojoPaginator class.
  */
 @FixMethodOrder(NAME_ASCENDING)
-public class ObjectViewerTest {
+public class ObjectViewer_Test {
 
-       ObjectViewer p = new ObjectViewer();
+       ObjectViewer ov = new ObjectViewer();
        BeanSession bs = BeanContext.DEFAULT_SESSION;
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -34,8 +34,8 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void nullInput() {
-               assertNull(p.run(bs, null, null));
+       public void a01_nullInput() {
+               assertNull(ov.run(bs, null, null));
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -54,45 +54,43 @@ public class ObjectViewerTest {
        }
 
        @Test
-       public void simpleBean() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void b01_simpleBean() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.runSingle(in, "f1")).asJson().is("{f1:'x1'}");
        }
 
        @Test
-       public void simpleBean_reverseColumns() {
-               ViewArgs sa = new ViewArgs("f2","f1");
+       public void b02_simpleBean_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2,f1");
                Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+               assertObject(ov.runSingle(in, 
"f2,f1")).asJson().is("{f2:'x2',f1:'x1'}");
        }
 
        @Test
-       public void simpleBean_dupColumns() {
-               ViewArgs sa = new ViewArgs("f1","f1");
+       public void b03_simpleBean_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1,f1");
                Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.runSingle(in, 
"f1,f1")).asJson().is("{f1:'x1'}");
        }
 
        @Test
-       public void simpleBean_nonExistentColumns() {
+       public void b04_simpleBean_nonExistentColumns() {
                ViewArgs sa = new ViewArgs("fx");
                Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{}");
-       }
-
-       @Test
-       public void simpleBean_nullColumn() {
-               ViewArgs sa = new ViewArgs("f1",null);
-               Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.runSingle(in, "fx")).asJson().is("{}");
        }
 
        @Test
-       public void simpleBean_emptyArgs() {
-               ViewArgs sa = new ViewArgs();
+       public void b05_simpleBean_emptyArgs() {
+               ViewArgs sa = new ViewArgs("");
                Object in = A.create("x1","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.runSingle(in, "")).asJson().is("{}");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -100,10 +98,11 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void simpleBeanMap() {
+       public void b06_simpleBeanMap() {
                ViewArgs sa = new ViewArgs("f1");
                Object in = bs.toBeanMap(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.runSingle(in, "f1")).asJson().is("{f1:'x1'}");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -111,38 +110,35 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void simpleMap() {
+       public void b07_simpleMap() {
                ViewArgs sa = new ViewArgs("f1");
                Object in = map("f1","x1","f2","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.runSingle(in, "f1")).asJson().is("{f1:'x1'}");
        }
 
        @Test
-       public void simpleMap_reverseColumns() {
-               ViewArgs sa = new ViewArgs("f2","f1");
+       public void b08_simpleMap_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2,f1");
                Object in = map("f1","x1","f2","x2");
-               assertObject(p.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("{f2:'x2',f1:'x1'}");
+               assertObject(ov.runSingle(in, 
"f2,f1")).asJson().is("{f2:'x2',f1:'x1'}");
        }
 
        @Test
-       public void simpleMap_nonExistentColumns() {
+       public void b09_simpleMap_nonExistentColumns() {
                ViewArgs sa = new ViewArgs("fx");
                Object in = map("f1","x1","f2","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{}");
-       }
-
-       @Test
-       public void simpleMap_nullColumn() {
-               ViewArgs sa = new ViewArgs("f1",null);
-               Object in = map("f1","x1","f2","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{f1:'x1'}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.runSingle(in, "fx")).asJson().is("{}");
        }
 
        @Test
-       public void simpleMap_emptyView() {
-               ViewArgs sa = new ViewArgs();
+       public void b10_simpleMap_emptyView() {
+               ViewArgs sa = new ViewArgs("");
                Object in = map("f1","x1","f2","x2");
-               assertObject(p.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.run(bs, in, sa)).asJson().is("{}");
+               assertObject(ov.runSingle(in, "")).asJson().is("{}");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -150,52 +146,51 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanArray() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void c01_beanArray() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanArray_reverseColumns() {
-               ViewArgs sa = new ViewArgs("f2","f1");
+       public void c02_beanArray_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2,f1");
                Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(in, 
"f2,f1")).asJson().is("[{f2:'x2',f1:'x1'}]");
        }
 
        @Test
-       public void beanArray_dupColumns() {
-               ViewArgs sa = new ViewArgs("f1","f1");
+       public void c03_beanArray_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1,f1");
                Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1,f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanArray_nonExistentColumns() {
+       public void c04_beanArray_nonExistentColumns() {
                ViewArgs sa = new ViewArgs("fx");
                Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
-       }
-
-       @Test
-       public void beanArray_nullColumn() {
-               ViewArgs sa = new ViewArgs("f1",null);
-               Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "fx")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanArray_emptyArgs() {
-               ViewArgs sa = new ViewArgs();
+       public void c05_beanArray_emptyArgs() {
+               ViewArgs sa = new ViewArgs("");
                Object in = new A[]{A.create("x1","x2")};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanArray_withNull() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void c06_beanArray_withNull() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = new A[]{A.create("x1","x2"),null};
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'},null]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -203,52 +198,51 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanList() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void d01_beanList() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanList_reverseColumns() {
-               ViewArgs sa = new ViewArgs("f2","f1");
+       public void d02_beanList_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2,f1");
                Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(in, 
"f2,f1")).asJson().is("[{f2:'x2',f1:'x1'}]");
        }
 
        @Test
-       public void beanList_dupColumns() {
-               ViewArgs sa = new ViewArgs("f1","f1");
+       public void d03_beanList_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1,f1");
                Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1,f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanList_nonExistentColumns() {
+       public void d04_beanList_nonExistentColumns() {
                ViewArgs sa = new ViewArgs("fx");
                Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "fx")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanList_nullColumn() {
-               ViewArgs sa = new ViewArgs("f1",null);
+       public void d05_beanList_emptyArgs() {
+               ViewArgs sa = new ViewArgs("");
                Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanList_emptyArgs() {
-               ViewArgs sa = new ViewArgs();
-               Object in = list(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
-       }
-
-       @Test
-       public void beanList_withNull() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void d06_beanList_withNull() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = list(A.create("x1","x2"),null);
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'},null]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -256,52 +250,51 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanSet() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void e01_beanSet() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanSet_reverseColumns() {
-               ViewArgs sa = new ViewArgs("f2","f1");
+       public void e02_beanSet_reverseColumns() {
+               ViewArgs sa = new ViewArgs("f2,f1");
                Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f2:'x2',f1:'x1'}]");
+               assertObject(ov.run(in, 
"f2,f1")).asJson().is("[{f2:'x2',f1:'x1'}]");
        }
 
        @Test
-       public void beanSet_dupColumns() {
-               ViewArgs sa = new ViewArgs("f1","f1");
+       public void e03_beanSet_dupColumns() {
+               ViewArgs sa = new ViewArgs("f1,f1");
                Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1,f1")).asJson().is("[{f1:'x1'}]");
        }
 
        @Test
-       public void beanSet_nonExistentColumns() {
+       public void e04_beanSet_nonExistentColumns() {
                ViewArgs sa = new ViewArgs("fx");
                Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
-       }
-
-       @Test
-       public void beanSet_nullColumn() {
-               ViewArgs sa = new ViewArgs("f1",null);
-               Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "fx")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanSet_emptyArgs() {
-               ViewArgs sa = new ViewArgs();
+       public void e05_beanSet_emptyArgs() {
+               ViewArgs sa = new ViewArgs("");
                Object in = set(A.create("x1","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{}]");
+               assertObject(ov.run(in, "")).asJson().is("[{}]");
        }
 
        @Test
-       public void beanSet_withNull() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void e06_beanSet_withNull() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = set(A.create("x1","x2"),null);
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(bs, in, 
sa)).asJson().is("[{f1:'x1'},null]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'},null]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -309,10 +302,10 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void otherObject() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void f01_otherObject() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = "foobar";
-               assertObject(p.run(bs, in, sa)).asJson().is("'foobar'");
+               assertObject(ov.run(bs, in, sa)).asJson().is("'foobar'");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -320,10 +313,11 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void mapList() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void g01_mapList() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = list(map("f1","x1","f2","x2"));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -331,10 +325,11 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void beanMapList() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void h01_beanMapList() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = list(bs.toBeanMap(A.create("x1","x2")));
-               assertObject(p.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(bs, in, sa)).asJson().is("[{f1:'x1'}]");
+               assertObject(ov.run(in, "f1")).asJson().is("[{f1:'x1'}]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -342,10 +337,10 @@ public class ObjectViewerTest {
        
//-----------------------------------------------------------------------------------------------------------------
 
        @Test
-       public void otherObjectList() {
-               ViewArgs sa = new ViewArgs("f1");;
+       public void i01_otherObjectList() {
+               ViewArgs sa = new ViewArgs("f1");
                Object in = list("foobar");
-               assertObject(p.run(bs, in, sa)).asJson().is("['foobar']");
+               assertObject(ov.run(bs, in, sa)).asJson().is("['foobar']");
+               assertObject(ov.run(in, "f1")).asJson().is("['foobar']");
        }
-
 }

Reply via email to