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 ninja.leaping.configurate.objectmapping.serialize; 018 019import com.google.common.reflect.TypeToken; 020import ninja.leaping.configurate.ConfigurationNode; 021import ninja.leaping.configurate.objectmapping.ObjectMappingException; 022import org.checkerframework.checker.nullness.qual.Nullable; 023 024import java.util.List; 025import java.util.function.Predicate; 026 027/** 028 * Serialize a value that can be represented as a scalar value within a node. Implementations must be able 029 * to serialize when one of the accepted types is a {@link String}, and may support any other types as desired. 030 * <p> 031 * When serializing to a node, null values will be passed through directly. If the type serialized by this serializer is 032 * one of the native types of the backing node, it will be written directly to the node without any transformation. 033 * <p> 034 * Any serialized value must be deserializable by the same serializer.. 035 * 036 * @param <T> The object type to serialize 037 */ 038public abstract class ScalarSerializer<T> implements TypeSerializer<T> { 039 private final TypeToken<T> type; 040 041 protected ScalarSerializer(TypeToken<T> type) { 042 this.type = type.wrap(); 043 } 044 045 protected ScalarSerializer(Class<T> type) { 046 if (type.getTypeParameters().length > 0) { 047 throw new IllegalArgumentException("Provided type " + type + " has type parameters but was not provided as a TypeToken!"); 048 } 049 this.type = TypeToken.of(type); 050 } 051 052 /** 053 * Get the general type token applicable for this serializer. This token may be parameterized. 054 * 055 * @return The type token for this serializer 056 */ 057 public final TypeToken<T> type() { 058 return this.type; 059 } 060 061 @Override 062 public final @Nullable T deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { 063 ConfigurationNode deserializeFrom = node; 064 if (node.isList()) { 065 List<? extends ConfigurationNode> children = node.getChildrenList(); 066 if (children.size() == 1) { 067 deserializeFrom = children.get(0); 068 } 069 } 070 071 if (deserializeFrom.isList() || deserializeFrom.isMap()) { 072 throw new ObjectMappingException("Value must be provided as a scalar!"); 073 } 074 075 @Nullable Object value = deserializeFrom.getValue(); 076 if (value == null) { 077 return null; 078 } 079 080 type = type.wrap(); // every primitive type should be boxed (cuz generics!) 081 @Nullable T possible = cast(value); 082 if (possible != null) { 083 return possible; 084 } 085 086 return deserialize(type, value); 087 } 088 089 @Override 090 public final void serialize(TypeToken<?> type, @Nullable T obj, ConfigurationNode node) { 091 if (obj == null) { 092 node.setValue(null); 093 return; 094 } 095 096 if (node.getOptions().acceptsType(obj.getClass())) { 097 node.setValue(obj); 098 return; 099 } 100 101 node.setValue(serialize(obj, node.getOptions()::acceptsType)); 102 } 103 104 /** 105 * Given an object of unknown type, attempt to convert it into the given type. 106 * 107 * @param type The specific type of the type's usage 108 * @param obj The object to convert 109 * @return A converted object 110 * @throws ObjectMappingException If the object could not be converted for any reason 111 */ 112 public abstract T deserialize(TypeToken<?> type, Object obj) throws ObjectMappingException; 113 114 /** 115 * @param item The value to serialize 116 * @param typeSupported A predicate to allow choosing which types are supported 117 * @return A serialized form of this object 118 */ 119 public abstract Object serialize(T item, Predicate<Class<?>> typeSupported); 120 121 /** 122 * Attempt to deserialize the provided object using an unspecialized type. This may fail on more complicated 123 * deserialization processes 124 * 125 * @param value The object to deserialize. 126 * @return The deserialized object, if possible 127 * @throws ObjectMappingException If unable to coerce the value to the requested type 128 */ 129 public final T deserialize(Object value) throws ObjectMappingException { 130 @Nullable T possible = cast(value); 131 if (possible != null) { 132 return possible; 133 } 134 135 return this.deserialize(this.type(), value); 136 } 137 138 @SuppressWarnings("unchecked") 139 private @Nullable T cast(Object value) { 140 Class<?> rawType = this.type().getRawType(); 141 if (rawType.isInstance(value)) { 142 return (T) value; 143 } 144 return null; 145 } 146 147 /** 148 * Attempt to deserialize the provided object, but rather than throwing an exception when a parse error occurs, 149 * return null instead. 150 * 151 * @param obj The object to try to deserialize 152 * @return An instance of the appropriate type, or null 153 * @see #deserialize(Object) 154 */ 155 public final @Nullable T tryDeserialize(@Nullable Object obj) { 156 if (obj == null) { 157 return null; 158 } 159 160 try { 161 return deserialize(obj); 162 } catch (ObjectMappingException ex) { 163 return null; 164 } 165 } 166 167 /** 168 * Serialize the item to a {@link String}, in a representation that can be interpreted by this serializer again 169 * 170 * @param item The item to serialize 171 * @return The serialized form of the item 172 */ 173 public final String serializeToString(T item) { 174 if (item instanceof CharSequence) { 175 return item.toString(); 176 } 177 // Otherwise, use the serializer 178 return (String) serialize(item, clazz -> clazz.isAssignableFrom(String.class)); 179 } 180 181}