001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.spongepowered.configurate.serialize;
018
019import org.checkerframework.checker.nullness.qual.Nullable;
020import org.spongepowered.configurate.ConfigurationNode;
021import org.spongepowered.configurate.ConfigurationOptions;
022import org.spongepowered.configurate.util.CheckedConsumer;
023
024import java.lang.reflect.Type;
025import java.util.Collections;
026import java.util.List;
027
028/**
029 * A serializer for nodes that are 'list-like' (i.e may be stored in nodes where {@link ConfigurationNode#isList()} is
030 * {@literal true}.
031 *
032 * @param <T> the type of collection to serialize
033 * @since 4.1.0
034 */
035public abstract class AbstractListChildSerializer<T> implements TypeSerializer<T> {
036
037    /**
038     * Create a new serializer, only for use by subclasses.
039     *
040     * @since 4.1.0
041     */
042    protected AbstractListChildSerializer() {
043    }
044
045    @Override
046    public final T deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
047        final Type entryType = elementType(type);
048        final @Nullable TypeSerializer<?> entrySerial = node.options().serializers().get(entryType);
049        if (entrySerial == null) {
050            throw new SerializationException(node, entryType, "No applicable type serializer for type");
051        }
052
053        if (node.isList()) {
054            final List<? extends ConfigurationNode> values = node.childrenList();
055            final T ret = createNew(values.size(), entryType);
056            for (int i = 0; i < values.size(); ++i) {
057                try {
058                    deserializeSingle(i, ret, entrySerial.deserialize(entryType, values.get(i)));
059                } catch (final SerializationException ex) {
060                    ex.initPath(values.get(i)::path);
061                    throw ex;
062                }
063            }
064            return ret;
065        } else {
066            final @Nullable Object unwrappedVal = node.raw();
067            if (unwrappedVal != null) {
068                final T ret = createNew(1, entryType);
069                deserializeSingle(0, ret, entrySerial.deserialize(entryType, node));
070                return ret;
071            }
072        }
073        return createNew(0, entryType);
074    }
075
076    @SuppressWarnings({"unchecked", "rawtypes"})
077    @Override
078    public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) throws SerializationException {
079        final Type entryType = elementType(type);
080        final @Nullable TypeSerializer entrySerial = node.options().serializers().get(entryType);
081        if (entrySerial == null) {
082            throw new SerializationException(node, entryType, "No applicable type serializer for type");
083        }
084
085        node.raw(Collections.emptyList());
086        if (obj != null) {
087            forEachElement(obj, el -> {
088                final ConfigurationNode child = node.appendListNode();
089                try {
090                    entrySerial.serialize(entryType, el, child);
091                } catch (final SerializationException ex) {
092                    ex.initPath(child::path);
093                    throw ex;
094                }
095            });
096        }
097    }
098
099    @Override
100    public @Nullable T emptyValue(final Type specificType, final ConfigurationOptions options) {
101        try {
102            return this.createNew(0, elementType(specificType));
103        } catch (final SerializationException ex) {
104            return null;
105        }
106    }
107
108    /**
109     * Given the type of container, provide the expected type of an element. If
110     * the element type is not available, an exception must be thrown.
111     *
112     * @param containerType the type of container with type parameters resolved
113     *                      to the extent possible.
114     * @return the element type
115     * @throws SerializationException if the element type could not be detected
116     * @since 4.1.0
117     */
118    protected abstract Type elementType(Type containerType) throws SerializationException;
119
120    /**
121     * Create a new instance of the collection. The returned instance must be
122     * mutable, but may have a fixed length.
123     *
124     * @param length the necessary collection length
125     * @param elementType the type of element contained within the collection,
126     *                    as provided by {@link #elementType(Type)}
127     * @return a newly created collection
128     * @throws SerializationException when an error occurs during the creation
129     *                                of the collection
130     * @since 4.1.0
131     */
132    protected abstract T createNew(int length, Type elementType) throws SerializationException;
133
134    /**
135     * Perform the provided action on each element of the provided collection.
136     *
137     * <p>This is equivalent to a foreach loop on the collection
138     *
139     * @param collection the collection to act on
140     * @param action the action to perform
141     * @throws SerializationException when thrown by the underlying action
142     * @since 4.1.0
143     */
144    protected abstract void forEachElement(T collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException;
145
146    /**
147     * Place a single deserialized value into the collection being deserialized.
148     *
149     * @param index location to set value at
150     * @param collection collection to modify
151     * @param deserialized value to add
152     * @throws SerializationException if object could not be coerced to an
153     *         appropriate type.
154     * @since 4.1.0
155     */
156    protected abstract void deserializeSingle(int index, T collection, @Nullable Object deserialized) throws SerializationException;
157
158}