/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.retention; import nova.core.util.ReflectionUtil; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.List; import java.util.Objects; /** * Classes with this interface declare ability to store and load itself. * Therefore, classes using this interface must have an empty constructor for new instantiation from load. */ public interface Storable { /** * Saves all the data of this object. * See {@link Data} for what data is storable. * * The default implementation saves all fields tagged with {@link Store @Store}. * * @param data The data object to put values in. */ default void save(Data data) { ReflectionUtil.forEachRecursiveAnnotatedField(Store.class, getClass(), (field, annotation) -> { try { field.setAccessible(true); String name = annotation.key(); if (name.isEmpty()) { name = field.getName(); assert !Data.ILLEGAL_SUFFIX.matcher(name).find(); } data.put(name, field.get(this)); field.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } }); } /** * Loads all the data for this object. * See {@link Data} for what data is storable. * * The default implementation loads all fields tagged with {@link Store @Store}. * * @param data The data object to load values from. */ default void load(Data data) { ReflectionUtil.forEachRecursiveAnnotatedField(Store.class, getClass(), (field, annotation) -> { String name = annotation.key(); if (name.isEmpty()) { name = field.getName(); assert !Data.ILLEGAL_SUFFIX.matcher(name).find(); } if (data.containsKey(name)) { try { field.setAccessible(true); Class<?> type = field.getType(); Object fieldValue = field.get(this); Object value = data.get(name); if (Storable.class.isAssignableFrom(type) || value instanceof Data) { if (fieldValue instanceof Storable && value instanceof Data) { //We already have an instance. Don't need to create the object. ((Storable) fieldValue).load((Data) value); } else if (Collection.class.isAssignableFrom(type)) { if (List.class.isAssignableFrom(type)) { field.set(this, Data.unserialize((Data) value)); } else { Collection<?> collection = (Collection) type.newInstance(); field.set(this, collection.addAll(Data.unserialize((Data) value))); } } else { field.set(this, Data.unserialize((Data) value)); } } else if (BigInteger.class.isAssignableFrom(type)) { if (value instanceof BigInteger) field.set(this, value); else field.set(this, new BigInteger(Objects.toString(value))); } else if (BigDecimal.class.isAssignableFrom(type)) { if (value instanceof BigDecimal) field.set(this, value); else field.set(this, new BigDecimal(Objects.toString(value))); } else if (value instanceof BigInteger) { if (int.class.isAssignableFrom(type) || Integer.class.isAssignableFrom(type)) field.set(this, ((BigInteger) value).intValue()); else field.set(this, ((BigInteger) value).longValue()); } else if (value instanceof BigDecimal) { field.set(this, ((BigDecimal) value).doubleValue()); } else { field.set(this, value); } field.setAccessible(false); } catch (Exception e) { e.printStackTrace(); } } }); } }