/* * Copyright 2015 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.ideamindmap.settings; import com.igormaznitsa.mindmap.model.logger.Logger; import com.igormaznitsa.mindmap.model.logger.LoggerFactory; import com.intellij.util.xmlb.annotations.Property; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; public class DeclaredFieldsSerializer implements Serializable { private static final long serialVersionUID = -92387498231123L; private static final Logger LOGGER = LoggerFactory.getLogger(DeclaredFieldsSerializer.class); @Property private final Map<String, String> storage = new TreeMap<String, String>(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); public interface Converter { @Nullable Object fromString(@Nonnull Class<?> fieldType, @Nonnull String value); @Nonnull String asString(@Nonnull Class<?> fieldType, @Nonnull Object value); @Nullable Object provideDefaultValue(@Nonnull String fieldName, @Nonnull Class<?> fieldType); } private interface FieldVisitor { void visitField(@Nonnull Object instance, @Nonnull Field field, @Nonnull String fieldName, @Nonnull Class<?> fieldType); } public DeclaredFieldsSerializer() { } private static void visitFields(@Nonnull final Object object, @Nonnull final FieldVisitor visitor) { for (final Field f : object.getClass().getDeclaredFields()) { if ((f.getModifiers() & (Modifier.FINAL | Modifier.NATIVE | Modifier.STATIC | Modifier.TRANSIENT)) == 0) { f.setAccessible(true); visitor.visitField(object, f, f.getName(), f.getType()); } } } private static String makeName(final String fieldName, final Object value, final boolean needConverter) { final StringBuilder result = new StringBuilder(fieldName); if (value == null) result.append('.'); if (needConverter) result.append('@'); return result.toString(); } public DeclaredFieldsSerializer(@Nonnull final Object object, @Nullable final Converter converter) { visitFields(object, new FieldVisitor() { @Override public void visitField(@Nonnull Object instance, @Nonnull Field field, @Nonnull String fieldName, @Nonnull Class<?> fieldType) { try { final Object value = field.get(instance); if (fieldType.isPrimitive()) { if (fieldType == float.class) { storage.put(makeName(fieldName,value,false),Integer.toString((Float.floatToIntBits((Float)value)))); } else if (fieldType == double.class) { storage.put(makeName(fieldName,value,false),Long.toString((Double.doubleToLongBits((Double)value)))); } else storage.put(makeName(fieldName, value, false), value.toString()); } else if (fieldType == String.class) { storage.put(makeName(fieldName, value, false), value == null ? "" : (String) value); } else { if (converter == null) { throw new NullPointerException("Unexpected field type " + fieldType.getName() + ", provide converter!"); } else { final String converted = value == null ? null : converter.asString(fieldType, value); storage.put(makeName(fieldName, converted, true), converted); } } } catch (Exception ex) { LOGGER.error("Can't make data for field [" + fieldName + ']'); if (ex instanceof RuntimeException) { throw ((RuntimeException) ex); } else throw new Error("Can't serialize field [" + fieldName + ']', ex); } } }); } @Nullable public String get(@Nonnull final String fieldName) { final String storageFieldName = findStorageFieldName(fieldName); return storageFieldName == null ? null : this.storage.get(storageFieldName); } @Nullable public String findStorageFieldName(@Nonnull final String fieldName) { for (final String k : this.storage.keySet()) { if (k.equals(fieldName)) { return k; } else if (k.startsWith(fieldName) && (k.length() - fieldName.length()) < 3) { final String rest = k.substring(fieldName.length()); boolean onlySpecialChars = true; for (int i = 0; i < rest.length(); i++) { if (".@".indexOf(rest.charAt(i)) < 0) { onlySpecialChars = false; break; } } if (onlySpecialChars) return k; } } return null; } private static boolean isNull(final String fieldName) { return fieldName.indexOf('.') >= 0; } private static boolean doesNeedConverter(final String fieldName) { return fieldName.indexOf('@') >= 0; } public void fill(@Nonnull final Object instance, @Nullable final Converter converter) { visitFields(instance, new FieldVisitor() { @Override public void visitField(@Nonnull Object instance, @Nonnull Field field, @Nonnull String fieldName, @Nonnull Class<?> fieldType) { try { final String storageFieldName = findStorageFieldName(fieldName); if (storageFieldName == null) { if (converter == null) { throw new NullPointerException("Needed converter for non-saved field, to provide default value [" + fieldName + ']'); } else { field.set(instance, converter.provideDefaultValue(fieldName, fieldType)); } } else { final String value = get(storageFieldName); final boolean isNull = isNull(storageFieldName); final boolean needsConverter = doesNeedConverter(storageFieldName); if (isNull) { field.set(instance, null); } else if (needsConverter) { field.set(instance, converter.fromString(fieldType, value)); } else { if (fieldType == boolean.class) { field.set(instance, Boolean.parseBoolean(value)); } else if (fieldType == byte.class) { field.set(instance, Byte.parseByte(value)); } else if (fieldType == char.class) { field.set(instance, (char) Integer.parseInt(value)); } else if (fieldType == short.class) { field.set(instance, Short.parseShort(value)); } else if (fieldType == int.class) { field.set(instance, Integer.parseInt(value)); } else if (fieldType == long.class) { field.set(instance, Long.parseLong(value)); } else if (fieldType == float.class) { field.set(instance, Float.intBitsToFloat(Integer.parseInt(value))); } else if (fieldType == double.class) { field.set(instance, Double.longBitsToDouble(Long.parseLong(value))); } else if (fieldType == String.class) { field.set(instance, value); } else throw new Error("Unexpected primitive type [" + fieldName + " " + fieldType + ']'); } } } catch (Exception ex) { LOGGER.error("Can't fill field by data [" + fieldName + ']'); if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new Error("Unexpected exception for field processing [" + field + ']', ex); } } } }); } }