com.github.mrenou.jacksonatic.internal.introspection.AnnotatedClassConstructor.java Source code

Java tutorial

Introduction

Here is the source code for com.github.mrenou.jacksonatic.internal.introspection.AnnotatedClassConstructor.java

Source

/**
 * Copyright (C) 2015 Morgan Renou (mrenou@gmail.com)
 *
 * 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.mrenou.jacksonatic.internal.introspection;

import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.github.mrenou.jacksonatic.internal.JacksonOperation;
import com.github.mrenou.jacksonatic.internal.JacksonaticInternal;
import com.github.mrenou.jacksonatic.internal.annotations.ClassAnnotationDecorator;
import com.github.mrenou.jacksonatic.internal.mapping.ClassMappingInternal;
import com.github.mrenou.jacksonatic.internal.mapping.ClassesMapping;
import com.github.mrenou.jacksonatic.internal.util.Mergeable;
import com.github.mrenou.jacksonatic.internal.util.TypedHashMap;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import static com.github.mrenou.jacksonatic.internal.JacksonOperation.*;

/**
 * Build {@link com.fasterxml.jackson.databind.introspect.AnnotatedClass} adding annotations defined in class mapping;
 * <p>
 * Class mapping is share in three sources :
 * - standard class mapping {@link com.github.mrenou.jacksonatic.internal.JacksonOperation#ANY}
 * - class mapping defined only for serialization process {@link com.github.mrenou.jacksonatic.internal.JacksonOperation#SERIALIZATION}
 * - class mapping defined only for deserialization process {@link com.github.mrenou.jacksonatic.internal.JacksonOperation#DESERIALIZATION}
 * <p>
 * Class mapping for serialization and deserialization overrides the standard class mapping.
 * <p>
 * Class mapping can be inherited from class mapping parent (expected when process is
 * {@link com.github.mrenou.jacksonatic.internal.JacksonOperation#NO_SUPER_TYPES}). Child class mapping override parent class mapping.
 * <p>
 * When final class mapping is built from all these class mapping, it is saved into {@link #mergedClassesMappingByOperation } to
 * avoid a re-computation.
 */
public class AnnotatedClassConstructor {

    private ClassAnnotationDecorator classAnnotationDecorator = new ClassAnnotationDecorator();

    private TypedHashMap<JacksonOperation, ClassesMapping> classesMappingByOperation = new TypedHashMap<>();

    private TypedHashMap<JacksonOperation, ClassesMapping> mergedClassesMappingByOperation = new TypedHashMap<>();

    public AnnotatedClassConstructor(JacksonaticInternal mappingConfigurer) {
        this.classesMappingByOperation = mappingConfigurer.getClassesMappingByOperation().copy();
        this.mergedClassesMappingByOperation.put(JacksonOperation.SERIALIZATION, new ClassesMapping());
        this.mergedClassesMappingByOperation.put(JacksonOperation.DESERIALIZATION, new ClassesMapping());
        this.mergedClassesMappingByOperation.put(JacksonOperation.NO_SUPER_TYPES, new ClassesMapping());
    }

    public AnnotatedClass constructForSerialization(Class<?> cls, AnnotationIntrospector annotationIntrospector,
            ClassIntrospector.MixInResolver mir) {
        AnnotatedClass annotatedClass = AnnotatedClass.construct(cls, annotationIntrospector, mir);
        return processAnnotatedClass(JacksonOperation.SERIALIZATION, annotatedClass);
    }

    public AnnotatedClass constructForDeserialization(Class<?> cls, AnnotationIntrospector annotationIntrospector,
            ClassIntrospector.MixInResolver mir) {
        AnnotatedClass annotatedClass = AnnotatedClass.construct(cls, annotationIntrospector, mir);
        return processAnnotatedClass(JacksonOperation.DESERIALIZATION, annotatedClass);
    }

    public AnnotatedClass constructWithoutSuperTypes(Class<?> cls, AnnotationIntrospector annotationIntrospector,
            ClassIntrospector.MixInResolver mir) {
        AnnotatedClass annotatedClass = AnnotatedClass.construct(cls, annotationIntrospector, mir);
        return processAnnotatedClass(JacksonOperation.NO_SUPER_TYPES, annotatedClass);
    }

    @SuppressWarnings("unchecked")
    private AnnotatedClass processAnnotatedClass(JacksonOperation processType, AnnotatedClass ac) {
        if (ac.getAnnotated().getName().startsWith("java.")) {
            return ac;
        }
        Class<Object> annotated = (Class<Object>) ac.getAnnotated();
        ClassesMapping serOrDeserClassesMapping = getSerOrDeserClassMapping(processType);
        ClassesMapping mergedClassesMapping = this.mergedClassesMappingByOperation.getTyped(processType);

        return Optional
                .ofNullable(mergedClassesMapping.getOpt(annotated)
                        .orElseGet(() -> mergeAndPutInMergedClassesMapping(mergedClassesMapping, annotated,
                                serOrDeserClassesMapping.getOpt(annotated),
                                classesMappingByOperation.get(ANY).getOpt(annotated),
                                getClassMappingFromSuperTypes(annotated, serOrDeserClassesMapping,
                                        mergedClassesMapping))))
                .map(classMapping -> classAnnotationDecorator.decorate(ac, classMapping)).orElse(ac);
    }

    @SuppressWarnings("unchecked")
    private Optional<ClassMappingInternal<Object>> getClassMappingFromSuperTypes(Class<?> type,
            ClassesMapping serOrDeserClassesMapping, ClassesMapping mergedClassesMapping) {
        List<Class<?>> superTypes = ClassUtil.findSuperTypes(type, Object.class);
        Collections.reverse(superTypes);
        return Stream.concat(Stream.of(Object.class), superTypes.stream())
                .map(superType -> (Class<Object>) superType)
                .map(superType -> Optional.ofNullable(mergedClassesMapping.getOpt(superType)
                        .orElseGet(() -> mergeAndPutInMergedClassesMapping(mergedClassesMapping, superType,
                                serOrDeserClassesMapping.getOpt(superType),
                                classesMappingByOperation.get(ANY).getOpt(superType)))))
                .reduce(Optional.empty(), Mergeable::merge);
    }

    @SafeVarargs
    private final ClassMappingInternal<Object> mergeAndPutInMergedClassesMapping(
            ClassesMapping mergedClassesMapping, Class<Object> superType,
            Optional<ClassMappingInternal<Object>>... classMappings) {
        Optional<ClassMappingInternal<Object>> classMappingOpt = Mergeable.merge(classMappings)
                .map(classMapping -> classMapping.getType() != superType
                        ? new ClassMappingInternal<>(superType).mergeWith(classMapping)
                        : classMapping);
        classMappingOpt.ifPresent(classMapping -> mergedClassesMapping.put(superType, classMapping));
        return classMappingOpt.orElse(null);
    }

    private ClassesMapping getSerOrDeserClassMapping(JacksonOperation processType) {
        if (processType == JacksonOperation.SERIALIZATION || processType == JacksonOperation.NO_SUPER_TYPES) {
            return classesMappingByOperation.get(SERIALIZATION);
        } else {
            return classesMappingByOperation.get(DESERIALIZATION);
        }
    }
}