/* * Copyright (c) 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.api.client.util; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Map; import java.util.concurrent.ConcurrentMap; /** * Collects the array values of a key/value data object, writing the fields or map values only after * all values have been collected. * * <p> * The typical application for this is when parsing JSON or XML when the value type is known to be * an array. It stores the values in a collection during the parsing, and only when the parsing of * an object is finished does it convert the collection into an array and stores it. * </p> * * <p> * Use {@link #put(String, Class, Object)} when the destination object is a map with string keys and * whose values accept an array of objects. Use {@link #put(Field, Class, Object)} when setting the * value of a field using reflection, assuming its type accepts an array of objects. One can * potentially use both {@code put} methods for example on an instance of {@link GenericData}. * </p> * * <p> * Implementation is not thread-safe. For a thread-safe choice instead use an implementation of * {@link ConcurrentMap}. * </p> * * @since 1.4 * @author Yaniv Inbar */ public final class ArrayValueMap { /** Array values on a particular field. */ static class ArrayValue { /** Array component type. */ final Class<?> componentType; /** Values to be stored in the array. */ final ArrayList<Object> values = new ArrayList<Object>(); /** * @param componentType array component type */ ArrayValue(Class<?> componentType) { this.componentType = componentType; } /** Creates a new array whose content matches that of the {@link #values}. */ Object toArray() { return Types.toArray(values, componentType); } /** * Adds a given value to the array, checking the given component type matches the previously * stored component type. */ void addValue(Class<?> componentType, Object value) { Preconditions.checkArgument(componentType == this.componentType); values.add(value); } } /** Map from key name to its array values. */ private final Map<String, ArrayValueMap.ArrayValue> keyMap = ArrayMap.create(); /** Map from field to its array values. */ private final Map<Field, ArrayValueMap.ArrayValue> fieldMap = ArrayMap.create(); /** Destination object whose fields must be set, or destination map whose values must be set. */ private final Object destination; /** * @param destination destination object whose fields must be set, or destination map whose values * must be set */ public ArrayValueMap(Object destination) { this.destination = destination; } /** * Sets the fields of the given object using the values collected during parsing of the object's * fields. */ public void setValues() { for (Map.Entry<String, ArrayValueMap.ArrayValue> entry : keyMap.entrySet()) { @SuppressWarnings("unchecked") Map<String, Object> destinationMap = (Map<String, Object>) destination; destinationMap.put(entry.getKey(), entry.getValue().toArray()); } for (Map.Entry<Field, ArrayValueMap.ArrayValue> entry : fieldMap.entrySet()) { FieldInfo.setFieldValue(entry.getKey(), destination, entry.getValue().toArray()); } } /** * Puts an additional value for the given field, accumulating values on repeated calls on the same * field. * * @param field field * @param arrayComponentType array component type * @param value value */ public void put(Field field, Class<?> arrayComponentType, Object value) { ArrayValueMap.ArrayValue arrayValue = fieldMap.get(field); if (arrayValue == null) { arrayValue = new ArrayValue(arrayComponentType); fieldMap.put(field, arrayValue); } arrayValue.addValue(arrayComponentType, value); } /** * Puts an additional value for the given key name, accumulating values on repeated calls on the * same key name. * * @param keyName key name * @param arrayComponentType array component type * @param value value */ public void put(String keyName, Class<?> arrayComponentType, Object value) { ArrayValueMap.ArrayValue arrayValue = keyMap.get(keyName); if (arrayValue == null) { arrayValue = new ArrayValue(arrayComponentType); keyMap.put(keyName, arrayValue); } arrayValue.addValue(arrayComponentType, value); } }