All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.commons.util.canonical.OrderingBeanSerializerModifier Maven / Gradle / Ivy

The newest version!
package org.conqat.engine.commons.util.canonical;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.checkerframework.checker.nullness.qual.NonNull;

import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.fasterxml.jackson.databind.util.Converter;

/**
 * Implements the behavior described in {@link CanonicalJson}.
 * 

* Implements the {@link CanonicalJson#ordering() ordering} by replacing the respective serializer * with one that orders the elements first (see {@link SortedCollection} and * {@link SortedCollectionConverter}). */ /* package */ class OrderingBeanSerializerModifier extends BeanSerializerModifier { @Override public List changeProperties(SerializationConfig config, BeanDescription beanDesc, List beanProperties) { for (BeanPropertyWriter beanProperty : beanProperties) { CanonicalJson canonicalJson = beanProperty.getAnnotation(CanonicalJson.class); if (canonicalJson != null) { applyCanonicalJson(config, beanProperty, canonicalJson); } } return beanProperties; } private static void applyCanonicalJson(SerializationConfig config, BeanPropertyWriter beanProperty, CanonicalJson canonicalJson) { if (!Collection.class.isAssignableFrom(beanProperty.getType().getRawClass())) { // For now only support Collections throw new IllegalArgumentException(String.format("Type %s is not supported for ordered serialization", beanProperty.getType().getRawClass())); } Comparator comparator = buildComparator(config, beanProperty, canonicalJson.ordering()); StdDelegatingSerializer serializer = new StdDelegatingSerializer( new SortedCollectionConverter<>(comparator, beanProperty.getType())); beanProperty.assignSerializer(serializer); } private static Comparator buildComparator(SerializationConfig config, BeanPropertyWriter beanProperty, CanonicalJson.OrderBy orderBy) { boolean hasComparator = orderBy.comparator() != CanonicalJson.OrderBy.NoComparator.class; boolean hasProperties = orderBy.properties().length > 0; if (hasComparator && hasProperties) { throw new IllegalArgumentException(String.format( "%s annotation on %s has properties (%s) and a comparator (%s) defined, but only one can be present", CanonicalJson.OrderBy.class.getSimpleName(), beanProperty.getMember(), Arrays.toString(orderBy.properties()), orderBy.comparator())); } else if (!hasComparator && !hasProperties) { throw new IllegalArgumentException( String.format("%s annotation on %s has neither properties nor a comparator defined", CanonicalJson.OrderBy.class.getSimpleName(), beanProperty.getMember())); } if (hasComparator) { return instantiateComparatorByClass(config, orderBy.comparator()); } else { return instantiateComparatorByProperties(config, beanProperty.getType().getContentType(), orderBy.properties()); } } @SuppressWarnings({ "rawtypes", "unchecked" }) private static Comparator instantiateComparatorByProperties(SerializationConfig config, JavaType contentType, String[] properties) { Comparator result = EmptyComparator.getInstance(); Map propertiesByName = config.introspect(contentType).findProperties().stream() .collect(Collectors.toMap(BeanPropertyDefinition::getName, Function.identity())); for (String propertyName : properties) { BeanPropertyDefinition propertyDefinition = propertiesByName.get(propertyName); if (propertyDefinition == null) { throw new IllegalArgumentException( String.format("Property \"%s\" was not found on %s", propertyName, contentType.getRawClass())); } if (!Comparable.class.isAssignableFrom(propertyDefinition.getRawPrimaryType()) // primitives are also comparable (by their wrapper) && !propertyDefinition.getRawPrimaryType().isPrimitive()) { throw new IllegalArgumentException( String.format("Property \"%s\" is not of type Comparable (detected type: %s)", propertyName, propertyDefinition.getRawPrimaryType())); } AnnotatedMember accessor = propertyDefinition.getAccessor(); if (config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { accessor.fixAccess(config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } result = result.thenComparing(accessor::getValue, Comparator.nullsFirst(Comparator.naturalOrder())); } return result; } @NonNull @SuppressWarnings({ "unchecked", "rawtypes" }) private static Comparator instantiateComparatorByClass(SerializationConfig config, Class comparatorClass) { try { Constructor constructor = comparatorClass.getDeclaredConstructor(); if (Modifier.isPrivate(constructor.getModifiers())) { // Do not allow any private constructor, as the class expects this to be // not invoked externally throw new IllegalArgumentException( String.format("Comparator %s only has a private no-args constructor and cannot be instantiated", comparatorClass)); } if (config.isEnabled(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)) { // Still overwrite accessibility for package/protected constructors or private // inner class with public constructor ClassUtil.checkAndFixAccess(constructor, config.isEnabled(MapperFeature.OVERRIDE_PUBLIC_ACCESS_MODIFIERS)); } return constructor.newInstance(); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new IllegalArgumentException(String.format("Unable to instantiate %s", comparatorClass), e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( String.format("Comparator %s has no invocable no-args constructor", comparatorClass), e); } } /** * Simple wrapper around any collection, that provides a stable sorted JSON representation. */ private static class SortedCollection { private final Collection source; private final Comparator comparator; public SortedCollection(Collection source, Comparator comparator) { this.source = source; this.comparator = comparator; } /** * @return Sorted list according to the #comparator */ @JsonValue public List toJson() { return source.stream().sorted(comparator).collect(Collectors.toList()); } } /** * {@link Converter} from any {@link Collection} to a {@link SortedCollection}. */ private static class SortedCollectionConverter implements Converter, SortedCollection> { private final Comparator comparator; private final JavaType inputType; private SortedCollectionConverter(Comparator comparator, JavaType inputType) { this.inputType = inputType; this.comparator = comparator; } @Override public JavaType getInputType(TypeFactory typeFactory) { return inputType; } @Override public JavaType getOutputType(TypeFactory typeFactory) { return typeFactory.constructCollectionLikeType(SortedCollection.class, inputType.getContentType()); } @Override public SortedCollection convert(Collection value) { return new SortedCollection<>(value, comparator); } } /** * "Empty" implementation of {@link Comparator}, that orders all elements the same. */ private static class EmptyComparator implements Comparator { @SuppressWarnings("rawtypes") private static final EmptyComparator INSTANCE = new EmptyComparator(); @SuppressWarnings("unchecked") public static EmptyComparator getInstance() { return INSTANCE; } @Override public Comparator reversed() { // nothing to do, as everything is the same return this; } @Override @SuppressWarnings("unchecked") public Comparator thenComparing(Comparator other) { // Can directly return other, as this one will always delegate to it anyway. return (Comparator) other; } @Override public int compare(T o1, T o2) { return 0; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy