G2A Many GEOs
SHARE
TWEET

FormDeserializer

tpeierls Mar 16th, 2012 716 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
Ledger Nano X - The secure hardware wallet
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top