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}