blob: b3e57ebbd26837ba80228a3d6275ff561b4b1cf6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.johnzon.mapper;
import org.apache.johnzon.core.JsonParserFactoryImpl;
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.access.BaseAccessMode;
import org.apache.johnzon.mapper.access.FieldAccessMode;
import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
import org.apache.johnzon.mapper.access.MethodAccessMode;
import org.apache.johnzon.mapper.converter.BigDecimalConverter;
import org.apache.johnzon.mapper.converter.BigIntegerConverter;
import org.apache.johnzon.mapper.converter.BooleanConverter;
import org.apache.johnzon.mapper.converter.ByteConverter;
import org.apache.johnzon.mapper.converter.CachedDelegateConverter;
import org.apache.johnzon.mapper.converter.CharacterConverter;
import org.apache.johnzon.mapper.converter.ClassConverter;
import org.apache.johnzon.mapper.converter.DateConverter;
import org.apache.johnzon.mapper.converter.DoubleConverter;
import org.apache.johnzon.mapper.converter.FloatConverter;
import org.apache.johnzon.mapper.converter.IntegerConverter;
import org.apache.johnzon.mapper.converter.LocaleConverter;
import org.apache.johnzon.mapper.converter.LongConverter;
import org.apache.johnzon.mapper.converter.ShortConverter;
import org.apache.johnzon.mapper.converter.StringConverter;
import org.apache.johnzon.mapper.converter.URIConverter;
import org.apache.johnzon.mapper.converter.URLConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
import org.apache.johnzon.mapper.internal.ConverterAdapter;
import javax.json.JsonReaderFactory;
import javax.json.spi.JsonProvider;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
import java.io.Closeable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
// this class is responsible to hold any needed config
// to build the runtime
public class MapperBuilder {
private static final Map<AdapterKey, Adapter<?, ?>> DEFAULT_CONVERTERS = new HashMap<AdapterKey, Adapter<?, ?>>(24);
static {
//DEFAULT_CONVERTERS.put(Date.class, new DateConverter("yyyy-MM-dd'T'HH:mm:ssZ")); // ISO8601 long RFC822 zone
DEFAULT_CONVERTERS.put(new AdapterKey(Date.class, String.class), new ConverterAdapter<Date>(new DateConverter("yyyyMMddHHmmssZ"))); // ISO8601 short
DEFAULT_CONVERTERS.put(new AdapterKey(URL.class, String.class), new ConverterAdapter<URL>(new URLConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(URI.class, String.class), new ConverterAdapter<URI>(new URIConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Class.class, String.class), new ConverterAdapter<Class<?>>(new ClassConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(String.class, String.class), new ConverterAdapter<String>(new StringConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(BigDecimal.class, String.class), new ConverterAdapter<BigDecimal>(new BigDecimalConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(BigInteger.class, String.class), new ConverterAdapter<BigInteger>(new BigIntegerConverter()));
/* primitives should be hanlded low level and adapters will wrap them in string which is unlikely
DEFAULT_CONVERTERS.put(new AdapterKey(Byte.class, String.class), new ConverterAdapter<Byte>(new CachedDelegateConverter<Byte>(new ByteConverter())));
DEFAULT_CONVERTERS.put(new AdapterKey(Character.class, String.class), new ConverterAdapter<Character>(new CharacterConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Double.class, String.class), new ConverterAdapter<Double>(new DoubleConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Float.class, String.class), new ConverterAdapter<Float>(new FloatConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Integer.class, String.class), new ConverterAdapter<Integer>(new IntegerConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Long.class, String.class), new ConverterAdapter<Long>(new LongConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Short.class, String.class), new ConverterAdapter<Short>(new ShortConverter()));
DEFAULT_CONVERTERS.put(new AdapterKey(Boolean.class, String.class), new ConverterAdapter<Boolean>(new CachedDelegateConverter<Boolean>(new BooleanConverter())));
DEFAULT_CONVERTERS.put(new AdapterKey(byte.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Byte.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(char.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Character.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(double.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Double.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(float.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Float.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(int.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Integer.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(long.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Long.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(short.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Short.class, String.class)));
DEFAULT_CONVERTERS.put(new AdapterKey(boolean.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Boolean.class, String.class)));
*/
DEFAULT_CONVERTERS.put(new AdapterKey(Locale.class, String.class), new LocaleConverter());
}
private JsonReaderFactory readerFactory;
private JsonGeneratorFactory generatorFactory;
private boolean supportHiddenAccess = true;
private int maxSize = -1;
private int bufferSize = -1;
private String bufferStrategy;
private Comparator<String> attributeOrder = null;
private boolean supportConstructors;
private boolean useGetterForCollections;
private String accessModeName;
private boolean pretty;
private final Collection<Closeable> closeables = new ArrayList<Closeable>();
private int version = -1;
private boolean close;
private boolean skipNull = true;
private boolean skipEmptyArray;
private boolean supportsComments;
private boolean treatByteArrayAsBase64;
private boolean treatByteArrayAsBase64URL;
private boolean readAttributeBeforeWrite;
private boolean enforceQuoteString;
private AccessMode accessMode;
private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
private ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters = new ConcurrentHashMap<AdapterKey, Adapter<?, ?>>(DEFAULT_CONVERTERS);
private Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders = new HashMap<Class<?>, ObjectConverter.Reader<?>>();
private Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters = new HashMap<Class<?>, ObjectConverter.Writer<?>>();
private Map<Class<?>, String[]> ignoredForFields = new HashMap<Class<?>, String[]>();
private boolean primitiveConverters;
public Mapper build() {
if (readerFactory == null || generatorFactory == null) {
final JsonProvider provider = JsonProvider.provider();
final Map<String, Object> config = new HashMap<String, Object>();
if (bufferStrategy != null) {
config.put(JsonParserFactoryImpl.BUFFER_STRATEGY, bufferStrategy);
}
if (pretty) {
config.put(JsonGenerator.PRETTY_PRINTING, true);
}
if (generatorFactory == null) {
generatorFactory = provider.createGeneratorFactory(config);
}
config.remove(JsonGenerator.PRETTY_PRINTING); // doesnt mean anything anymore for reader
if (supportsComments) {
config.put(JsonParserFactoryImpl.SUPPORTS_COMMENTS, "true");
}
if (maxSize > 0) {
config.put(JsonParserFactoryImpl.MAX_STRING_LENGTH, maxSize);
}
if (bufferSize > 0) {
config.put(JsonParserFactoryImpl.BUFFER_LENGTH, bufferSize);
}
if (readerFactory == null) {
readerFactory = provider.createReaderFactory(config);
}
}
if (accessMode == null) {
if ("field".equalsIgnoreCase(accessModeName)) {
accessMode = new FieldAccessMode(supportConstructors, supportHiddenAccess);
} else if ("method".equalsIgnoreCase(accessModeName)) {
accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, true);
} else if ("strict-method".equalsIgnoreCase(accessModeName)) {
accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, false);
} else if ("both".equalsIgnoreCase(accessModeName) || accessModeName == null) {
accessMode = new FieldAndMethodAccessMode(supportConstructors, supportHiddenAccess, useGetterForCollections);
} else {
throw new IllegalArgumentException("Unsupported access mode: " + accessModeName);
}
}
if (!ignoredForFields.isEmpty()) {
if (BaseAccessMode.class.isInstance(accessMode)) {
final BaseAccessMode baseAccessMode = BaseAccessMode.class.cast(accessMode);
for (final Map.Entry<Class<?>, String[]> ignored : ignoredForFields.entrySet()) {
final String[] fields = ignored.getValue();
if (fields == null || fields.length == 0) {
baseAccessMode.getFieldsToRemove().remove(ignored.getKey());
} else {
baseAccessMode.getFieldsToRemove().put(ignored.getKey(), fields);
}
}
} else {
throw new IllegalStateException("AccessMode is not an BaseAccessMode");
}
}
if (primitiveConverters) {
adapters.put(new AdapterKey(Byte.class, String.class), new ConverterAdapter<Byte>(new CachedDelegateConverter<Byte>(new ByteConverter())));
adapters.put(new AdapterKey(Character.class, String.class), new ConverterAdapter<Character>(new CharacterConverter()));
adapters.put(new AdapterKey(Double.class, String.class), new ConverterAdapter<Double>(new DoubleConverter()));
adapters.put(new AdapterKey(Float.class, String.class), new ConverterAdapter<Float>(new FloatConverter()));
adapters.put(new AdapterKey(Integer.class, String.class), new ConverterAdapter<Integer>(new IntegerConverter()));
adapters.put(new AdapterKey(Long.class, String.class), new ConverterAdapter<Long>(new LongConverter()));
adapters.put(new AdapterKey(Short.class, String.class), new ConverterAdapter<Short>(new ShortConverter()));
adapters.put(new AdapterKey(Boolean.class, String.class), new ConverterAdapter<Boolean>(new CachedDelegateConverter<Boolean>(new BooleanConverter())));
adapters.put(new AdapterKey(byte.class, String.class), adapters.get(new AdapterKey(Byte.class, String.class)));
adapters.put(new AdapterKey(char.class, String.class), adapters.get(new AdapterKey(Character.class, String.class)));
adapters.put(new AdapterKey(double.class, String.class), adapters.get(new AdapterKey(Double.class, String.class)));
adapters.put(new AdapterKey(float.class, String.class), adapters.get(new AdapterKey(Float.class, String.class)));
adapters.put(new AdapterKey(int.class, String.class), adapters.get(new AdapterKey(Integer.class, String.class)));
adapters.put(new AdapterKey(long.class, String.class), adapters.get(new AdapterKey(Long.class, String.class)));
adapters.put(new AdapterKey(short.class, String.class), adapters.get(new AdapterKey(Short.class, String.class)));
adapters.put(new AdapterKey(boolean.class, String.class), adapters.get(new AdapterKey(Boolean.class, String.class)));
}
return new Mapper(
readerFactory, generatorFactory,
new MapperConfig(
adapters, objectConverterWriters, objectConverterReaders,
version, close,
skipNull, skipEmptyArray,
treatByteArrayAsBase64, treatByteArrayAsBase64URL, readAttributeBeforeWrite,
accessMode, encoding, attributeOrder, enforceQuoteString),
closeables);
}
public MapperBuilder addCloseable(final Closeable closeable) {
closeables.add(closeable);
return this;
}
public MapperBuilder setIgnoreFieldsForType(final Class<?> type, final String... fields) {
ignoredForFields.put(type, fields == null ? new String[0] : fields);
return this;
}
public MapperBuilder setSupportGetterForCollections(final boolean useGetterForCollections) {
this.useGetterForCollections = useGetterForCollections;
return this;
}
public MapperBuilder setSupportsComments(final boolean supportsComments) {
this.supportsComments = supportsComments;
return this;
}
public MapperBuilder setPretty(final boolean pretty) {
this.pretty = pretty;
return this;
}
public MapperBuilder setBufferSize(final int bufferSize) {
this.bufferSize = bufferSize;
return this;
}
public MapperBuilder setBufferStrategy(final String bufferStrategy) {
this.bufferStrategy = bufferStrategy;
return this;
}
public MapperBuilder setMaxSize(final int size) {
this.maxSize = size;
return this;
}
public MapperBuilder setAccessMode(final AccessMode mode) {
this.accessMode = mode;
return this;
}
public MapperBuilder setAccessModeName(final String mode) {
if (!"field".equalsIgnoreCase(mode) && !"method".equalsIgnoreCase(mode) &&
!"strict-method".equalsIgnoreCase(mode) && !"both".equalsIgnoreCase(mode)) {
throw new IllegalArgumentException("Mode " + mode + " unsupported");
}
this.accessModeName = mode;
return this;
}
public MapperBuilder setSupportHiddenAccess(final boolean supportHiddenAccess) {
this.supportHiddenAccess = supportHiddenAccess;
return this;
}
public MapperBuilder setAttributeOrder(final Comparator<String> attributeOrder) {
this.attributeOrder = attributeOrder;
return this;
}
public MapperBuilder setReaderFactory(final JsonReaderFactory readerFactory) {
this.readerFactory = readerFactory;
return this;
}
public MapperBuilder setGeneratorFactory(final JsonGeneratorFactory generatorFactory) {
this.generatorFactory = generatorFactory;
return this;
}
public MapperBuilder setDoCloseOnStreams(final boolean doCloseOnStreams) {
this.close = doCloseOnStreams;
return this;
}
@Deprecated // use addAdapter
public MapperBuilder addPropertyEditor(final Class<?> clazz, final Converter<?> converter) {
adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter));
return this;
}
@Deprecated // use addAdapter
public MapperBuilder addConverter(final Type clazz, final Converter<?> converter) {
adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter));
return this;
}
public MapperBuilder addAdapter(final Type from, final Type to, final Adapter<?, ?> adapter) {
adapters.put(new AdapterKey(from, to), adapter);
return this;
}
public MapperBuilder addAdapter(final Adapter<?, ?> converter) {
for (final Type gi : converter.getClass().getGenericInterfaces()) {
if (ParameterizedType.class.isInstance(gi) && Adapter.class == ParameterizedType.class.cast(gi).getRawType()) {
final Type[] args = ParameterizedType.class.cast(gi).getActualTypeArguments();
adapters.put(new AdapterKey(args[0], args[1]), converter);
return this;
}
}
throw new IllegalArgumentException("Can't find Adapter generics from " + converter + ", please use addAdapter(t1, t2, adapter) instead");
}
public MapperBuilder setVersion(final int version) {
this.version = version;
return this;
}
public MapperBuilder setSkipNull(final boolean skipNull) {
this.skipNull = skipNull;
return this;
}
public MapperBuilder setSkipEmptyArray(final boolean skipEmptyArray) {
this.skipEmptyArray = skipEmptyArray;
return this;
}
public MapperBuilder setTreatByteArrayAsBase64(final boolean treatByteArrayAsBase64) {
this.treatByteArrayAsBase64 = treatByteArrayAsBase64;
return this;
}
public MapperBuilder setTreatByteArrayAsBase64URL(final boolean treatByteArrayAsBase64URL) {
this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL;
return this;
}
public MapperBuilder setSupportConstructors(final boolean supportConstructors) {
this.supportConstructors = supportConstructors;
return this;
}
public MapperBuilder setEncoding(final String encoding) {
this.encoding = Charset.forName(encoding);
return this;
}
public MapperBuilder setReadAttributeBeforeWrite(final boolean readAttributeBeforeWrite) {
this.readAttributeBeforeWrite = readAttributeBeforeWrite;
return this;
}
public <T> MapperBuilder addObjectConverter(final Class<T> targetType, final MapperConverter objectConverter) {
if (ObjectConverter.Reader.class.isInstance(objectConverter)) {
this.objectConverterReaders.put(targetType, ObjectConverter.Reader.class.cast(objectConverter));
}
if (ObjectConverter.Writer.class.isInstance(objectConverter)) {
this.objectConverterWriters.put(targetType, ObjectConverter.Writer.class.cast(objectConverter));
}
return this;
}
public MapperBuilder setEnforceQuoteString() {
return setEnforceQuoteString(true);
}
public MapperBuilder setEnforceQuoteString(final boolean val) {
this.enforceQuoteString = val;
return this;
}
public MapperBuilder setPrimitiveConverters(final boolean val) {
this.primitiveConverters = val;
return this;
}
}