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

com.github.robtimus.obfuscation.jackson.databind.ObfuscatedBeanDeserializerModifier Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * ObfuscatedBeanDeserializerModifier.java
 * Copyright 2020 Rob Spoor
 *
 * Licensed 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 com.github.robtimus.obfuscation.jackson.databind;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.github.robtimus.obfuscation.Obfuscated;
import com.github.robtimus.obfuscation.Obfuscator;
import com.github.robtimus.obfuscation.annotation.CharacterRepresentationProvider;
import com.github.robtimus.obfuscation.annotation.ObjectFactory;

final class ObfuscatedBeanDeserializerModifier extends BeanDeserializerModifier {

    private static final ObjectFactory CAN_OVERRIDE_ACCESS_MODIFIERS = ObfuscatedBeanDeserializerModifier::createInstanceWithCanFixAccess;
    private static final ObjectFactory CANNOT_OVERRIDE_ACCESS_MODIFIERS = ObfuscatedBeanDeserializerModifier::createInstanceWithoutCanFixAccess;
    private static final Function FACTORY_MAPPER = config -> config.canOverrideAccessModifiers()
            ? CAN_OVERRIDE_ACCESS_MODIFIERS
            : CANNOT_OVERRIDE_ACCESS_MODIFIERS;

    private final Function factoryMapper;
    private final Obfuscator defaultObfuscator;

    private final Map, Obfuscator> classObfuscators;
    private final Map, Obfuscator> interfaceObfuscators;

    private final Map, CharacterRepresentationProvider> classCharacterRepresentationProviders;
    private final Map, CharacterRepresentationProvider> interfaceCharacterRepresentationProviders;

    private final boolean requireObfuscatorAnnotation;

    ObfuscatedBeanDeserializerModifier(ObjectFactory objectFactory,
            Obfuscator defaultObfuscator,
            Map, Obfuscator> classObfuscators,
            Map, Obfuscator> interfaceObfuscators,
            Map, CharacterRepresentationProvider> classCharacterRepresentationProviders,
            Map, CharacterRepresentationProvider> interfaceCharacterRepresentationProviders,
            boolean requireObfuscatorAnnotation) {

        this.factoryMapper = objectFactory != null ? config -> objectFactory : FACTORY_MAPPER;
        this.defaultObfuscator = defaultObfuscator;

        this.classObfuscators = classObfuscators;
        this.interfaceObfuscators = interfaceObfuscators;
        this.classCharacterRepresentationProviders = classCharacterRepresentationProviders;
        this.interfaceCharacterRepresentationProviders = interfaceCharacterRepresentationProviders;

        this.requireObfuscatorAnnotation = requireObfuscatorAnnotation;
    }

    private static  T createInstanceWithCanFixAccess(Class type) {
        return ClassUtil.createInstance(type, true);
    }

    private static  T createInstanceWithoutCanFixAccess(Class type) {
        return ClassUtil.createInstance(type, false);
    }

    @Override
    public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc, BeanDeserializerBuilder builder) {
        BeanDeserializerBuilder updatedBuilder = super.updateBuilder(config, beanDesc, builder);
        Map propertyReplacements = new LinkedHashMap<>();

        ValueInstantiator valueInstantiator = updatedBuilder.getValueInstantiator();
        SettableBeanProperty[] constructorArguments = valueInstantiator.getFromObjectArguments(config);

        for (Iterator i = updatedBuilder.getProperties(); i.hasNext(); ) {
            SettableBeanProperty property = i.next();
            Class rawPropertyType = property.getType().getRawClass();

            // These if-statements check for exact interface declarations, so the obfuscating replacement will have a compatible type

            if (rawPropertyType == Obfuscated.class) {
                JsonDeserializer newDeserializer = createDeserializerForObfuscated(config, property);
                replaceProperty(property, newDeserializer, propertyReplacements, constructorArguments);

            } else if (rawPropertyType == List.class) {
                createDeserializerForList(config, property)
                        .ifPresent(newDeserializer -> replaceProperty(property, newDeserializer, propertyReplacements, constructorArguments));

            } else if (rawPropertyType == Set.class) {
                createDeserializerForSet(config, property)
                        .ifPresent(newDeserializer -> replaceProperty(property, newDeserializer, propertyReplacements, constructorArguments));

            } else if (rawPropertyType == Collection.class) {
                createDeserializerForCollection(config, property)
                        .ifPresent(newDeserializer -> replaceProperty(property, newDeserializer, propertyReplacements, constructorArguments));

            } else if (rawPropertyType == Map.class) {
                createDeserializerForMap(config, property)
                        .ifPresent(newDeserializer -> replaceProperty(property, newDeserializer, propertyReplacements, constructorArguments));
            }
        }
        for (SettableBeanProperty property : propertyReplacements.values()) {
            updatedBuilder.addOrReplaceProperty(property, true);
        }
        return updatedBuilder;
    }

    // Obfuscated

    private JsonDeserializer createDeserializerForObfuscated(DeserializationConfig config, SettableBeanProperty property) {
        ObjectFactory objectFactory = factoryMapper.apply(config);
        Optional optionalObfuscator = objectFactory.obfuscator(property::getAnnotation);
        if (!optionalObfuscator.isPresent()) {
            // property.getType() is Obfuscated, so this returns the actual T
            Class type = property.getType().getBindings().getBoundType(0).getRawClass();
            optionalObfuscator = findClassSpecificObfuscator(type, objectFactory);
        }
        Obfuscator obfuscator = optionalObfuscator.orElse(defaultObfuscator);
        return createDeserializerForObfuscated(property, obfuscator, objectFactory);
    }

    private JsonDeserializer createDeserializerForObfuscated(SettableBeanProperty property, Obfuscator obfuscator,
            ObjectFactory objectFactory) {

        JsonDeserializer deserializer = property.getValueDeserializer();
        // property.getType() is Obfuscated, so index 0 is T
        CharacterRepresentationProvider characterRepresentationProvider = getCharacterRepresentationProvider(property, 0, objectFactory);
        return new ObfuscatedDeserializer.ForObfuscated(property, deserializer, obfuscator, characterRepresentationProvider);
    }

    // List

    private Optional> createDeserializerForList(DeserializationConfig config, SettableBeanProperty property) {
        ObjectFactory objectFactory = factoryMapper.apply(config);
        Optional optionalObfuscator = objectFactory.obfuscator(property::getAnnotation);
        if (!optionalObfuscator.isPresent() && !requireObfuscatorAnnotation) {
            // property.getType() is List, so this returns the actual T
            Class type = property.getType().getBindings().getBoundType(0).getRawClass();
            optionalObfuscator = findClassSpecificObfuscator(type, objectFactory);
        }
        return optionalObfuscator.map(obfuscator -> createDeserializerForList(property, obfuscator, objectFactory));
    }

    private JsonDeserializer createDeserializerForList(SettableBeanProperty property, Obfuscator obfuscator, ObjectFactory objectFactory) {
        JsonDeserializer deserializer = property.getValueDeserializer();
        // property.getType() is List, so index 0 is T
        CharacterRepresentationProvider characterRepresentationProvider = getCharacterRepresentationProvider(property, 0, objectFactory);
        return new ObfuscatedDeserializer.ForList(property, deserializer, obfuscator, characterRepresentationProvider);
    }

    // Set

    private Optional> createDeserializerForSet(DeserializationConfig config, SettableBeanProperty property) {
        ObjectFactory objectFactory = factoryMapper.apply(config);
        Optional optionalObfuscator = objectFactory.obfuscator(property::getAnnotation);
        if (!optionalObfuscator.isPresent() && !requireObfuscatorAnnotation) {
            // property.getType() is Set, so this returns the actual T
            Class type = property.getType().getBindings().getBoundType(0).getRawClass();
            optionalObfuscator = findClassSpecificObfuscator(type, objectFactory);
        }
        return optionalObfuscator.map(obfuscator -> createDeserializerForSet(property, obfuscator, objectFactory));
    }

    private JsonDeserializer createDeserializerForSet(SettableBeanProperty property, Obfuscator obfuscator, ObjectFactory objectFactory) {
        JsonDeserializer deserializer = property.getValueDeserializer();
        // property.getType() is Set, so index 0 is T
        CharacterRepresentationProvider characterRepresentationProvider = getCharacterRepresentationProvider(property, 0, objectFactory);
        return new ObfuscatedDeserializer.ForSet(property, deserializer, obfuscator, characterRepresentationProvider);
    }

    // Collection

    private Optional> createDeserializerForCollection(DeserializationConfig config, SettableBeanProperty property) {
        ObjectFactory objectFactory = factoryMapper.apply(config);
        Optional optionalObfuscator = objectFactory.obfuscator(property::getAnnotation);
        if (!optionalObfuscator.isPresent() && !requireObfuscatorAnnotation) {
            // property.getType() is Collection, so this returns the actual T
            Class type = property.getType().getBindings().getBoundType(0).getRawClass();
            optionalObfuscator = findClassSpecificObfuscator(type, objectFactory);
        }
        return optionalObfuscator.map(obfuscator -> createDeserializerForCollection(property, obfuscator, objectFactory));
    }

    private JsonDeserializer createDeserializerForCollection(SettableBeanProperty property, Obfuscator obfuscator,
            ObjectFactory objectFactory) {

        JsonDeserializer deserializer = property.getValueDeserializer();
        // property.getType() is Collection, so index 0 is T
        CharacterRepresentationProvider characterRepresentationProvider = getCharacterRepresentationProvider(property, 0, objectFactory);
        return new ObfuscatedDeserializer.ForCollection(property, deserializer, obfuscator, characterRepresentationProvider);
    }

    // Map

    private Optional> createDeserializerForMap(DeserializationConfig config, SettableBeanProperty property) {
        ObjectFactory objectFactory = factoryMapper.apply(config);
        Optional optionalObfuscator = objectFactory.obfuscator(property::getAnnotation);
        if (!optionalObfuscator.isPresent() && !requireObfuscatorAnnotation) {
            // property.getType() is Map, so this returns the actual V
            Class type = property.getType().getBindings().getBoundType(1).getRawClass();
            optionalObfuscator = findClassSpecificObfuscator(type, objectFactory);
        }
        return optionalObfuscator.map(obfuscator -> createDeserializerForMap(property, obfuscator, objectFactory));
    }

    private JsonDeserializer createDeserializerForMap(SettableBeanProperty property, Obfuscator obfuscator, ObjectFactory objectFactory) {
        JsonDeserializer deserializer = property.getValueDeserializer();
        // property.getType() is Map, so index 1 is V
        CharacterRepresentationProvider characterRepresentationProvider = getCharacterRepresentationProvider(property, 1, objectFactory);
        return new ObfuscatedDeserializer.ForMap(property, deserializer, obfuscator, characterRepresentationProvider);
    }

    // shared

    private Optional findClassSpecificObfuscator(Class type, ObjectFactory objectFactory) {
        Obfuscator obfuscator = findClassSpecificObject(type, classObfuscators, interfaceObfuscators);
        return obfuscator != null ? Optional.of(obfuscator) : objectFactory.obfuscator(type::getAnnotation);
    }

    private CharacterRepresentationProvider getCharacterRepresentationProvider(BeanProperty property, int subTypeIndex, ObjectFactory objectFactory) {
        Optional optionalProvider = objectFactory.characterRepresentationProvider(property::getAnnotation);
        if (optionalProvider.isPresent()) {
            return optionalProvider.get();
        }

        Class type = property.getType().getBindings().getBoundType(subTypeIndex).getRawClass();
        optionalProvider = findClassSpecificCharacterRepresentationProvider(type, objectFactory);
        return optionalProvider.orElseGet(() -> CharacterRepresentationProvider.getDefaultInstance(type));
    }

    private Optional findClassSpecificCharacterRepresentationProvider(Class type, ObjectFactory objectFactory) {
        CharacterRepresentationProvider provider = findClassSpecificObject(type, classCharacterRepresentationProviders,
                interfaceCharacterRepresentationProviders);

        return provider != null ? Optional.of(provider) : objectFactory.characterRepresentationProvider(type::getAnnotation);
    }

    private void replaceProperty(SettableBeanProperty property, JsonDeserializer newDeserializer,
            Map propertyReplacements, SettableBeanProperty[] constructorArguments) {

        SettableBeanProperty replacement = property.withValueDeserializer(newDeserializer);
        propertyReplacements.put(property.getName(), replacement);
        replaceProperty(constructorArguments, property, replacement);
    }

    private void replaceProperty(SettableBeanProperty[] constructorArguments, SettableBeanProperty property, SettableBeanProperty replacement) {
        // keep the properties in sync
        if (constructorArguments != null) {
            for (int i = 0; i < constructorArguments.length; i++) {
                if (constructorArguments[i] == property) {
                    constructorArguments[i] = replacement;
                    return;
                }
            }
        }
    }

    // class-based lookups

    static  T findClassSpecificObject(Class type, Map, T> classMappings, Map, T> interfaceMappings) {
        // Try direct match first
        T result = findDirectClassSpecificObject(type, classMappings, interfaceMappings);
        if (result != null) {
            return result;
        }

        if (!interfaceMappings.isEmpty()) {
            // Try super-interfaces
            result = findInterfaceSpecificObject(type, interfaceMappings);
            if (result != null) {
                return result;
            }

            // Try interfaces of super classes
            if (!type.isInterface()) {
                Class iterator = type.getSuperclass();
                while (iterator != null) {
                    result = findInterfaceSpecificObject(iterator, interfaceMappings);
                    if (result != null) {
                        return result;
                    }
                    iterator = iterator.getSuperclass();
                }
            }
        }
        return null;
    }

    private static  T findDirectClassSpecificObject(Class type, Map, T> classMappings, Map, T> interfaceMappings) {
        if (type.isInterface()) {
            // no need to check for interfaceMappings.isEmpty(), as interfaceMappings.get(type) will then return null anyway
            return interfaceMappings.get(type);
        }
        if (!classMappings.isEmpty()) {
            Class iterator = type;
            while (iterator != null) {
                T result = classMappings.get(iterator);
                if (result != null) {
                    return result;
                }
                iterator = iterator.getSuperclass();
            }
        }
        return null;
    }

    private static  T findInterfaceSpecificObject(Class type, Map, T> interfaceMappings) {
        for (Class iface : type.getInterfaces()) {
            T result = interfaceMappings.get(iface);
            if (result != null) {
                return result;
            }
            result = findInterfaceSpecificObject(iface, interfaceMappings);
            if (result != null) {
                return result;
            }
        }
        return null;
    }
}