SHARE
TWEET

FormDeserializer

tpeierls Mar 16th, 2012 573 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. package net.peierls.util.restlet;
  2.  
  3. import com.google.common.base.CharMatcher;
  4. import com.google.common.base.Functions;
  5. import com.google.common.base.Predicate;
  6. import com.google.common.base.Splitter;
  7. import com.google.common.collect.ImmutableList;
  8. import com.google.common.collect.Iterables;
  9. import com.google.common.collect.Ordering;
  10. import com.google.common.collect.PeekingIterator;
  11. import static com.google.common.collect.Iterators.peekingIterator;
  12. import static com.google.common.collect.Lists.newArrayList;
  13. import static com.google.common.collect.Maps.newLinkedHashMap;
  14.  
  15. import java.util.ArrayList;
  16. import java.util.List;
  17. import java.util.Map;
  18.  
  19. import javax.inject.Inject;
  20.  
  21. import org.codehaus.jackson.map.ObjectMapper;
  22.  
  23. import org.restlet.data.Form;
  24. import org.restlet.util.NamedValue;
  25.  
  26.  
  27. /**
  28.  * Converts HTML form representation (application/www-form-urlencoded)
  29.  * to POJO.
  30.  */
  31. public class FormDeserializer {
  32.  
  33.     /**
  34.      * Creates a form deserializer that will use the give object
  35.      * mapper to convert the basic structure to a typed target
  36.      * object and to deserialize the values of that structure
  37.      * from strings.
  38.      */
  39.     @Inject public FormDeserializer(ObjectMapper objectMapper) {
  40.         this.objectMapper = objectMapper;
  41.     }
  42.  
  43.  
  44.     /**
  45.      * Converts a Form to a Java object of the target type, using
  46.      * each {@code name=value} pair to set the corresponding property
  47.      * on the target object.
  48.      * <p>
  49.      * Compound names, using period (.) as the delimiter, are treated
  50.      * as pseudo-dereferences (a la JavaScript or Groovy) to set properties
  51.      * of sub-objects, e.g., {@code a.b=c} for bean targets is treated
  52.      * like a call to {@code target.getA().setB(c)}.
  53.      * <p>
  54.      * Numeric components of compound names are treated as indices
  55.      * into a sequence named by the preceding components, e.g.,
  56.      * {@code a.1=c} is treated as {@code target.getA()[1] = c}
  57.      * (or {@code target.getA().set(1, c)}, if the "a" property of
  58.      * the target is a list rather than an array).
  59.      * Unless an element with index 0 is set, the indices are origin 1.
  60.      * <p>
  61.      * Sequences are also created by names with multiple values, e.g.,
  62.      * {@code a=x&a=y} is equivalent to {@code a.1=x&a.2=y}, with the
  63.      * value of the "a" property in the target being a sequence of two
  64.      * values.
  65.      * <p>
  66.      * When a name appears both indexed and non-indexed, the last
  67.      * assignment wins: {@code a=x&a.1=a1&a.2=a2} will set the "a"
  68.      * property to a sequence of values {@code [a1, a2]}, but
  69.      * {@code a.1=a1&a.2=a2&a=x} will set the "a" property to {@code x}.
  70.      * <p>
  71.      * If the top level consists only of integer indices, the subobjects
  72.      * will be interpreted as elements of a sequence (and T must be a List
  73.      * or List subtype instead).
  74.      * <p>
  75.      * The values are deserialized using the Jackson ObjectMapper that was
  76.      * used to construct this FormDeserializer.
  77.      * Any type that can be deserialized from a string can be used.
  78.      * While it is possible for Jackson to deserialize object graphs
  79.      * that have internal references, it is not possible for the form
  80.      * values to refer outside of themselves.
  81.      * <p>
  82.      * Runtime exceptions from Jackson conversion are propagated without
  83.      * exception translation. This could be considered a bug.
  84.      * @param source the Restlet Form object to be deserialized
  85.      * @param targetType the type of the target object into which the form
  86.      * is to be deserialized.
  87.      * @return the deserialized object of the target type
  88.      */
  89.     public <T> T deserialize(Form source, Class<T> targetType) {
  90.         Map<String, Object> rootMap = newLinkedHashMap();
  91.         for (NamedValue<String> namedValue : source) {
  92.             String name = namedValue.getName();
  93.             String value = namedValue.getValue();
  94.             Iterable<String> names = ON_PERIODS.split(name);
  95.             if (!Iterables.isEmpty(names) && Iterables.all(names, NON_BLANK)) {
  96.                 addToMap(rootMap, peekingIterator(names.iterator()), value);
  97.             }
  98.         }
  99.         removeNullPrefix(rootMap);
  100.         List<Object> rootList = numericKeysToList(rootMap);
  101.         if (rootList == null) {
  102.             return objectMapper.convertValue(rootMap, targetType);
  103.         } else {
  104.             return objectMapper.convertValue(rootList, targetType);
  105.         }
  106.     }
  107.  
  108.     private static void addToMap(Map<String, Object> map,
  109.                                  PeekingIterator<String> names, String value) {
  110.  
  111.         assert names.hasNext() : "empty name list in call to addToMap";
  112.         String name = names.next();
  113.         Object oldValue = map.get(name);
  114.  
  115.         // Decide whether this is a leaf node, in which case we can
  116.         // assign the value, or an internal node.
  117.  
  118.         if (names.hasNext()) {
  119.  
  120.             // This is an internal node, so the thing referred to by
  121.             // name is either a List or a Map, depending on whether
  122.             // the next name is an index or a name.
  123.  
  124.             String nextKey = names.peek();
  125.             if (NUMERIC.apply(nextKey)) {
  126.  
  127.                 // The next name is an index, so we make sure
  128.                 // the thing referred to by name is a list, and
  129.                 // then we call addToList.
  130.  
  131.                 ArrayList<Object> values;
  132.  
  133.                 if (oldValue instanceof ArrayList) {
  134.  
  135.                     // Already have a list in place.
  136.  
  137.                     @SuppressWarnings("unchecked")
  138.                     ArrayList<Object> tmp = (ArrayList<Object>) oldValue;
  139.  
  140.                     values = tmp;
  141.  
  142.                 } else {
  143.  
  144.                     // Nothing here, or something that isn't indexable,
  145.                     // so we replace with list.
  146.  
  147.                     values = newArrayList();
  148.  
  149.                     map.put(name, values);
  150.                 }
  151.  
  152.                 int index = Integer.valueOf(names.next());
  153.                 addToList(values, index, names, value);
  154.  
  155.             } else {
  156.  
  157.                 // The next name is a name, not an index, so
  158.                 // we make sure the value at this name is a map.
  159.  
  160.                 Map<String, Object> subMap;
  161.  
  162.                 if (oldValue instanceof Map) {
  163.  
  164.                     // It's a map already.
  165.  
  166.                     @SuppressWarnings("unchecked")
  167.                     Map<String, Object> tmp = (Map<String, Object>) oldValue;
  168.  
  169.                     subMap = tmp;
  170.  
  171.                 } else {
  172.  
  173.                     // Whatever is there is not a map, so we replace
  174.                     // it with an empty map.
  175.  
  176.                     subMap = newLinkedHashMap();
  177.                     map.put(name, subMap);
  178.                 }
  179.  
  180.                 // Now we can recursively add. We only peeked
  181.                 // at next name; we haven't consumed it.
  182.  
  183.                 addToMap(subMap, names, value);
  184.  
  185.             }
  186.  
  187.         } else {
  188.  
  189.             // This is a leaf node, so we can put the new value,
  190.             // but we handle things differently depending on
  191.             // whether there is an existing value (and what type
  192.             // it is, if so).
  193.  
  194.             if (oldValue == null) {
  195.  
  196.                 // Nothing there, so we add it.
  197.                 map.put(name, value);
  198.  
  199.             } else if (oldValue instanceof Map) {
  200.  
  201.                 // There's an existing map. We could go either way
  202.                 // on this, but our policy is last thing in wins,
  203.                 // so we replace the map with the new scalar value.
  204.  
  205.                 map.put(name, value);
  206.  
  207.             } else {
  208.  
  209.                 // There's an existing value that is either scalar
  210.                 // or a list. Either way this name is going to be
  211.                 // associated with a list.
  212.  
  213.                 if (oldValue instanceof ArrayList) {
  214.  
  215.                     // Existing value is a list, so we add to it.
  216.  
  217.                     @SuppressWarnings("unchecked")
  218.                     ArrayList<Object> values = (ArrayList<Object>) oldValue;
  219.  
  220.                     values.add(value);
  221.  
  222.                 } else {
  223.  
  224.                     // Existing value is a scalar, so we convert
  225.                     // the value at this name to a list containing
  226.                     // the existing value and the new value.
  227.  
  228.                     map.put(name, newArrayList(oldValue, value));
  229.                 }
  230.             }
  231.         }
  232.     }
  233.  
  234.     private static void addToList(ArrayList<Object> list, int index,
  235.                                   PeekingIterator<String> names, String value) {
  236.  
  237.         // We are going to be putting something at the given index, so
  238.         // we add nulls to make sure there's a slot of it.
  239.  
  240.         while (list.size() <= index) {
  241.             list.add(null);
  242.         }
  243.  
  244.         Object oldValue = list.get(index);
  245.  
  246.         // Decide whether this is a leaf node, in which case we can
  247.         // assign the value, or an internal node.
  248.  
  249.         if (names.hasNext()) {
  250.  
  251.             String nextKey = names.peek();
  252.             if (NUMERIC.apply(nextKey)) {
  253.  
  254.                 // The next name is an index, so we make sure
  255.                 // the thing at the given index is a list, and
  256.                 // then we call addToList.
  257.  
  258.                 ArrayList<Object> values;
  259.  
  260.                 if (oldValue instanceof ArrayList) {
  261.  
  262.                     // Already have a list in place.
  263.  
  264.                     @SuppressWarnings("unchecked")
  265.                     ArrayList<Object> tmp = (ArrayList<Object>) oldValue;
  266.  
  267.                     values = tmp;
  268.  
  269.                 } else {
  270.  
  271.                     // Nothing here, or something that isn't indexable,
  272.                     // so we replace with list.
  273.  
  274.                     values = newArrayList();
  275.                     list.set(index, values);
  276.                 }
  277.  
  278.                 int subIndex = Integer.valueOf(names.next());
  279.                 addToList(values, subIndex, names, value);
  280.  
  281.             } else {
  282.  
  283.                 // The next name is a name, not an index, so
  284.                 // we make sure the value at this index is a map.
  285.  
  286.                 Map<String, Object> subMap;
  287.  
  288.                 if (oldValue instanceof Map) {
  289.  
  290.                     // It's a map already.
  291.  
  292.                     @SuppressWarnings("unchecked")
  293.                     Map<String, Object> tmp = (Map<String, Object>) oldValue;
  294.  
  295.                     subMap = tmp;
  296.  
  297.                 } else {
  298.  
  299.                     // Whatever is there is not a map, so we replace
  300.                     // it with an empty map.
  301.  
  302.                     subMap = newLinkedHashMap();
  303.                     list.set(index, subMap);
  304.                 }
  305.  
  306.                 // Now we can recursively add. We only peeked
  307.                 // at next name; we haven't consumed it.
  308.  
  309.                 addToMap(subMap, names, value);
  310.  
  311.             }
  312.         } else {
  313.  
  314.             // This is a leaf node, so we can set the new value,
  315.             // but we handle things differently depending on
  316.             // whether there is an existing value (and what type
  317.             // it is, if so).
  318.  
  319.             if (oldValue == null) {
  320.  
  321.                 // Nothing there, so we set it.
  322.                 list.set(index, value);
  323.  
  324.             } else if (oldValue instanceof Map) {
  325.  
  326.                 // There's an existing map. We could go either way
  327.                 // on this, but our policy is last thing in wins,
  328.                 // so we replace the map with the new scalar value.
  329.  
  330.                 list.set(index, value);
  331.  
  332.             } else {
  333.  
  334.                 // There's an existing value that is either scalar
  335.                 // or a list. Either way this name is going to be
  336.                 // associated with a list.
  337.  
  338.                 if (oldValue instanceof ArrayList) {
  339.  
  340.                     // Existing value is a list, so we add to it.
  341.  
  342.                     @SuppressWarnings("unchecked")
  343.                     ArrayList<Object> values = (ArrayList<Object>) oldValue;
  344.  
  345.                     values.add(value);
  346.  
  347.                 } else {
  348.  
  349.                     // Existing value is a scalar, so we convert
  350.                     // the value at this name to a list containing
  351.                     // the existing value and the new value.
  352.  
  353.                     list.set(index, newArrayList(oldValue, value));
  354.                 }
  355.             }
  356.         }
  357.     }
  358.  
  359.     /**
  360.      * Recursively removes the 0th (first) element of every
  361.      * list within a value when that element is null. This
  362.      * handles the question of 0-origin vs. 1-origin indexing.
  363.      */
  364.     private static void removeNullPrefix(Object value) {
  365.         if (value instanceof ArrayList) {
  366.             @SuppressWarnings("unchecked")
  367.             ArrayList<Object> list = (ArrayList<Object>) value;
  368.             if (list.get(0) == null) {
  369.                 list.remove(0);
  370.             }
  371.             for (Object v : list) {
  372.                 removeNullPrefix(v);
  373.             }
  374.         } else if (value instanceof Map) {
  375.             @SuppressWarnings("unchecked")
  376.             Map<String, Object> map = (Map<String, Object>) value;
  377.             for (Object v : map.values()) {
  378.                 removeNullPrefix(v);
  379.             }
  380.         }
  381.     }
  382.  
  383.  
  384.     /**
  385.      * If all of the given map's names are numeric, converts this map
  386.      * into a list by sorting the names and returning a list of the
  387.      * values in name order. Returns null otherwise. This means that
  388.      * missing indices are ignored, but this is only called at the
  389.      * top level, where it makes no sense to have gaps.
  390.      */
  391.     private static List<Object> numericKeysToList(final Map<String, Object> map) {
  392.         if (Iterables.all(map.keySet(), NUMERIC)) {
  393.             return ImmutableList.copyOf(Iterables.transform(
  394.                 Ordering.natural().sortedCopy(map.keySet()),
  395.                 Functions.forMap(map)
  396.             ));
  397.         } else {
  398.             return null;
  399.         }
  400.     }
  401.  
  402.     private final ObjectMapper objectMapper;
  403.  
  404.     private static final Splitter ON_PERIODS = Splitter.on('.').trimResults();
  405.  
  406.     private static final Predicate<String> NON_BLANK = new Predicate<String>() {
  407.         public boolean apply(String s) {
  408.             return !s.isEmpty();
  409.         }
  410.     };
  411.  
  412.     private static final Predicate<String> NUMERIC = new Predicate<String>() {
  413.         public boolean apply(String s) {
  414.             return CharMatcher.DIGIT.matchesAllOf(s);
  415.         }
  416.     };
  417. }
RAW Paste Data
Top