/*
* 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);
}
}