| /* |
| * 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.mapper.access.AccessMode; |
| import org.apache.johnzon.mapper.converter.EnumConverter; |
| import org.apache.johnzon.mapper.internal.AdapterKey; |
| import org.apache.johnzon.mapper.internal.ConverterAdapter; |
| import org.apache.johnzon.mapper.map.LazyConverterMap; |
| |
| import javax.json.JsonValue; |
| import java.lang.reflect.Type; |
| import java.nio.charset.Charset; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.function.Function; |
| import java.util.function.Predicate; |
| |
| import static java.util.stream.Collectors.toList; |
| |
| /** |
| * Contains internal configuration for all the mapper stuff. |
| * It needs to be immutable and 100% runtime oriented. |
| */ |
| public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { |
| |
| private static final ObjectConverter.Codec NO_CONVERTER = new ObjectConverter.Codec() { |
| @Override |
| public void writeJson(Object instance, MappingGenerator jsonbGenerator) { |
| // just a dummy |
| } |
| |
| @Override |
| public Object fromJson(JsonValue jsonObject, Type targetType, MappingParser parser) { |
| return null; |
| } |
| }; |
| |
| private final int version; |
| private final boolean useJsRange; |
| private final boolean close; |
| private final boolean skipNull; |
| private final boolean skipEmptyArray; |
| private final boolean treatByteArrayAsBase64; |
| private final boolean treatByteArrayAsBase64URL; |
| private final boolean readAttributeBeforeWrite; |
| private final boolean supportEnumMapDeserialization; // for tck |
| private final AccessMode accessMode; |
| private final Charset encoding; |
| private final LazyConverterMap adapters; |
| private final ConcurrentMap<Adapter<?, ?>, AdapterKey> reverseAdapters; |
| |
| private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters; |
| private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders; |
| private final Comparator<String> attributeOrder; |
| private final boolean enforceQuoteString; |
| private final boolean failOnUnknown; |
| private final SerializeValueFilter serializeValueFilter; |
| private final boolean useBigDecimalForFloats; |
| private final Boolean deduplicateObjects; |
| private final Map<Class<?>, Class<?>> interfaceImplementationMapping; |
| private final boolean useBigDecimalForObjectNumbers; |
| |
| private final Function<String, Class<?>> typeLoader; |
| private final Function<Class<?>, String> discriminatorMapper; |
| private final Predicate<Class<?>> serializationPredicate; |
| private final Predicate<Class<?>> deserializationPredicate; |
| private final String discriminator; |
| |
| private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriterCache; |
| private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaderCache; |
| |
| private final Collection<Type> noParserAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); |
| private final Collection<Type> noGeneratorAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); |
| |
| //disable checkstyle for 10+ parameters |
| //CHECKSTYLE:OFF |
| public MapperConfig(final LazyConverterMap adapters, |
| final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters, |
| final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders, |
| final int version, final boolean close, |
| final boolean skipNull, final boolean skipEmptyArray, |
| final boolean treatByteArrayAsBase64, final boolean treatByteArrayAsBase64URL, |
| final boolean readAttributeBeforeWrite, |
| final AccessMode accessMode, final Charset encoding, |
| final Comparator<String> attributeOrder, |
| final boolean enforceQuoteString, final boolean failOnUnknown, |
| final SerializeValueFilter serializeValueFilter, |
| final boolean useBigDecimalForFloats, |
| final Boolean deduplicateObjects, |
| final Map<Class<?>, Class<?>> interfaceImplementationMapping, |
| final boolean useJsRange, |
| final boolean useBigDecimalForObjectNumbers, |
| final boolean supportEnumMapDeserialization, |
| final Function<String, Class<?>> typeLoader, |
| final Function<Class<?>, String> discriminatorMapper, |
| final String discriminator, |
| final Predicate<Class<?>> deserializationPredicate, |
| final Predicate<Class<?>> serializationPredicate) { |
| //CHECKSTYLE:ON |
| this.objectConverterWriters = objectConverterWriters; |
| this.objectConverterReaders = objectConverterReaders; |
| this.version = version; |
| this.close = close; |
| this.skipNull = skipNull; |
| this.skipEmptyArray = skipEmptyArray; |
| this.treatByteArrayAsBase64 = treatByteArrayAsBase64; |
| this.treatByteArrayAsBase64URL = treatByteArrayAsBase64URL; |
| this.readAttributeBeforeWrite = readAttributeBeforeWrite; |
| this.accessMode = accessMode; |
| this.encoding = encoding; |
| this.useJsRange = useJsRange; |
| this.useBigDecimalForObjectNumbers = useBigDecimalForObjectNumbers; |
| this.supportEnumMapDeserialization = supportEnumMapDeserialization; |
| this.typeLoader = typeLoader; |
| this.discriminatorMapper = discriminatorMapper; |
| this.serializationPredicate = serializationPredicate; |
| this.deserializationPredicate = deserializationPredicate; |
| this.discriminator = discriminator; |
| |
| // handle Adapters |
| this.adapters = adapters; |
| this.reverseAdapters = new ConcurrentHashMap<>(adapters.size()); |
| adapters.forEach((k, v) -> this.reverseAdapters.put(v, k)); |
| |
| |
| this.attributeOrder = attributeOrder; |
| this.enforceQuoteString = enforceQuoteString; |
| this.failOnUnknown = failOnUnknown; |
| this.serializeValueFilter = serializeValueFilter == null ? (name, value) -> false : serializeValueFilter; |
| this.interfaceImplementationMapping = interfaceImplementationMapping; |
| |
| this.objectConverterWriterCache = new HashMap<>(objectConverterWriters.size()); |
| this.objectConverterReaderCache = new HashMap<>(objectConverterReaders.size()); |
| this.useBigDecimalForFloats = useBigDecimalForFloats; |
| this.deduplicateObjects = deduplicateObjects; |
| } |
| |
| public Collection<Type> getNoParserAdapterTypes() { |
| return noParserAdapterTypes; |
| } |
| |
| public Collection<Type> getNoGeneratorAdapterTypes() { |
| return noGeneratorAdapterTypes; |
| } |
| |
| public Function<String, Class<?>> getTypeLoader() { |
| return typeLoader; |
| } |
| |
| public Function<Class<?>, String> getDiscriminatorMapper() { |
| return discriminatorMapper; |
| } |
| |
| public Predicate<Class<?>> getDeserializationPredicate() { |
| return deserializationPredicate; |
| } |
| |
| public Predicate<Class<?>> getSerializationPredicate() { |
| return serializationPredicate; |
| } |
| |
| public String getDiscriminator() { |
| return discriminator; |
| } |
| |
| public boolean isUseBigDecimalForObjectNumbers() { |
| return useBigDecimalForObjectNumbers; |
| } |
| |
| public boolean isUseJsRange() { |
| return useJsRange; |
| } |
| |
| public Map<Class<?>, Class<?>> getInterfaceImplementationMapping() { |
| return interfaceImplementationMapping; |
| } |
| |
| public SerializeValueFilter getSerializeValueFilter() { |
| return serializeValueFilter; |
| } |
| |
| public Adapter findAdapter(final Type aClass) { |
| if (getNoGeneratorAdapterTypes().contains(aClass)) { // avoid to create a key for nothing |
| return null; |
| } |
| |
| final Adapter<?, ?> converter = adapters.get(new AdapterKey(aClass, String.class, true)); |
| if (converter != null) { |
| return converter; |
| } |
| /* could be an option but doesnt fit well our old converters |
| final Adapter<?, ?> reverseConverter = adapters.get(new AdapterKey(String.class, aClass)); |
| if (reverseConverter != null) { |
| return new ReversedAdapter<>(reverseConverter); |
| } |
| */ |
| if (Class.class.isInstance(aClass)) { |
| final Class<?> clazz = Class.class.cast(aClass); |
| if (Enum.class.isAssignableFrom(clazz)) { |
| final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz), clazz); |
| adapters.putIfAbsent(new AdapterKey(String.class, aClass), enumConverter); |
| return enumConverter; |
| } |
| } |
| final List<AdapterKey> matched = adapters.adapterKeys().stream() |
| .filter(k -> k.isAssignableFrom(aClass)) |
| .collect(toList()); |
| if (matched.size() == 1) { |
| final Adapter<?, ?> adapter = adapters.get(matched.iterator().next()); |
| if (TypeAwareAdapter.class.isInstance(adapter)) { |
| adapters.put(new AdapterKey(aClass, TypeAwareAdapter.class.cast(adapter).getTo()), adapter); |
| } |
| return adapter; |
| } |
| getNoGeneratorAdapterTypes().add(aClass); |
| return null; |
| } |
| |
| /** |
| * Search for an {@link ObjectConverter} for the given class. |
| * |
| * If no {@link ObjectConverter} was found for the specific class, |
| * the whole type hierarchy will be scanned for a matching {@link ObjectConverter}. |
| * |
| * In case the given class implements more than on interfaces and for at least two |
| * we have configured an {@link ObjectConverter} the {@link ObjectConverter} for the |
| * first interface we get will be taken. |
| * |
| * @param clazz the {@link Class} |
| * |
| * @return the found {@link ObjectConverter} or {@code null} if no {@link ObjectConverter} has been found |
| * |
| * @throws IllegalArgumentException if {@code clazz} is {@code null} |
| */ |
| public ObjectConverter.Reader findObjectConverterReader(Class clazz) { |
| return findObjectConverter(clazz, objectConverterReaders, objectConverterReaderCache); |
| } |
| public ObjectConverter.Writer findObjectConverterWriter(Class clazz) { |
| return findObjectConverter(clazz, objectConverterWriters, objectConverterWriterCache); |
| } |
| |
| private <T> T findObjectConverter(final Class clazz, |
| final Map<Class<?>, T> from, |
| final Map<Class<?>, T> cache) { |
| if (clazz == null) { |
| throw new IllegalArgumentException("clazz must not be null"); |
| } |
| |
| // first lets look in our cache |
| T converter = cache.get(clazz); |
| if (converter != null && converter != NO_CONVERTER) { |
| return converter; |
| } |
| |
| // if we have found a dummy, we return null |
| if (converter == NO_CONVERTER) { |
| return null; |
| } |
| |
| // we get called the first time for this class |
| // lets search... |
| |
| Map<Class<?>, T> matchingConverters = new HashMap<Class<?>, T>(); |
| |
| for (Map.Entry<Class<?>, T> entry : from.entrySet()) { |
| |
| if (clazz == entry.getKey()) { |
| converter = entry.getValue(); |
| break; |
| } |
| |
| if (entry.getKey().isAssignableFrom(clazz)) { |
| matchingConverters.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| if (converter != null) { |
| cache.put(clazz, converter); |
| return converter; |
| } |
| |
| if (matchingConverters.isEmpty()) { |
| cache.put(clazz, (T) NO_CONVERTER); |
| return null; |
| } |
| |
| // search the most significant |
| Class toProcess = clazz; |
| while (toProcess != null && converter == null) { |
| |
| converter = matchingConverters.get(toProcess); |
| if (converter != null) { |
| break; |
| } |
| |
| Class[] interfaces = toProcess.getInterfaces(); |
| if (interfaces.length > 0) { |
| for (Class interfaceToSearch : interfaces) { |
| |
| converter = matchingConverters.get(interfaceToSearch); |
| if (converter != null) { |
| break; |
| } |
| } |
| } |
| |
| if (converter == null && toProcess.isInterface()) { |
| converter = matchingConverters.get(Object.class); |
| break; |
| } |
| |
| toProcess = toProcess.getSuperclass(); |
| } |
| |
| if (converter == null) { |
| cache.put(clazz, (T) NO_CONVERTER); |
| } else { |
| cache.put(clazz, converter); |
| } |
| |
| return converter; |
| } |
| |
| public boolean isFailOnUnknown() { |
| return failOnUnknown; |
| } |
| |
| public int getVersion() { |
| return version; |
| } |
| |
| public boolean isClose() { |
| return close; |
| } |
| |
| public boolean isSkipNull() { |
| return skipNull; |
| } |
| |
| public boolean isSkipEmptyArray() { |
| return skipEmptyArray; |
| } |
| |
| public boolean isTreatByteArrayAsBase64() { |
| return treatByteArrayAsBase64; |
| } |
| |
| public boolean isTreatByteArrayAsBase64URL() { |
| return treatByteArrayAsBase64URL; |
| } |
| |
| public boolean isReadAttributeBeforeWrite() { |
| return readAttributeBeforeWrite; |
| } |
| |
| public AccessMode getAccessMode() { |
| return accessMode; |
| } |
| |
| public Charset getEncoding() { |
| return encoding; |
| } |
| |
| public LazyConverterMap getAdapters() { |
| return adapters; |
| } |
| |
| public ConcurrentMap<Adapter<?, ?>, AdapterKey> getReverseAdapters() { |
| return reverseAdapters; |
| } |
| |
| public Map<Class<?>, ObjectConverter.Writer<?>> getObjectConverterWriters() { |
| return objectConverterWriters; |
| } |
| |
| public Map<Class<?>, ObjectConverter.Reader<?>> getObjectConverterReaders() { |
| return objectConverterReaders; |
| } |
| |
| public Comparator<String> getAttributeOrder() { |
| return attributeOrder; |
| } |
| |
| public boolean isEnforceQuoteString() { |
| return enforceQuoteString; |
| } |
| |
| public boolean isUseBigDecimalForFloats() { |
| return useBigDecimalForFloats; |
| } |
| |
| public Boolean isDeduplicateObjects() { |
| return deduplicateObjects; |
| } |
| |
| public boolean isSupportEnumContainerDeserialization() { |
| return supportEnumMapDeserialization; |
| } |
| } |