/**
* Copyright 2010 Wealthfront 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.kaching.platform.converters;
import static com.google.common.collect.Lists.newArrayList;
import static java.lang.String.format;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import com.kaching.platform.common.Option;
class InstantiatorImpl<T> implements Instantiator<T> {
private final Constructor<T> constructor;
private final Field[] fields;
@SuppressWarnings("rawtypes")
private final Converter[] converters;
private final BitSet optionality;
private final BitSet wrapInOption;
private final String[] defaultValues;
private final Object[] defaultConstants;
private final String[] parameterNames;
InstantiatorImpl(
Constructor<T> constructor,
Converter<?>[] converters,
Field[] fields,
BitSet optionality,
BitSet wrapInOption,
String[] defaultValues,
Object[] defaultConstants,
String[] parameterNames) {
this.constructor = constructor;
this.converters = converters;
this.fields = fields;
this.optionality = optionality;
this.wrapInOption = wrapInOption;
this.defaultValues = defaultValues;
this.defaultConstants = defaultConstants;
this.parameterNames = parameterNames;
}
@Override
public T newInstance(String... values) {
return newInstance(Arrays.asList(values));
}
@Override
public T newInstance(Map<String, String> namedValues) {
if (parameterNames == null) {
throw new UnsupportedOperationException();
}
List<String> values = newArrayList();
for (String paramaterName : parameterNames) {
values.add(namedValues.get(paramaterName));
}
return newInstance(values);
}
@Override
public T newInstance(Iterable<String> values) {
try {
if (converters != null) {
Object[] parameters = new Object[converters.length];
Iterator<String> valuesIterator = values.iterator();
for (int i = 0; i < converters.length; i++) {
String value = valuesIterator.hasNext() ? valuesIterator.next() : null;
Converter<?> converter = converters[i];
// TODO(pascal): properly handle predicates.
Object parameter;
if (value == null) {
if (wrapInOption.get(i)) {
parameter = Option.none();
} else if (optionality.get(i)) {
if (defaultValues != null && defaultValues[i] != null) {
parameter = convert(converter, defaultValues[i]);
} else {
if (defaultConstants != null && defaultConstants[i] != null) {
parameter = defaultConstants[i];
} else {
parameter = null;
}
}
} else {
throw new IllegalArgumentException(format(
"parameter %s is not optional but null was provided",
i + 1));
}
} else {
parameter = convert(converter, value);
if (wrapInOption.get(i)) {
parameter = Option.some(parameter);
}
}
parameters[i] = parameter;
}
if (valuesIterator.hasNext()) {
throw new IllegalArgumentException("wrong number of arguments");
}
return constructor.newInstance(parameters);
} else {
return constructor.newInstance();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
// do proper exception handling including de-wrapping exceptions
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public List<String> fromInstance(T instance) {
// TODO(pascal): Rewrite this naive implementation. The goal is to show
// the skeleton a full example of destantiating.
List<String> parameters = Lists.newArrayListWithCapacity(fields.length);
for (int i = 0; i < fields.length; i++) {
try {
Field field = fields[i];
String parameterAsString;
if (field != null) {
Object value = field.get(instance);
if (wrapInOption.get(i)) {
value = ((Option<Object>) value).getOrElse((Object) null);
}
parameterAsString = value == null ? null : converters[i].toString(value);
} else {
parameterAsString = null;
}
parameters.add(parameterAsString);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return parameters;
}
private Object convert(Converter<?> converter, String value) {
Object parameter = converter.fromString(value);
if (parameter == null) {
throw new IllegalStateException(format(
"converter %s produced a null value", converter.getClass()));
}
return parameter;
}
@Override
public Constructor<T> getConstructor() {
return constructor;
}
@Override
public String toString() {
return "instantiator " + constructor.toString().replaceFirst("(public|protected|private) ", "");
}
}