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 com.google.errorprone.annotations.ForOverride;
020import io.leangen.geantyref.GenericTypeReflector;
021import org.checkerframework.checker.nullness.qual.Nullable;
022import org.spongepowered.configurate.ConfigurationNode;
023import org.spongepowered.configurate.ConfigurationOptions;
024import org.spongepowered.configurate.util.CheckedConsumer;
025
026import java.lang.reflect.AnnotatedType;
027import java.lang.reflect.Type;
028import java.util.Collections;
029import java.util.List;
030
031/**
032 * A serializer for nodes that are 'list-like' (i.e may be stored in nodes where {@link ConfigurationNode#isList()} is
033 * {@literal true}.
034 *
035 * @param <T> the type of collection to serialize
036 * @since 4.1.0
037 */
038public abstract class AbstractListChildSerializer<T> implements TypeSerializer.Annotated<T> {
039
040    /**
041     * Create a new serializer, only for use by subclasses.
042     *
043     * @since 4.1.0
044     */
045    protected AbstractListChildSerializer() {
046    }
047
048    @Override
049    public final T deserialize(final AnnotatedType type, final ConfigurationNode node) throws SerializationException {
050        final AnnotatedType entryType = this.elementType(type);
051        final @Nullable TypeSerializer<?> entrySerial = node.options().serializers().get(entryType);
052        if (entrySerial == null) {
053            throw new SerializationException(node, entryType, "No applicable type serializer for type");
054        }
055
056        if (node.isList()) {
057            final List<? extends ConfigurationNode> values = node.childrenList();
058            final T ret = this.createNew(values.size(), entryType);
059            for (int i = 0; i < values.size(); ++i) {
060                try {
061                    this.deserializeSingle(i, ret, entrySerial.deserialize(entryType, values.get(i)));
062                } catch (final SerializationException ex) {
063                    ex.initPath(values.get(i)::path);
064                    throw ex;
065                }
066            }
067            return ret;
068        } else {
069            final @Nullable Object unwrappedVal = node.raw();
070            if (unwrappedVal != null) {
071                final T ret = this.createNew(1, entryType);
072                this.deserializeSingle(0, ret, entrySerial.deserialize(entryType, node));
073                return ret;
074            }
075        }
076        return this.createNew(0, entryType);
077    }
078
079    @SuppressWarnings({"unchecked", "rawtypes"})
080    @Override
081    public final void serialize(final AnnotatedType type, final @Nullable T obj, final ConfigurationNode node) throws SerializationException {
082        final AnnotatedType entryType = this.elementType(type);
083        final @Nullable TypeSerializer entrySerial = node.options().serializers().get(entryType);
084        if (entrySerial == null) {
085            throw new SerializationException(node, entryType, "No applicable type serializer for type");
086        }
087
088        node.raw(Collections.emptyList());
089        if (obj != null) {
090            this.forEachElement(obj, el -> {
091                final ConfigurationNode child = node.appendListNode();
092                try {
093                    entrySerial.serialize(entryType, el, child);
094                } catch (final SerializationException ex) {
095                    ex.initPath(child::path);
096                    throw ex;
097                }
098            });
099        }
100    }
101
102    @Override
103    public @Nullable T emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
104        try {
105            return this.createNew(0, this.elementType(specificType));
106        } catch (final SerializationException ex) {
107            return null;
108        }
109    }
110
111    /**
112     * Given the type of container, provide the expected type of an element. If
113     * the element type is not available, an exception must be thrown.
114     *
115     * @param containerType the type of container with type parameters resolved
116     *                      to the extent possible.
117     * @return the element type
118     * @throws SerializationException if the element type could not be detected
119     * @since 4.2.0
120     */
121    @ForOverride
122    protected AnnotatedType elementType(final AnnotatedType containerType) throws SerializationException {
123        return GenericTypeReflector.annotate(this.elementType(containerType.getType()));
124    }
125
126    /**
127     * Given the type of container, provide the expected type of an element. If
128     * the element type is not available, an exception must be thrown.
129     *
130     * @param containerType the type of container with type parameters resolved
131     *                      to the extent possible.
132     * @return the element type
133     * @throws SerializationException if the element type could not be detected
134     * @since 4.1.0
135     * @deprecated for removal since 4.2.0, override {@link #elementType(AnnotatedType)} instead
136     *     to pass through annotation information
137     */
138    @Deprecated
139    protected Type elementType(final Type containerType) throws SerializationException {
140        throw new IllegalStateException("AbstractListChildSerializer implementations should override elementType(AnnotatedType)");
141    }
142
143    /**
144     * Create a new instance of the collection. The returned instance must be
145     * mutable, but may have a fixed length.
146     *
147     * @param length the necessary collection length
148     * @param elementType the type of element contained within the collection,
149     *                    as provided by {@link #elementType(Type)}
150     * @return a newly created collection
151     * @throws SerializationException when an error occurs during the creation
152     *                                of the collection
153     * @since 4.2.0
154     */
155    @ForOverride
156    protected T createNew(final int length, final AnnotatedType elementType) throws SerializationException {
157        return this.createNew(length, elementType.getType());
158    }
159
160    /**
161     * Create a new instance of the collection. The returned instance must be
162     * mutable, but may have a fixed length.
163     *
164     * @param length the necessary collection length
165     * @param elementType the type of element contained within the collection,
166     *                    as provided by {@link #elementType(Type)}
167     * @return a newly created collection
168     * @throws SerializationException when an error occurs during the creation
169     *                                of the collection
170     * @since 4.1.0
171     * @deprecated for removal since 4.2.0, override {@link #createNew(int, AnnotatedType)} instead
172     *     to pass through annotation information
173     */
174    @ForOverride
175    @Deprecated
176    protected T createNew(final int length, final Type elementType) throws SerializationException {
177        throw new IllegalStateException("AbstractListChildSerializer implementations should override createNew(int, AnnotatedType)");
178    }
179
180    /**
181     * Perform the provided action on each element of the provided collection.
182     *
183     * <p>This is equivalent to a foreach loop on the collection
184     *
185     * @param collection the collection to act on
186     * @param action the action to perform
187     * @throws SerializationException when thrown by the underlying action
188     * @since 4.1.0
189     */
190    @ForOverride
191    protected abstract void forEachElement(T collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException;
192
193    /**
194     * Place a single deserialized value into the collection being deserialized.
195     *
196     * @param index location to set value at
197     * @param collection collection to modify
198     * @param deserialized value to add
199     * @throws SerializationException if object could not be coerced to an
200     *         appropriate type.
201     * @since 4.1.0
202     */
203    @ForOverride
204    protected abstract void deserializeSingle(int index, T collection, @Nullable Object deserialized) throws SerializationException;
205
206}