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 io.leangen.geantyref.GenericTypeReflector;
020import io.leangen.geantyref.TypeToken;
021import org.checkerframework.checker.nullness.qual.Nullable;
022import org.spongepowered.configurate.ConfigurationNode;
023
024import java.lang.reflect.AnnotatedType;
025import java.lang.reflect.Type;
026import java.util.List;
027import java.util.function.Predicate;
028
029/**
030 * Serialize a value that can be represented as a scalar value within a node.
031 * Implementations must be able to serialize when one of the accepted types is
032 * a {@link String}, and may support any other types as desired.
033 *
034 * <p>When serializing to a node, null values will be passed through directly.
035 * If the type serialized by this serializer is one of the native types of the
036 * backing node, it will be written directly to the node without
037 * any transformation.
038 *
039 * <p>Any serialized value must be deserializable by the same serializer.
040 *
041 * @param <T> the object type to serialize
042 * @since 4.0.0
043 */
044public abstract class ScalarSerializer<T> implements TypeSerializer.Annotated<T> {
045
046    private final TypeToken<T> type;
047
048    /**
049     * Create a new scalar serializer that handles the provided type.
050     *
051     * @param type type to handle
052     * @since 4.0.0
053     */
054    @SuppressWarnings("unchecked")
055    protected ScalarSerializer(final TypeToken<T> type) {
056        final Type boxed = GenericTypeReflector.box(type.getType());
057        this.type = boxed == type.getType() ? type : (TypeToken<T>) TypeToken.get(boxed);
058    }
059
060    /**
061     * Create a new scalar serializer that handles the provided type.
062     *
063     * <p>{@code type} must not be a raw parameterized type.</p>
064     *
065     * @param type type to handle
066     * @since 4.0.0
067     */
068    protected ScalarSerializer(final Class<T> type) {
069        if (type.getTypeParameters().length > 0) {
070            throw new IllegalArgumentException("Provided type " + type + " has type parameters but was not provided as a TypeToken!");
071        }
072        this.type = TypeToken.get(type);
073    }
074
075    @SuppressWarnings("unchecked")
076    ScalarSerializer(final Type type) {
077        this.type = (TypeToken<T>) TypeToken.get(type);
078    }
079
080    /**
081     * Get the general type token applicable for this serializer. This token may
082     * be parameterized.
083     *
084     * @return the type token for this serializer
085     * @since 4.0.0
086     */
087    public final TypeToken<T> type() {
088        return this.type;
089    }
090
091    @Override
092    public final T deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
093        return TypeSerializer.Annotated.super.deserialize(type, node);
094    }
095
096    @Override
097    public final T deserialize(AnnotatedType type, final ConfigurationNode node) throws SerializationException {
098        ConfigurationNode deserializeFrom = node;
099        if (node.isList()) {
100            final List<? extends ConfigurationNode> children = node.childrenList();
101            if (children.size() == 1) {
102                deserializeFrom = children.get(0);
103            }
104        }
105
106        if (deserializeFrom.isList() || deserializeFrom.isMap()) {
107            throw new SerializationException(type, "Value must be provided as a scalar!");
108        }
109
110        final @Nullable Object value = deserializeFrom.rawScalar();
111        if (value == null) {
112            throw new SerializationException(type, "No scalar value present");
113        }
114
115        type = GenericTypeReflector.toCanonicalBoxed(type); // every primitive type should be boxed (cuz generics!)
116        final @Nullable T possible = this.cast(value);
117        if (possible != null) {
118            return possible;
119        }
120
121        return this.deserialize(type, value);
122    }
123
124    /**
125     * Attempt to deserialize the provided object using an unspecialized type.
126     * This may fail on more complicated deserialization processes such as with
127     * enum types.
128     *
129     * @param value the object to deserialize.
130     * @return the deserialized object, if possible
131     * @throws SerializationException if unable to coerce the value to the
132     *                                requested type.
133     * @since 4.0.0
134     */
135    public final T deserialize(final Object value) throws SerializationException {
136        final @Nullable T possible = this.cast(value);
137        if (possible != null) {
138            return possible;
139        }
140
141        return this.deserialize(this.type().getAnnotatedType(), value);
142    }
143
144    /**
145     * Given an object of unknown type, attempt to convert it into the given
146     * type.
147     *
148     * @param type the specific type of the type's usage
149     * @param obj the object to convert
150     * @return a converted object
151     * @throws SerializationException if the object could not be converted for
152     *                                any reason
153     * @since 4.2.0
154     */
155    public T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
156        return this.deserialize(type.getType(), obj);
157    }
158
159    /**
160     * Given an object of unknown type, attempt to convert it into the given
161     * type.
162     *
163     * @param type the specific type of the type's usage
164     * @param obj the object to convert
165     * @return a converted object
166     * @throws SerializationException if the object could not be converted for
167     *                                any reason
168     * @since 4.0.0
169     */
170    public abstract T deserialize(Type type, Object obj) throws SerializationException;
171
172    @Override
173    public final void serialize(final AnnotatedType type, final @Nullable T obj, final ConfigurationNode node) {
174        if (obj == null) {
175            node.raw(null);
176            return;
177        }
178
179        if (node.options().acceptsType(obj.getClass())) {
180            node.raw(obj);
181            return;
182        }
183
184        node.raw(this.serialize(type, obj, node.options()::acceptsType));
185    }
186
187    @Override
188    public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) {
189        this.serialize(GenericTypeReflector.annotate(type), obj, node);
190    }
191
192    /**
193     * Serialize the provided value to a supported type, testing against the
194     * provided predicate.
195     *
196     * <p>Annotated type information is provided for reference.</p>
197     *
198     * @param type the annotated type of the field being serialized
199     * @param item the value to serialize
200     * @param typeSupported a predicate to allow choosing which types are
201     *                      supported
202     * @return a serialized form of this object
203     * @since 4.2.0
204     */
205    protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
206        return this.serialize(item, typeSupported);
207    }
208
209    /**
210     * Serialize the provided value to a supported type, testing against the
211     * provided predicate.
212     *
213     * @param item the value to serialize
214     * @param typeSupported a predicate to allow choosing which types are
215     *                      supported
216     * @return a serialized form of this object
217     * @since 4.0.0
218     */
219    protected abstract Object serialize(T item, Predicate<Class<?>> typeSupported);
220
221    @SuppressWarnings("unchecked")
222    private @Nullable T cast(final Object value) {
223        final Class<?> rawType = GenericTypeReflector.erase(this.type().getType());
224        if (rawType.isInstance(value)) {
225            return (T) value;
226        }
227        return null;
228    }
229
230    /**
231     * Attempt to deserialize the provided object, but rather than throwing an
232     * exception when a deserialization error occurs, return null instead.
233     *
234     * @param obj the object to try to deserialize
235     * @return an instance of the appropriate type, or null
236     * @see #deserialize(Object)
237     * @since 4.0.0
238     */
239    public final @Nullable T tryDeserialize(final @Nullable Object obj) {
240        if (obj == null) {
241            return null;
242        }
243
244        try {
245            return this.deserialize(obj);
246        } catch (final SerializationException ex) {
247            return null;
248        }
249    }
250
251    /**
252     * Serialize the item to a {@link String}, in a representation that can be
253     * interpreted by this serializer again.
254     *
255     * @param item the item to serialize
256     * @return the serialized form of the item
257     * @since 4.0.0
258     */
259    public final String serializeToString(final T item) {
260        if (item instanceof CharSequence) {
261            return item.toString();
262        }
263        // Otherwise, use the serializer
264        return (String) this.serialize(GenericTypeReflector.annotate(item.getClass()), item, clazz -> clazz.isAssignableFrom(String.class));
265    }
266
267    /**
268     * A specialization of the scalar serializer that favors
269     * annotated type methods over unannotated methods.
270     *
271     * @param <V> the value to deserialize
272     * @since 4.2.0
273     */
274    public abstract static class Annotated<V> extends ScalarSerializer<V> {
275
276        /**
277         * Create a new annotated scalar serializer
278         * that handles the provided type.
279         *
280         * <p>{@code type} must not be a raw parameterized type.</p>
281         *
282         * @param type type to handle
283         * @since 4.2.0
284         */
285        protected Annotated(final Class<V> type) {
286            super(type);
287        }
288
289        /**
290         * Create a new annotated scalar serializer
291         * that handles the provided type.
292         *
293         * @param type type to handle
294         * @since 4.2.0
295         */
296        protected Annotated(final TypeToken<V> type) {
297            super(type);
298        }
299
300        @Override
301        public abstract V deserialize(
302            AnnotatedType type,
303            Object obj
304        ) throws SerializationException;
305
306        @Override
307        public V deserialize(
308            final Type type,
309            final Object obj
310        ) throws SerializationException {
311            return this.deserialize(GenericTypeReflector.annotate(type), obj);
312        }
313
314        @Override
315        protected abstract Object serialize(AnnotatedType type, V item, Predicate<Class<?>> typeSupported);
316
317        @Override
318        protected Object serialize(final V item, final Predicate<Class<?>> typeSupported) {
319            return this.serialize(GenericTypeReflector.annotate(item.getClass()), item, typeSupported);
320        }
321
322    }
323
324}