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.Type;
025import java.util.List;
026import java.util.function.Predicate;
027
028/**
029 * Serialize a value that can be represented as a scalar value within a node.
030 * Implementations must be able to serialize when one of the accepted types is
031 * a {@link String}, and may support any other types as desired.
032 *
033 * <p>When serializing to a node, null values will be passed through directly.
034 * If the type serialized by this serializer is one of the native types of the
035 * backing node, it will be written directly to the node without
036 * any transformation.
037 *
038 * <p>Any serialized value must be deserializable by the same serializer.
039 *
040 * @param <T> the object type to serialize
041 * @since 4.0.0
042 */
043public abstract class ScalarSerializer<T> implements TypeSerializer<T> {
044
045    private final TypeToken<T> type;
046
047    /**
048     * Create a new scalar serializer that handles the provided type.
049     *
050     * @param type type to handle
051     * @since 4.0.0
052     */
053    @SuppressWarnings("unchecked")
054    protected ScalarSerializer(final TypeToken<T> type) {
055        final Type boxed = GenericTypeReflector.box(type.getType());
056        this.type = boxed == type.getType() ? type : (TypeToken<T>) TypeToken.get(boxed);
057    }
058
059    /**
060     * Create a new scalar serializer that handles the provided type.
061     *
062     * <p>{@code type} must not be a raw parameterized type.</p>
063     *
064     * @param type type to handle
065     * @since 4.0.0
066     */
067    protected ScalarSerializer(final Class<T> type) {
068        if (type.getTypeParameters().length > 0) {
069            throw new IllegalArgumentException("Provided type " + type + " has type parameters but was not provided as a TypeToken!");
070        }
071        this.type = TypeToken.get(type);
072    }
073
074    @SuppressWarnings("unchecked")
075    ScalarSerializer(final Type type) {
076        this.type = (TypeToken<T>) TypeToken.get(type);
077    }
078
079    /**
080     * Get the general type token applicable for this serializer. This token may
081     * be parameterized.
082     *
083     * @return the type token for this serializer
084     * @since 4.0.0
085     */
086    public final TypeToken<T> type() {
087        return this.type;
088    }
089
090    @Override
091    public final T deserialize(Type type, final ConfigurationNode node) throws SerializationException {
092        ConfigurationNode deserializeFrom = node;
093        if (node.isList()) {
094            final List<? extends ConfigurationNode> children = node.childrenList();
095            if (children.size() == 1) {
096                deserializeFrom = children.get(0);
097            }
098        }
099
100        if (deserializeFrom.isList() || deserializeFrom.isMap()) {
101            throw new SerializationException(type, "Value must be provided as a scalar!");
102        }
103
104        final @Nullable Object value = deserializeFrom.rawScalar();
105        if (value == null) {
106            throw new SerializationException(type, "No scalar value present");
107        }
108
109        type = GenericTypeReflector.box(type); // every primitive type should be boxed (cuz generics!)
110        final @Nullable T possible = cast(value);
111        if (possible != null) {
112            return possible;
113        }
114
115        return deserialize(type, value);
116    }
117
118    /**
119     * Attempt to deserialize the provided object using an unspecialized type.
120     * This may fail on more complicated deserialization processes such as with
121     * enum types.
122     *
123     * @param value the object to deserialize.
124     * @return the deserialized object, if possible
125     * @throws SerializationException if unable to coerce the value to the
126     *                                requested type.
127     * @since 4.0.0
128     */
129    public final T deserialize(final Object value) throws SerializationException {
130        final @Nullable T possible = cast(value);
131        if (possible != null) {
132            return possible;
133        }
134
135        return this.deserialize(this.type().getType(), value);
136    }
137
138    /**
139     * Given an object of unknown type, attempt to convert it into the given
140     * type.
141     *
142     * @param type the specific type of the type's usage
143     * @param obj the object to convert
144     * @return a converted object
145     * @throws SerializationException if the object could not be converted for
146     *                                any reason
147     * @since 4.0.0
148     */
149    public abstract T deserialize(Type type, Object obj) throws SerializationException;
150
151    @Override
152    public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) {
153        if (obj == null) {
154            node.raw(null);
155            return;
156        }
157
158        if (node.options().acceptsType(obj.getClass())) {
159            node.raw(obj);
160            return;
161        }
162
163        node.raw(serialize(obj, node.options()::acceptsType));
164    }
165
166    /**
167     * Serialize the provided value to a supported type, testing against the
168     * provided predicate.
169     *
170     * @param item the value to serialize
171     * @param typeSupported a predicate to allow choosing which types are
172     *                      supported
173     * @return a serialized form of this object
174     * @since 4.0.0
175     */
176    protected abstract Object serialize(T item, Predicate<Class<?>> typeSupported);
177
178    @SuppressWarnings("unchecked")
179    private @Nullable T cast(final Object value) {
180        final Class<?> rawType = GenericTypeReflector.erase(this.type().getType());
181        if (rawType.isInstance(value)) {
182            return (T) value;
183        }
184        return null;
185    }
186
187    /**
188     * Attempt to deserialize the provided object, but rather than throwing an
189     * exception when a deserialization error occurs, return null instead.
190     *
191     * @param obj the object to try to deserialize
192     * @return an instance of the appropriate type, or null
193     * @see #deserialize(Object)
194     * @since 4.0.0
195     */
196    public final @Nullable T tryDeserialize(final @Nullable Object obj) {
197        if (obj == null) {
198            return null;
199        }
200
201        try {
202            return deserialize(obj);
203        } catch (final SerializationException ex) {
204            return null;
205        }
206    }
207
208    /**
209     * Serialize the item to a {@link String}, in a representation that can be
210     * interpreted by this serializer again.
211     *
212     * @param item the item to serialize
213     * @return the serialized form of the item
214     * @since 4.0.0
215     */
216    public final String serializeToString(final T item) {
217        if (item instanceof CharSequence) {
218            return item.toString();
219        }
220        // Otherwise, use the serializer
221        return (String) serialize(item, clazz -> clazz.isAssignableFrom(String.class));
222    }
223
224}