001package org.conqat.lib.commons.serialization.utils;
002
003import java.io.IOException;
004import java.util.function.Function;
005
006import org.conqat.lib.commons.assertion.CCSMAssert;
007import org.conqat.lib.commons.serialization.SerializedEntityPool;
008import org.conqat.lib.commons.serialization.classes.SerializedBooleanField;
009import org.conqat.lib.commons.serialization.classes.SerializedClass;
010import org.conqat.lib.commons.serialization.classes.SerializedFieldBase;
011import org.conqat.lib.commons.serialization.classes.SerializedLongField;
012import org.conqat.lib.commons.serialization.classes.SerializedObjectField;
013import org.conqat.lib.commons.serialization.objects.SerializedObject;
014import org.conqat.lib.commons.serialization.objects.SerializedStringObject;
015
016/**
017 * Utility class to consistently migrate field names of a
018 * {@link SerializedClass} with their corresponding field values in all
019 * {@link SerializedObject} of the {@link SerializedClass}.
020 */
021public class SerializedClassFieldMigrator {
022
023        private final SerializedEntityPool entityPool;
024
025        public SerializedClassFieldMigrator(SerializedEntityPool entityPool) {
026                this.entityPool = entityPool;
027        }
028
029        /**
030         * Renames the field of the {@link SerializedClass} and adjusts the
031         * corresponding field values in all {@link SerializedObject}s of the
032         * {@link SerializedClass}.
033         */
034        public void renameField(String serializedClassName, String oldFieldName, String newFieldName) {
035                SerializedClass serializedClass = entityPool.findClass(serializedClassName);
036
037                if (serializedClass == null) {
038                        return;
039                }
040
041                SerializedFieldBase field = serializedClass.getField(oldFieldName);
042
043                // Can happen in case migrations occur across multiple file system versions with
044                // one migration to add the field being missing.
045                if (field != null) {
046                        field.setName(newFieldName);
047                }
048        }
049
050        /**
051         * Updates the {@link String} value of a field with type {@link String}
052         * according to the field updater function.
053         */
054        public void updateStringFieldValue(String serializedClassName, String fieldName,
055                        Function<String, String> fieldValueUpdater) throws IOException {
056                SerializedClass serializedClass = entityPool.findClass(serializedClassName);
057
058                if (serializedClass == null) {
059                        return;
060                }
061
062                for (SerializedObject serializedObject : SerializedEntityUtils.findInstancesOf(serializedClass, entityPool)) {
063                        int stringObjectHandle = (int) serializedObject.getFieldValue(fieldName);
064                        String oldValue = entityPool.getEntity(stringObjectHandle, SerializedStringObject.class).getValue();
065                        String newValue = fieldValueUpdater.apply(oldValue);
066
067                        serializedObject.setFieldValue(fieldName, new SerializedStringObject(newValue, entityPool).getHandle());
068                }
069        }
070
071        /**
072         * Adds a new {@link Boolean} field with a default value set in all
073         * {@link SerializedObject}s of the {@link SerializedClass}. The field can't
074         * exist yet.
075         */
076        public void addBooleanField(String serializedClassName, String fieldName, boolean defaultValue) throws IOException {
077                addField(serializedClassName, fieldName, new SerializedBooleanField(fieldName), defaultValue);
078        }
079
080
081        /**
082         * Adds a new long integer field with a default value set in all
083         * {@link SerializedObject}s of the {@link SerializedClass}. The field can't
084         * exist yet.
085         */
086        public void addLongField(String serializedClassName, String fieldName, long defaultValue) throws IOException {
087                addField(serializedClassName, fieldName, new SerializedLongField(fieldName), defaultValue);
088        }
089
090        /**
091         * Adds a new {@link String} field with a default value set in all
092         * {@link SerializedObject}s of the {@link SerializedClass}. The field can't
093         * exist yet.
094         */
095        public void addStringField(String serializedClassName, String fieldName, String defaultValue) throws IOException {
096                addField(serializedClassName, fieldName,
097                                new SerializedObjectField(fieldName,
098                                                SerializedObjectField.createJvmNotationFromPlainClassName(String.class.getName())),
099                                new SerializedStringObject(defaultValue, entityPool).getHandle());
100        }
101
102        private void addField(String serializedClassName, String fieldName, SerializedFieldBase serializedField,
103                        Object defaultValue) throws IOException {
104                SerializedClass serializedClass = entityPool.findClass(serializedClassName);
105
106                if (serializedClass == null) {
107                        return;
108                }
109
110                CCSMAssert.isTrue(serializedClass.getField(fieldName) == null,
111                                () -> "Can't add the already existing field " + fieldName + " to class " + serializedClassName);
112                serializedClass.addField(serializedField);
113
114                for (SerializedObject entity : SerializedEntityUtils.findInstancesOf(serializedClass, entityPool)) {
115                        entity.setFieldValue(fieldName, defaultValue);
116                }
117        }
118}