/* * The MIT License (MIT) * * Copyright (c) 2016. Diorite (by Bartłomiej Mazur (aka GotoFinal)) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package org.diorite.config.serialization; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import org.apache.commons.lang3.Validate; import org.diorite.commons.reflections.DioriteReflectionUtils; import org.diorite.config.serialization.comments.CommentsNode; import org.diorite.config.serialization.comments.DocumentComments; import org.diorite.config.serialization.snakeyaml.YamlCollectionCreator; class SimpleSerializationData implements SerializationData { private final SerializationType serializationType; protected final Class<?> type; protected final Serialization serialization; private DocumentComments comments; private final Map<Class<?>, DocumentComments> scannedCommentsClasses = new HashMap<>(20); private final Set<String> joinedKeys = new HashSet<>(20); private final List<Object> dataList = new ArrayList<>(20); protected final Map<Object, Object> dataMap = new LinkedHashMap<>(20); private String trueValue = "true"; private String falseValue = "false"; protected SimpleSerializationData(SerializationType serializationType, Serialization serialization, Class<?> type) { this.serializationType = serializationType; this.serialization = serialization; this.type = type; this.comments = Serialization.getGlobal().getCommentsManager().getComments(type); } @Override public SerializationType getSerializationType() { return this.serializationType; } @Override public DocumentComments getComments() { return this.comments; } @Override public void setComments(DocumentComments comments) { this.comments = comments; } protected <T> Object serialize(T object, @Nullable DocumentComments comments) { return this.serialization.serialize(object, this.serializationType, comments); } @SuppressWarnings("unchecked") @Nullable protected <T> Object serialize(Class<T> type, @Nullable T object, @Nullable DocumentComments comments) { if (object == null) { return null; } if (type == Object.class) { type = (Class<T>) object.getClass(); } return this.serialization.serialize(type, object, this.serializationType, comments); } private void validateList(String key) { if (! key.isEmpty()) { return; } if (! this.dataMap.isEmpty()) { throw new SerializationException(this.type, "Can't serialize directly as list when other values are used."); } } protected void validateMap(String key) { if (! key.isEmpty()) { return; } if (! this.dataList.isEmpty()) { throw new SerializationException(this.type, "Can't serialize map value when other value is directly serialized as list."); } } @SuppressWarnings("unchecked") @Nullable protected <T> String toString(@Nullable T object, @Nullable Class<T> type) { if (object == null) { return null; } if ((type == null) || (type == Object.class)) { type = (Class<T>) object.getClass(); } if (DioriteReflectionUtils.getWrapperClass(type).equals(Boolean.class)) { if ((Boolean) object) { return this.trueValue; } else { return this.falseValue; } } return object.toString(); } @Override public void setTrueValue(String str) { this.trueValue = str; } @Override public void setFalseValue(String str) { this.falseValue = str; } @Override public Serialization getSerializationInstance() { return this.serialization; } protected DocumentComments addComments(Class<?> type, String... key) { DocumentComments cached = this.scannedCommentsClasses.get(type); if (cached != null) { return cached; } for (int i = 0; i < key.length; i++) { if (key[i].isEmpty()) { key[i] = CommentsNode.ANY; } } DocumentComments comments = Serialization.getGlobal().getCommentsManager().getComments(type); this.comments.join(key, comments); this.scannedCommentsClasses.put(type, comments); return comments; } protected void joinComments(String key, @Nullable DocumentComments comments) { if ((comments == null) || ! this.joinedKeys.add(key)) { return; } if (key.isEmpty()) { this.comments.join(CommentsNode.ANY, comments); } else { this.comments.join(key, comments); } } private DocumentComments addComments(Class<?> type, String key) { DocumentComments cached = this.scannedCommentsClasses.get(type); if (cached != null) { return cached; } DocumentComments comments = Serialization.getGlobal().getCommentsManager().getComments(type); if (key.isEmpty()) { this.comments.join(CommentsNode.ANY, comments); } else { this.comments.join(key, comments); } this.scannedCommentsClasses.put(type, comments); return comments; } @Override public <T> void add(String key, @Nullable T value) { this.validateMap(key); if (value == null) { this.dataMap.put(key, null); return; } if (Serialization.isSimple(value)) { this.dataMap.put(key, this.toString(value, null)); return; } Class<?> valueClass = value.getClass(); DocumentComments comments = this.addComments(valueClass, key); if (! this.serialization.isSerializable(value)) { if (this.serialization.isStringSerializable(value)) { this.dataMap.put(key, this.serialization.serializeToString(value)); return; } if (this.serialization.canBeSerialized(valueClass)) { this.dataMap.put(key, this.serialize(value, comments)); this.joinComments(key, comments); return; } throw new SerializationException(this.type, "Given value must be serializable! key: " + key + ", value: " + value); } this.dataMap.put(key, this.serialize(value, comments)); this.joinComments(key, comments); } @Override public <T> void add(String key, @Nullable T value, Class<T> type) { this.validateMap(key); if (value == null) { this.dataMap.put(key, null); return; } if (Serialization.isSimple(value)) { this.dataMap.put(key, this.toString(value, type)); return; } DocumentComments comments = this.addComments(value.getClass(), key); if (! this.serialization.isSerializable(type)) { if (this.serialization.isStringSerializable(type)) { this.dataMap.put(key, this.serialization.serializeToString(type, value)); return; } if (this.serialization.canBeSerialized(value.getClass())) { this.dataMap.put(key, this.serialize(type, value, comments)); this.joinComments(key, comments); return; } throw new SerializationException(this.type, "Given value must be serializable! key: " + key + ", value: (" + type.getName() + ") -> " + value); } this.dataMap.put(key, this.serialize(type, value, comments)); this.joinComments(key, comments); } @Override public <T> void addMappedList(String key, Class<T> type, @Nullable Collection<? extends T> value, Function<T, String> mapper) { this.validateMap(key); this.addComments(type, key, CommentsNode.ANY); if (value == null) { this.add(key, null); return; } Map<String, Object> valueMap = new LinkedHashMap<>(value.size()); for (T t : value) { if (t == null) { valueMap.put(key, null); continue; } DocumentComments comments = this.addComments(t.getClass(), key, CommentsNode.ANY); if (Serialization.isSimple(t)) { valueMap.put(key, this.toString(t, type)); continue; } valueMap.put(mapper.apply(t), this.serialize(type, t, comments)); this.joinComments(key, comments); } this.dataMap.put(key, valueMap); } @Override public <T> void addCollection(String key, @Nullable Collection<? extends T> value, Class<T> type) { this.validateList(key); this.addComments(type, key); List<Object> result; if (key.isEmpty()) { Validate.notNull(value); result = this.dataList; } else { if (value == null) { this.add(key, null); return; } result = new ArrayList<>(value.size()); } for (T t : value) { if (t == null) { result.add(null); continue; } if (Serialization.isSimple(t)) { result.add(this.toString(t, type)); continue; } DocumentComments comments = this.addComments(t.getClass(), key); result.add(this.serialize(type, t, comments)); this.joinComments(key, comments); } if (! key.isEmpty()) { this.dataMap.put(key, result); } } @Override public <T> void addMapAsList(String key, @Nullable Map<?, ? extends T> value, Class<T> type) { this.validateList(key); this.addComments(type, key); List<Object> result; if (key.isEmpty()) { Validate.notNull(value); result = this.dataList; } else { if (value == null) { this.add(key, null); return; } result = new ArrayList<>(value.size()); } for (Entry<?, ? extends T> entry : value.entrySet()) { T entryValue = entry.getValue(); DocumentComments comments = null; if (entryValue != null) { comments = this.addComments(entryValue.getClass(), key); } if (Serialization.isSimple(entryValue)) { result.add(this.toString(entryValue, type)); continue; } result.add(this.serialize(type, entryValue, comments)); this.joinComments(key, comments); } if (! key.isEmpty()) { this.dataMap.put(key, result); } } @SuppressWarnings({"unchecked", "rawtypes"}) @Override public <T> void addMapAsListWithKeys(String key, @Nullable Map<?, ? extends T> value, Class<T> type, String keyPropertyName) { this.validateList(key); this.addComments(type, key); List<Object> result; if (key.isEmpty()) { Validate.notNull(value); result = this.dataList; } else { if (value == null) { this.add(key, null); return; } result = new ArrayList<>(value.size()); } for (Entry<?, ? extends T> entry : value.entrySet()) { T entryValue = entry.getValue(); DocumentComments comments = null; if (entryValue != null) { comments = this.addComments(entryValue.getClass(), key); } Object o = this.serialize(type, entryValue, comments); this.joinComments(key, comments); if (o instanceof Map) { Object entryKey = entry.getKey(); if (entryKey != null) { this.addComments(entryKey.getClass(), key); } String keyStr; if (Serialization.isSimple(entryKey)) { keyStr = this.toString(entryKey, null); } else if (this.serialization.isStringSerializable(entryKey)) { keyStr = this.serialization.serializeToString(entryKey); } else { throw new SerializationException(this.type, "(key: '" + key + "') Invalid, not serializable, key found" + entryKey); } try { Map collection = YamlCollectionCreator.createCollection(o.getClass(), ((Map) o).size()); collection.put(keyPropertyName, keyStr); collection.putAll(((Map) o)); o = collection; } catch (Exception e) { e.printStackTrace(); ((Map) o).put(keyPropertyName, keyStr); } } else { throw new SerializationException(this.type, "(key: '" + key + "') Expected map but found: " + o); } result.add(o); } if (! key.isEmpty()) { this.dataMap.put(key, result); } } @Override public <K, T> void addMap(String key, @Nullable Map<? extends K, ? extends T> value, Class<K> keyType, Class<T> type) { this.validateMap(key); this.addComments(type, key, CommentsNode.ANY); this.addComments(keyType, key); Map<Object, Object> resultMap; if (key.isEmpty()) { Validate.notNull(value); resultMap = this.dataMap; } else { if (value == null) { this.add(key, null); return; } resultMap = new LinkedHashMap<>(value.size()); } for (Entry<? extends K, ? extends T> entry : value.entrySet()) { K entryKey = entry.getKey(); T entryValue = entry.getValue(); if (entryKey != null) { this.addComments(entryKey.getClass(), key); } DocumentComments comments = null; if (entryValue != null) { comments = this.addComments(entryValue.getClass(), key, CommentsNode.ANY); } if (Serialization.isSimple(entryKey)) { if (Serialization.isSimple(entryValue)) { resultMap.put(entryKey, this.toString(entryValue, type)); continue; } resultMap.put(entryKey, this.serialize(type, entryValue, comments)); this.joinComments(key, comments); continue; } resultMap.put(this.serialization.serializeToString(keyType, entryKey), this.serialize(type, entryValue, comments)); this.joinComments(key, comments); } if (! key.isEmpty()) { this.dataMap.put(key, resultMap); } } @Override public <K, T> void addMap(String key, @Nullable Map<? extends K, ? extends T> value, Class<T> type) { this.validateMap(key); this.addComments(type, key, CommentsNode.ANY); Map<Object, Object> resultMap; if (key.isEmpty()) { Validate.notNull(value); resultMap = this.dataMap; } else { if (value == null) { this.add(key, null); return; } resultMap = new LinkedHashMap<>(value.size()); } for (Entry<? extends K, ? extends T> entry : value.entrySet()) { K entryKey = entry.getKey(); T entryValue = entry.getValue(); if (entryKey != null) { this.addComments(entryKey.getClass(), key); } DocumentComments comments = null; if (entryValue != null) { comments = this.addComments(entryValue.getClass(), key, CommentsNode.ANY); } if (Serialization.isSimple(entryKey)) { if (Serialization.isSimple(entryValue)) { resultMap.put(entryKey, this.toString(entryValue, type)); continue; } resultMap.put(entryKey, this.serialize(type, entryValue, comments)); this.joinComments(key, comments); continue; } resultMap.put(this.serialization.serializeToString(entryKey), this.serialize(type, entryValue, comments)); this.joinComments(key, comments); } if (! key.isEmpty()) { this.dataMap.put(key, resultMap); } } @Override public <K, T> void addMap(String key, @Nullable Map<? extends K, ? extends T> value, Class<T> type, Function<K, String> keyMapper) { this.validateMap(key); this.addComments(type, key, CommentsNode.ANY); Map<Object, Object> resultMap; if (key.isEmpty()) { Validate.notNull(value); resultMap = this.dataMap; } else { if (value == null) { this.add(key, null); return; } resultMap = new LinkedHashMap<>(value.size()); } for (Entry<? extends K, ? extends T> entry : value.entrySet()) { K entryKey = entry.getKey(); T entryValue = entry.getValue(); if (entryKey != null) { this.addComments(entryKey.getClass(), key); } DocumentComments comments = null; if (entryValue != null) { comments = this.addComments(entryValue.getClass(), key, CommentsNode.ANY); } if (Serialization.isSimple(entryKey)) { if (Serialization.isSimple(entryValue)) { resultMap.put(entryKey, this.toString(entryValue, type)); continue; } resultMap.put(entryKey, this.serialize(type, entryValue, comments)); this.joinComments(key, comments); continue; } resultMap.put(keyMapper.apply(entryKey), this.serialize(type, entryValue, comments)); this.joinComments(key, comments); } if (! key.isEmpty()) { this.dataMap.put(key, resultMap); } } Object rawValue() { if (! this.dataList.isEmpty()) { return this.dataList; } return this.dataMap; } static SimpleSerializationData createSerializationData(SerializationType serializationType, Serialization serialization, Class<?> type) { if (serializationType == SerializationType.YAML) { return new YamlSerializationData(serialization, type); } return new SimpleSerializationData(serializationType, serialization, type); } }