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}