JsonAdapters.java
/*
* Copyright © 2024 Indiana University
* All rights reserved.
*
* BSD 3-Clause License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package iu.client;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.WeakHashMap;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import edu.iu.IuException;
import edu.iu.client.IuJsonAdapter;
/**
* Provides standard {@link IuJsonAdapter} instances.
*/
public final class JsonAdapters {
private static final Map<Class<?>, Class<?>> ARRAY_TYPES = new WeakHashMap<>();
/**
* {@link IuJsonAdapter} factory method.
*
* @param type Java type
* @param valueAdapter value type adapter
* @return {@link JsonAdapter}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static IuJsonAdapter adapt(Type type, Function<Class<?>, IuJsonAdapter<?>> valueAdapter) {
Class erased = erase(type);
if (erased == Object.class)
return BasicJsonAdapter.INSTANCE;
if (erased == Boolean.class)
return BooleanJsonAdapter.INSTANCE;
if (erased == boolean.class)
return BooleanJsonAdapter.PRIMITIVE;
if (erased == BigDecimal.class //
|| erased == Number.class)
return NumberAdapter.BIG_DECIMAL;
if (erased == BigInteger.class)
return NumberAdapter.BIG_INTEGER;
if (erased == Byte.class)
return NumberAdapter.BYTE;
if (erased == byte.class)
return NumberAdapter.BYTE_PRIMITIVE;
if (erased == Double.class)
return NumberAdapter.DOUBLE;
if (erased == double.class)
return NumberAdapter.DOUBLE_PRIMITIVE;
if (erased == Float.class)
return NumberAdapter.FLOAT;
if (erased == float.class)
return NumberAdapter.FLOAT_PRIMITIVE;
if (erased == Long.class)
return NumberAdapter.LONG;
if (erased == long.class)
return NumberAdapter.LONG_PRIMITIVE;
if (erased == Integer.class)
return NumberAdapter.INT;
if (erased == int.class)
return NumberAdapter.INT_PRIMITIVE;
if (erased == Short.class)
return NumberAdapter.SHORT;
if (erased == short.class)
return NumberAdapter.SHORT_PRIMITIVE;
if (erased == CharSequence.class //
|| erased == String.class)
return TextJsonAdapter.INSTANCE;
if (erased == byte[].class)
return BinaryJsonAdapter.INSTANCE;
if (erased == Calendar.class)
return CalendarJsonAdapter.INSTANCE;
if (erased == Date.class)
return DateJsonAdapter.INSTANCE;
if (erased == Duration.class)
return ParsingJsonAdapter.of(Duration.class, Duration::parse);
if (erased == Instant.class)
return ParsingJsonAdapter.of(Instant.class, Instant::parse);
if (erased == LocalDate.class)
return ParsingJsonAdapter.of(LocalDate.class, LocalDate::parse);
if (erased == LocalTime.class)
return ParsingJsonAdapter.of(LocalTime.class, LocalTime::parse);
if (erased == LocalDateTime.class)
return ParsingJsonAdapter.of(LocalDateTime.class, LocalDateTime::parse);
if (erased == OffsetDateTime.class)
return ParsingJsonAdapter.of(OffsetDateTime.class, OffsetDateTime::parse);
if (erased == OffsetTime.class)
return ParsingJsonAdapter.of(OffsetTime.class, OffsetTime::parse);
if (erased == Pattern.class)
return ParsingJsonAdapter.of(Pattern.class, Pattern::compile);
if (erased == Period.class)
return ParsingJsonAdapter.of(Period.class, Period::parse);
if (erased == SimpleTimeZone.class)
return TimeZoneJsonAdapter.INSTANCE;
if (erased == TimeZone.class)
return TimeZoneJsonAdapter.INSTANCE;
if (erased == ZonedDateTime.class)
return ParsingJsonAdapter.of(ZonedDateTime.class, ZonedDateTime::parse);
if (erased == ZoneId.class)
return ParsingJsonAdapter.of(ZoneId.class, ZoneId::of);
if (erased == ZoneOffset.class)
return ParsingJsonAdapter.of(ZoneOffset.class, ZoneOffset::of);
if (erased == URI.class)
return ParsingJsonAdapter.of(URI.class, URI::create);
if (erased == URL.class)
return ParsingJsonAdapter.of(URL.class, a -> IuException.unchecked(() -> new URL(a)));
if (erased.isEnum())
return ParsingJsonAdapter.of(erased, v -> Enum.valueOf(erased, v));
if (erased == Optional.class)
if (valueAdapter != null)
return new OptionalJsonAdapter(valueAdapter.apply(item(type)));
else if (type instanceof ParameterizedType)
return new OptionalJsonAdapter(IuJsonAdapter.of(item(type)));
else
return OptionalJsonAdapter.INSTANCE;
if (erased.isArray()) {
final var item = item(type);
final IntFunction factory = n -> Array.newInstance(item, n);
final IuJsonAdapter itemAdapter;
if (valueAdapter != null)
itemAdapter = valueAdapter.apply(item);
else
itemAdapter = IuJsonAdapter.of(item);
return new ArrayAdapter(itemAdapter, factory);
}
if (Iterable.class.isAssignableFrom(erased) //
|| erased == Enumeration.class //
|| erased == Iterator.class //
|| erased == Stream.class) {
final IuJsonAdapter itemAdapter;
if (valueAdapter != null)
itemAdapter = valueAdapter.apply(item(type));
else if (type instanceof ParameterizedType)
itemAdapter = IuJsonAdapter.of(item(type));
else
itemAdapter = BasicJsonAdapter.INSTANCE;
if (erased == Iterable.class)
return new IterableAdapter(itemAdapter);
if (erased == Collection.class //
|| erased == Queue.class //
|| erased == Deque.class //
|| erased == ArrayDeque.class)
return new CollectionAdapter(itemAdapter, ArrayDeque::new);
if (erased == List.class //
|| erased == ArrayList.class)
return new CollectionAdapter(itemAdapter, ArrayList::new);
if (erased == Set.class //
|| erased == LinkedHashSet.class)
return new CollectionAdapter(itemAdapter, LinkedHashSet::new);
if (erased == SortedSet.class //
|| erased == NavigableSet.class //
|| erased == TreeSet.class)
return new CollectionAdapter(itemAdapter, TreeSet::new);
if (erased == HashSet.class)
return new CollectionAdapter(itemAdapter, HashSet::new);
if (erased == Enumeration.class)
return new EnumerationAdapter(itemAdapter);
if (erased == Iterator.class)
return new IteratorAdapter(itemAdapter);
if (erased == Stream.class)
return new StreamAdapter(itemAdapter);
}
if (Map.class.isAssignableFrom(erased)) {
if (valueAdapter == null)
if (type instanceof ParameterizedType)
valueAdapter = IuJsonAdapter::of;
else
valueAdapter = a -> BasicJsonAdapter.INSTANCE;
final IuJsonAdapter keyAdapter ;
if (type instanceof ParameterizedType)
keyAdapter = valueAdapter.apply(erase(((ParameterizedType) type).getActualTypeArguments()[0]));
else
keyAdapter = BasicJsonAdapter.INSTANCE;
if (erased == Map.class //
|| erased == LinkedHashMap.class)
return new JsonObjectAdapter(keyAdapter, valueAdapter.apply(item(type)), LinkedHashMap::new);
if (erased == HashMap.class)
return new JsonObjectAdapter(keyAdapter, valueAdapter.apply(item(type)), HashMap::new);
if (erased == SortedMap.class //
|| erased == NavigableMap.class //
|| erased == TreeMap.class)
return new JsonObjectAdapter(keyAdapter, valueAdapter.apply(item(type)), TreeMap::new);
if (erased == Properties.class)
return new JsonObjectAdapter(keyAdapter, valueAdapter.apply(item(type)), Properties::new);
}
throw new UnsupportedOperationException("Unsupported for JSON conversion: " + type);
}
private static Class<?> erase(Type type) {
if (type instanceof Class)
return (Class<?>) type;
else if (type instanceof GenericArrayType) {
final var component = erase(((GenericArrayType) type).getGenericComponentType());
var array = ARRAY_TYPES.get(component);
if (array == null) {
array = Array.newInstance(component, 0).getClass();
synchronized (ARRAY_TYPES) {
ARRAY_TYPES.put(component, array);
}
}
return array;
} else if (type instanceof ParameterizedType)
return erase(((ParameterizedType) type).getRawType());
else if (type instanceof TypeVariable)
return erase(((TypeVariable<?>) type).getBounds()[0]);
else // if (type instanceof WildcardType)
return erase(((WildcardType) type).getUpperBounds()[0]);
}
private static Class<?> item(Type type) {
if (type instanceof Class) {
return ((Class<?>) type).getComponentType();
} else if (type instanceof GenericArrayType)
return erase(((GenericArrayType) type).getGenericComponentType());
else {
// assumes erase() was invoked and returned a supported type first
final var p = (ParameterizedType) type;
final var raw = erase(p);
if (Map.class.isAssignableFrom(raw))
return erase(p.getActualTypeArguments()[1]);
else
return erase(p.getActualTypeArguments()[0]);
}
}
private JsonAdapters() {
}
}