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) throws SerializationException {
174        this.serialize(type.getType(), obj, node);
175    }
176
177    @Override
178    public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) {
179        if (obj == null) {
180            node.raw(null);
181            return;
182        }
183
184        if (node.options().acceptsType(obj.getClass())) {
185            node.raw(obj);
186            return;
187        }
188
189        node.raw(this.serialize(obj, node.options()::acceptsType));
190    }
191
192    /**
193     * Serialize the provided value to a supported type, testing against the
194     * provided predicate.
195     *
196     * @param item the value to serialize
197     * @param typeSupported a predicate to allow choosing which types are
198     *                      supported
199     * @return a serialized form of this object
200     * @since 4.0.0
201     */
202    protected abstract Object serialize(T item, Predicate<Class<?>> typeSupported);
203
204    @SuppressWarnings("unchecked")
205    private @Nullable T cast(final Object value) {
206        final Class<?> rawType = GenericTypeReflector.erase(this.type().getType());
207        if (rawType.isInstance(value)) {
208            return (T) value;
209        }
210        return null;
211    }
212
213    /**
214     * Attempt to deserialize the provided object, but rather than throwing an
215     * exception when a deserialization error occurs, return null instead.
216     *
217     * @param obj the object to try to deserialize
218     * @return an instance of the appropriate type, or null
219     * @see #deserialize(Object)
220     * @since 4.0.0
221     */
222    public final @Nullable T tryDeserialize(final @Nullable Object obj) {
223        if (obj == null) {
224            return null;
225        }
226
227        try {
228            return this.deserialize(obj);
229        } catch (final SerializationException ex) {
230            return null;
231        }
232    }
233
234    /**
235     * Serialize the item to a {@link String}, in a representation that can be
236     * interpreted by this serializer again.
237     *
238     * @param item the item to serialize
239     * @return the serialized form of the item
240     * @since 4.0.0
241     */
242    public final String serializeToString(final T item) {
243        if (item instanceof CharSequence) {
244            return item.toString();
245        }
246        // Otherwise, use the serializer
247        return (String) this.serialize(item, clazz -> clazz.isAssignableFrom(String.class));
248    }
249
250    /**
251     * A specialization of the scalar serializer that favors
252     * annotated type methods over unannotated methods.
253     *
254     * @param <V> the value to deserialize
255     * @since 4.2.0
256     */
257    public abstract static class Annotated<V> extends ScalarSerializer<V> {
258
259        /**
260         * Create a new annotated scalar serializer
261         * that handles the provided type.
262         *
263         * <p>{@code type} must not be a raw parameterized type.</p>
264         *
265         * @param type type to handle
266         * @since 4.2.0
267         */
268        protected Annotated(final Class<V> type) {
269            super(type);
270        }
271
272        /**
273         * Create a new annotated scalar serializer
274         * that handles the provided type.
275         *
276         * @param type type to handle
277         * @since 4.2.0
278         */
279        protected Annotated(final TypeToken<V> type) {
280            super(type);
281        }
282
283        @Override
284        public abstract V deserialize(
285            AnnotatedType type,
286            Object obj
287        ) throws SerializationException;
288
289        @Override
290        public V deserialize(
291            final Type type,
292            final Object obj
293        ) throws SerializationException {
294            return this.deserialize(GenericTypeReflector.annotate(type), obj);
295        }
296
297    }
298
299}