blob: 0f20779c53ea28e95f461ce08b45e50973de1ef8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Roman Grigoriadi
******************************************************************************/
package org.eclipse.persistence.json.bind.internal;
import org.eclipse.persistence.json.bind.internal.adapter.AbstractComponentBinding;
import org.eclipse.persistence.json.bind.internal.adapter.AdapterBinding;
import org.eclipse.persistence.json.bind.internal.adapter.ComponentBindings;
import org.eclipse.persistence.json.bind.internal.adapter.DeserializerBinding;
import org.eclipse.persistence.json.bind.internal.adapter.SerializerBinding;
import org.eclipse.persistence.json.bind.internal.serializer.JsonbDateFormatter;
import org.eclipse.persistence.json.bind.model.JsonBindingModel;
import org.eclipse.persistence.json.bind.model.PropertyModel;
import org.eclipse.persistence.json.bind.model.TypeWrapper;
import javax.json.bind.JsonbConfig;
import javax.json.bind.adapter.JsonbAdapter;
import javax.json.bind.serializer.JsonbDeserializer;
import javax.json.bind.serializer.JsonbSerializer;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Searches for a registered adapter or Serializer for a given type.
*
* @author Roman Grigoriadi
*/
public class ComponentMatcher {
private final JsonbContext jsonbContext;
/**
* Supplier for component binging.
* @param <T> component binding class
*/
private interface ComponentSupplier<T extends AbstractComponentBinding> {
T getComponent(ComponentBindings componentBindings);
}
private final ConcurrentMap<Type, ComponentBindings> userComponents;
/**
* Create component matcher.
* @param context mandatory
*/
ComponentMatcher(JsonbContext context) {
Objects.requireNonNull(context);
this.jsonbContext = context;
userComponents = new ConcurrentHashMap<>();
init();
}
/**
* Called during context creation, introspecting user components provided with JsonbConfig.
*/
void init() {
final JsonbSerializer<?>[] serializers = (JsonbSerializer<?>[])jsonbContext.getConfig().getProperty(JsonbConfig.SERIALIZERS).orElseGet(()->new JsonbSerializer<?>[]{});
for (JsonbSerializer serializer : serializers) {
introspectSerialzierBinding(serializer.getClass(), serializer);
}
final JsonbDeserializer<?>[] deserializers = (JsonbDeserializer<?>[])jsonbContext.getConfig().getProperty(JsonbConfig.DESERIALIZERS).orElseGet(()->new JsonbDeserializer<?>[]{});
for (JsonbDeserializer deserializer : deserializers) {
introspectDeserializerBinding(deserializer.getClass(), deserializer);
}
final JsonbAdapter<?, ?>[] adapters = (JsonbAdapter<?, ?>[]) jsonbContext.getConfig().getProperty(JsonbConfig.ADAPTERS).orElseGet(()->new JsonbAdapter<?, ?>[]{});
for (JsonbAdapter<?, ?> adapter : adapters) {
introspectAdapterBinding(adapter.getClass(), adapter);
}
}
private ComponentBindings getBindingInfo(Type type) {
return userComponents.compute(type, (type1, bindingInfo) -> bindingInfo != null ? bindingInfo : new ComponentBindings(type1));
}
private void addSeserializer(Type bindingType, SerializerBinding serializer) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getSerializer() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, serializer, bindings.getDeserializer(), bindings.getAdapterInfo());
});
}
private void addDeserializer(Type bindingType, DeserializerBinding deserializer) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getDeserializer() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, bindings.getSerializer(), deserializer, bindings.getAdapterInfo());
});
}
private void addApapter(Type bindingType, AdapterBinding adapter) {
userComponents.computeIfPresent(bindingType, (type, bindings) -> {
if (bindings.getAdapterInfo() != null) {
return bindings;
}
registerGeneric(bindingType);
return new ComponentBindings(bindingType, bindings.getSerializer(), bindings.getDeserializer(), adapter);
});
}
/**
* If type is not parametrized runtime component resolution doesn't has to happen.
*
* @param bindingType component binding type
* @return true if parameterized
*/
private void registerGeneric(Type bindingType) {
if (bindingType instanceof ParameterizedType && !jsonbContext.genericComponentsPresent()) {
jsonbContext.registerGenericComponentFlag();
}
}
/**
* Lookup serializer binding for a given property runtime type.
* @param propertyRuntimeType runtime type of a property
* @param propertyModel model of a property
* @return serializer optional
*/
@SuppressWarnings("unchecked")
public Optional<SerializerBinding<?>> getSerialzierBinding(Type propertyRuntimeType, JsonBindingModel propertyModel) {
if (propertyModel == null || propertyModel.getCustomization() == null || propertyModel.getCustomization().getSerializerBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getSerializer);
}
return getComponentBinding(propertyRuntimeType, propertyModel.getCustomization().getSerializerBinding());
}
/**
* Lookup deserializer binding for a given property runtime type.
* @param propertyRuntimeType runtime type of a property
* @param model model of a property
* @return serializer optional
*/
@SuppressWarnings("unchecked")
public Optional<DeserializerBinding<?>> getDeserialzierBinding(Type propertyRuntimeType, JsonBindingModel model) {
if (model == null || model.getCustomization().getDeserializerBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getDeserializer);
}
return getComponentBinding(propertyRuntimeType, model.getCustomization().getDeserializerBinding());
}
/**
* Get adapter from property model (if declared by annotation and runtime type matches),
* or return adapter searched by runtime type
*
* @param propertyRuntimeType runtime type not null
* @param model model nullable
* @return adapter info if present
*/
public Optional<AdapterBinding> getAdapterBinding(Type propertyRuntimeType, JsonBindingModel model) {
//TODO do we need type wrapper adapters at all? Make better check or remove.
if (model != null && model instanceof PropertyModel && ((PropertyModel) model).getClassModel().getType() == TypeWrapper.class) {
return Optional.empty();
}
if (model == null || model.getCustomization() == null || model.getCustomization().getAdapterBinding() == null) {
return searchComponentBinding(propertyRuntimeType, ComponentBindings::getAdapterInfo);
}
return getComponentBinding(propertyRuntimeType, model.getCustomization().getAdapterBinding());
}
private <T extends AbstractComponentBinding> Optional<T> getComponentBinding(Type propertyRuntimeType, T componentBinding) {
//need runtime check, ParameterizedType property may have generic adapter assigned which is not compatible
//for given runtime type
if (matches(propertyRuntimeType, componentBinding.getBindingType())) {
return Optional.of(componentBinding);
}
return Optional.empty();
}
private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(Type runtimeType, ComponentSupplier<T> supplier) {
for (ComponentBindings componentBindings : userComponents.values()) {
final T component = supplier.getComponent(componentBindings);
if (component != null && matches(runtimeType, componentBindings.getBindingType())) {
return Optional.of(component);
}
}
return Optional.empty();
}
private boolean matches(Type runtimeType, Type componentBindingType) {
if (componentBindingType.equals(runtimeType)) {
return true;
}
//don't try to runtime generic scan if not needed
if (!jsonbContext.genericComponentsPresent()) {
return false;
}
if (componentBindingType instanceof Class && runtimeType instanceof Class) {
//for polymorphic adapters
return ((Class<?>) componentBindingType).isAssignableFrom((Class) runtimeType);
}
return runtimeType instanceof ParameterizedType && componentBindingType instanceof ParameterizedType &&
ReflectionUtils.getRawType(runtimeType) == ReflectionUtils.getRawType(componentBindingType) &&
matchTypeArguments((ParameterizedType) runtimeType, (ParameterizedType) componentBindingType);
}
/**
* If runtimeType to adapt is a ParametrizedType, check all type args to match against adapter args.
*/
private boolean matchTypeArguments(ParameterizedType requiredType, ParameterizedType componentBound) {
final Type[] requiredTypeArguments = requiredType.getActualTypeArguments();
final Type[] adapterBoundTypeArguments = componentBound.getActualTypeArguments();
if (requiredTypeArguments.length != adapterBoundTypeArguments.length) {
return false;
}
for(int i = 0; i< requiredTypeArguments.length; i++) {
Type adapterTypeArgument = adapterBoundTypeArguments[i];
if (!requiredTypeArguments[i].equals(adapterTypeArgument)) {
return false;
}
}
return true;
}
/**
* Introspect adapter generic information and put resolved types into metadata wrapper.
*
* @param adapterClass class of an adapter
* @param instance adapter instance
* @return introspected info with resolved typevar types.
*/
AdapterBinding introspectAdapterBinding(Class<? extends JsonbAdapter> adapterClass, JsonbAdapter instance) {
final ParameterizedType adapterRuntimeType = ReflectionUtils.findParameterizedType(adapterClass, JsonbAdapter.class);
final Type[] adapterTypeArguments = adapterRuntimeType.getActualTypeArguments();
Type adaptFromType = resolveTypeArg(adapterTypeArguments[0], adapterClass);
Type adaptToType = resolveTypeArg(adapterTypeArguments[1], adapterClass);
final ComponentBindings componentBindings = getBindingInfo(adaptFromType);
if (componentBindings.getAdapterInfo() != null && componentBindings.getAdapterInfo().getAdapter().getClass().equals(adapterClass)) {
return componentBindings.getAdapterInfo();
}
JsonbAdapter newAdapter = instance != null ? instance : jsonbContext.getComponentInstanceCreator().getOrCreateComponent(adapterClass);
final AdapterBinding adapterInfo = new AdapterBinding(adaptFromType, adaptToType, newAdapter);
addApapter(adaptFromType, adapterInfo);
return adapterInfo;
}
/**
* If an instance of deserializerClass is present in context and is bound for same type, return that instance.
* Otherwise create new instance and set it to context.
*
* @param deserializerClass class of deserialzier
* @param instance instance to use if not cached already
* @return wrapper used in property models
*/
DeserializerBinding introspectDeserializerBinding(Class<? extends JsonbDeserializer> deserializerClass, JsonbDeserializer instance) {
final ParameterizedType deserializerRuntimeType = ReflectionUtils.findParameterizedType(deserializerClass, JsonbDeserializer.class);
Type deserBindingType = resolveTypeArg(deserializerRuntimeType.getActualTypeArguments()[0], deserializerClass.getClass());
final ComponentBindings componentBindings = getBindingInfo(deserBindingType);
if (componentBindings.getDeserializer() != null && componentBindings.getDeserializer().getClass().equals(deserializerClass)) {
return componentBindings.getDeserializer();
} else {
JsonbDeserializer deserializer = instance != null ? instance : jsonbContext.getComponentInstanceCreator()
.getOrCreateComponent(deserializerClass);
final DeserializerBinding deserializerBinding = new DeserializerBinding(deserBindingType, deserializer);
addDeserializer(deserBindingType, deserializerBinding);
return deserializerBinding;
}
}
/**
* If an instance of serializerClass is present in context and is bound for same type, return that instance.
* Otherwise create new instance and set it to context.
*
* @param serializerClass class of deserialzier
* @param instance instance to use if not cached
* @return wrapper used in property models
*/
SerializerBinding introspectSerialzierBinding(Class<? extends JsonbSerializer> serializerClass, JsonbSerializer instance) {
final ParameterizedType serializerRuntimeType = ReflectionUtils.findParameterizedType(serializerClass, JsonbSerializer.class);
Type serBindingType = resolveTypeArg(serializerRuntimeType.getActualTypeArguments()[0], serializerClass.getClass());
final ComponentBindings componentBindings = getBindingInfo(serBindingType);
if (componentBindings.getSerializer() != null && componentBindings.getSerializer().getClass().equals(serializerClass)) {
return componentBindings.getSerializer();
} else {
JsonbSerializer serializer = instance != null ? instance : jsonbContext.getComponentInstanceCreator()
.getOrCreateComponent(serializerClass);
final SerializerBinding serializerBinding = new SerializerBinding(serBindingType, serializer);
addSeserializer(serBindingType, serializerBinding);
return serializerBinding;
}
}
private Type resolveTypeArg(Type adapterTypeArg, Type adapterType) {
if(adapterTypeArg instanceof ParameterizedType) {
return ReflectionUtils.resolveTypeArguments((ParameterizedType) adapterTypeArg, adapterType);
} else if (adapterTypeArg instanceof TypeVariable) {
return ReflectionUtils.resolveItemVariableType(new RuntimeTypeHolder(null, adapterType), (TypeVariable<?>) adapterTypeArg);
} else {
return adapterTypeArg;
}
}
/**
* Resolves date formatter either from model or global config.
* @param model model of processed value (field or collection item)
* @return formatter
*/
public JsonbDateFormatter getDateFormatter(JsonBindingModel model) {
if (model == null || model.getCustomization() == null || model.getCustomization().getDateTimeFormatter() == null) {
return jsonbContext.getConfigDateFormatter();
}
return model.getCustomization().getDateTimeFormatter();
}
}