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

io.micronaut.serde.support.deserializers.PropertiesBag Maven / Gradle / Ivy

/*
 * Copyright 2017-2021 original authors
 *
 * 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
 *
 * https://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 io.micronaut.serde.support.deserializers;

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.naming.Named;
import io.micronaut.core.util.StringIntMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * The collection of properties. Some operations are delegating to {@link BeanIntrospection} property index resolving,
 * which is using compile-time string switch instead of map.
 *
 * @param  The bean type
 * @author Denis Stepanov
 * @since 1.0.0
 */
@Internal
final class PropertiesBag {

    private final BeanIntrospection beanIntrospection;
    private final int[] originalNameToPropertiesMapping;
    private final DeserBean.DerProperty[] properties;
    @Nullable
    private final Map nameToPropertiesMapping;
    private final long propertiesMask;
    private final StringIntMap nameToPosition;

    private PropertiesBag(BeanIntrospection beanIntrospection,
                          int[] originalNameToPropertiesMapping,
                          DeserBean.DerProperty[] properties,
                          Map nameToPropertiesMapping) {
        this.beanIntrospection = beanIntrospection;
        this.originalNameToPropertiesMapping = originalNameToPropertiesMapping;
        this.properties = properties;
        this.nameToPropertiesMapping = nameToPropertiesMapping;
        if (properties.length > 0 && properties.length <= 64) {
            this.propertiesMask = -1L >>> (64 - properties.length);
        } else {
            this.propertiesMask = 0;
        }
        Stream propStream = beanIntrospection.getBeanProperties().stream().map(Named::getName);
        if (nameToPropertiesMapping != null) {
            propStream = Stream.concat(propStream, nameToPropertiesMapping.keySet().stream());
        }
        Set props = propStream.collect(Collectors.toSet());
        nameToPosition = new StringIntMap(props.size());
        for (String prop : props) {
            nameToPosition.put(prop, propertyIndexOfSlow(prop));
        }
    }

    /**
     * Get the properties in this bag.
     *
     * @return All properties in this bag
     */
    List> getProperties() {
        Stream> originalProperties = Arrays.stream(originalNameToPropertiesMapping)
            .filter(index -> index != -1)
            .mapToObj(index -> {
                DeserBean.DerProperty prop = properties[index];
                if (prop.beanProperty == null) {
                    return null;
                }
                return prop;
            });
        Stream> mappedByName = nameToPropertiesMapping == null ? Stream.empty() : nameToPropertiesMapping.values()
            .stream()
            .map(index -> properties[index]);
        return Stream.concat(originalProperties, mappedByName)
            .toList();
    }

    /**
     * Get the properties in this bag.
     *
     * @return All properties in this bag
     */
    List> getDerProperties() {
        return Collections.unmodifiableList(Arrays.asList(properties));
    }

    DeserBean.DerProperty[] getPropertiesArray() {
        return properties;
    }

    int propertyIndexOf(@NonNull String name) {
        return nameToPosition.get(name, -1);
    }

    private int propertyIndexOfSlow(@NonNull String name) {
        int propertyIndex = -1;
        int beanPropertyIndex = beanIntrospection.propertyIndexOf(name);
        if (beanPropertyIndex != -1) {
            propertyIndex = originalNameToPropertiesMapping[beanPropertyIndex];
        }
        if (propertyIndex != -1) {
            return propertyIndex;
        }
        return nameToPropertiesMapping == null ? -1 : nameToPropertiesMapping.getOrDefault(name, -1);
    }

    Consumer newConsumer() {
        return propertiesMask == 0 ? new ConsumerBig() : new ConsumerSmall();
    }

    /**
     * Properties consumer.
     */
    abstract sealed class Consumer {
        private Consumer() {
        }

        public DeserBean.DerProperty consume(String name) {
            int propertyIndex = nameToPosition.get(name, -1);
            if (propertyIndex == -1 || isConsumed(propertyIndex)) {
                return null;
            }
            setConsumed(propertyIndex);
            return properties[propertyIndex];
        }

        public void consume(int propertyIndex) {
            if (propertyIndex == -1 || isConsumed(propertyIndex)) {
                return;
            }
            setConsumed(propertyIndex);
        }

        public List> getNotConsumed() {
            List> list = new ArrayList<>(properties.length);
            int bound = properties.length;
            for (int index = 0; index < bound; index++) {
                if (!isConsumed(index)) {
                    list.add(properties[index]);
                }
            }
            return list;
        }

        abstract boolean isConsumed(int index);

        abstract void setConsumed(int index);

        public abstract boolean isAllConsumed();
    }

    private final class ConsumerBig extends Consumer {
        private final BitSet consumed = new BitSet(properties.length);
        private int remaining = properties.length;

        @Override
        boolean isConsumed(int index) {
            return consumed.get(index);
        }

        @Override
        public boolean isAllConsumed() {
            return remaining == 0;
        }

        @Override
        void setConsumed(int index) {
            consumed.set(index);
            remaining--;
        }
    }

    private final class ConsumerSmall extends Consumer {
        private long consumed = ~propertiesMask;

        @Override
        boolean isConsumed(int index) {
            return (consumed & (1L << index)) != 0;
        }

        @Override
        void setConsumed(int index) {
            consumed |= 1L << index;
        }

        @Override
        public boolean isAllConsumed() {
            return consumed == -1;
        }
    }

    static class Builder {

        private final BeanIntrospection beanIntrospection;
        private final int[] originalNameToPropertiesMapping;
        @Nullable
        private Map nameToPropertiesMapping;

        private final List> mutableProperties;

        Builder(BeanIntrospection beanIntrospection) {
            this(beanIntrospection, beanIntrospection.getBeanProperties().size());
        }

        Builder(BeanIntrospection beanIntrospection, int expectedPropertiesSize) {
            this.beanIntrospection = beanIntrospection;
            int beanPropertiesSize = beanIntrospection.getBeanProperties().size();
            this.originalNameToPropertiesMapping = new int[beanPropertiesSize];
            Arrays.fill(originalNameToPropertiesMapping, -1);
            this.mutableProperties = new ArrayList<>(expectedPropertiesSize);
        }

        void register(String name, DeserBean.DerProperty derProperty, boolean addAliases) {
            int newPropertyIndex = mutableProperties.size();
            if (derProperty.beanProperty != null && derProperty.beanProperty.getDeclaringBean() == beanIntrospection && name.equals(derProperty.beanProperty.getName())) {
                originalNameToPropertiesMapping[beanIntrospection.propertyIndexOf(name)] = newPropertyIndex;
            } else {
                if (nameToPropertiesMapping == null) {
                    nameToPropertiesMapping = new HashMap<>();
                }
                nameToPropertiesMapping.put(name, newPropertyIndex);
            }
            if (addAliases && derProperty.aliases != null && derProperty.aliases.length > 0) {
                if (nameToPropertiesMapping == null) {
                    nameToPropertiesMapping = new HashMap<>();
                }
                for (String alias : derProperty.aliases) {
                    nameToPropertiesMapping.put(alias, newPropertyIndex);
                }
            }
            mutableProperties.add(derProperty);
        }

        @Nullable
        PropertiesBag build() {
            if (mutableProperties.isEmpty()) {
                return null;
            }
            return new PropertiesBag<>(
                beanIntrospection,
                originalNameToPropertiesMapping,
                mutableProperties.toArray(DeserBean.DerProperty[]::new),
                nameToPropertiesMapping
            );
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy