All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thegoate.utils.fill.serialize.Nanny Maven / Gradle / Ivy

/*
 * Copyright (c) 2017. Eric Angeli
 *
 *  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 com.thegoate.utils.fill.serialize;

import com.thegoate.Goate;
import com.thegoate.HealthMonitor;
import com.thegoate.logging.BleatBox;
import com.thegoate.logging.BleatFactory;
import com.thegoate.reflection.GoateReflection;
import com.thegoate.utils.compare.Compare;
import com.thegoate.utils.compare.CompareUtility;

import javax.swing.text.html.parser.Entity;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Created by Eric Angeli on 7/3/2020.
 */
public class Nanny implements HealthMonitor, TypeT, Cloneable {

    @GoateIgnore
    protected BleatBox LOG = BleatFactory.getLogger(getClass());

    @GoateIgnore
    protected String equalityCheck = "==";

    @GoateIgnore
    private List goateType = new ArrayList<>();

    @GoateIgnore
    private Goate health = new Goate();

    public void setGoateType(List type) {
        this.goateType = type;
    }

    public Nanny equalityCheck(String check) {
        this.equalityCheck = check;
        return this;
    }

    @Override
    public Class goateType(int index) {
        Class c = Object.class;
        if (index < goateType.size()) {
            c = goateType.get(index);
        }
        return c;
    }

    /**
     * Resets the health collection to a new empty one.
     */
    protected void resetHealth() {
        health = new Goate();
    }

    public void reportHealth(String id, Object message) {
        health.put(id, message);
    }

    /**
     * This returns and resets the current health report.
     *
     * @return Goate collection containing any reported health issues.
     */
    public Goate healthCheck() {
        Goate report = health;
        resetHealth();
        return report;
    }

    @Override
    public boolean equals(Object compare) {
        boolean result = true;
        if (compare == null) {
            result = false;
            reportHealth(getClass().getSimpleName(), "{ initialized instance != null }");
        }
        if (getClass().isInstance(compare)) {
            GoateReflection gr = new GoateReflection();
            for (Map.Entry field : gr.findFields(getClass()).entrySet()) {
                IgnoreOnCompare ignore = field.getValue().getAnnotation(IgnoreOnCompare.class);
                if (ignore == null) {
                    boolean accessible = field.getValue().isAccessible();
                    try {
                        field.getValue().setAccessible(true);
                        Object actual = field.getValue().get(this);
                        Object expected = field.getValue().get(compare);
                        CompareUtility check = new Compare(actual).to(expected).using(equalityCheck);
                        if (!check.evaluate()) {
                            result = false;
                            String checkHealth = check.healthCheck().toString("\t\t", "", true);
                            String label = actual.getClass().getSimpleName() + " " + field.getKey();
                            if (checkHealth.isEmpty()) {
                                //reportHealth("field mismatch ##",  label + " {" + actual + " != " + expected + "}");
                                reportHealth(label, " {" + actual + " != " + expected + "}");
                            } else {
                                //checkHealth.replace("%_goate_t_%", "%_goate_t_%%_goate_t_%").replace("\t","%_goate_t_%");
                                checkHealth = check.healthCheck().pad(checkHealth + "\n\t}", "\t");
                                //reportHealth("field mismatch ##", label + " {\n" + checkHealth);
                                reportHealth(label, " {\n" + checkHealth);
                            }
                        }
                    } catch (Exception e) {
                        reportHealth("field access ##", "problem accessing field: " + e.getMessage());
                    } finally {
                        field.getValue().setAccessible(accessible);
                    }
                } else {
                    LOG.debug("Pojo Equals", "Ignored field: " + field.getKey());
                }
            }
        } else {
            result = false;
        }
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName()).append(":{");
        GoateReflection gr = new GoateReflection();
        boolean notFirst = false;
        for (Map.Entry field : gr.findFields(getClass()).entrySet()) {
            if (notFirst) {
                sb.append(", ");
            }
            sb.append(field.getKey()).append(":");
            boolean accessible = field.getValue().isAccessible();
            Object fieldValue = "Exception: could not get toString";
            try {
                field.getValue().setAccessible(true);
                fieldValue = field.getValue().get(this);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            sb.append(fieldValue);
            notFirst = true;
        }
        sb.append("}");
        return sb.toString();
    }

    public Object clone() throws CloneNotSupportedException {
        Object c = super.clone();
        GoateReflection gr = new GoateReflection();
        Map fields = gr.findFields(getClass());
        fields.entrySet().parallelStream().forEach(field -> copy(field, gr, c));
        return c;
    }

    protected void copy(Map.Entry field, GoateReflection gr, Object clone) {
        Field value = field.getValue();
        if (!gr.isPrimitive(field.getValue().getType())) {
            try {
                if (Cloneable.class.isAssignableFrom(value.getType())) {
                    boolean acc = field.getValue().isAccessible();
                    boolean isAcc = acc;
                    try {
                        field.getValue().setAccessible(true);
                        isAcc = field.getValue().isAccessible();
                    } catch (Exception e) {
                        LOG.debug("Serializer", "Failed to make " + field.getValue().getName() + " accessible, skipping for serialization unless it is already accessible");
                    }
                    if (isAcc) {
                        Object og = value.get(this);
                        if (og != null) {
                            Method doClone = gr.findMethod(value.getType(), "clone");
                            boolean macc = doClone.isAccessible();
                            doClone.setAccessible(macc);
                            field.getValue().set(clone, doClone.invoke(og));
                            doClone.setAccessible(macc);
                        }
                    }
                    try {
                        field.getValue().setAccessible(acc);
                    } catch (Exception exception) {
                        LOG.debug("Serializer", "Unable to reset accessibility: " + field.getKey());
                    }
                } else {
                    LOG.debug("Clone: " + field.getKey(), "Object in pojo was not a type that is cloneable, so this was just a shallow copy.");
                }
            } catch (IllegalAccessException | InvocationTargetException cloneException) {
                LOG.debug("Clone: " + field.getKey(), "Couldn't clone properly, so if you notice some odd behavior, it may be because java lies about not having pointers: " + cloneException.getMessage());
            }
        }
    }

    /**
     * I apologize for what comes below. This is a crazy idea, even if it works, and the code is a mess. I got it to a point
     * where the test cases are passing and did not bother to clean up the code to make it more readable. I am sorry.
     * Especially to my future self who will like be the one that has to touch this code.
     * The gist of the below is to provide a clone of the object, but setting the "ignored" fields to either
     * a default primitive value or null. Nested fields are only processed for lists, collections, arrays, maps, or kid objects.
     *
     * @param fieldNames the list of Strings of fields to ignore
     * @return the cloned object with ignored fields set to a defined default.
     * @throws CloneNotSupportedException
     */
    public Object cloneButIgnore(List fieldNames) throws CloneNotSupportedException {
        Object clone = this.clone();
        GoateReflection gr = new GoateReflection();
        Map fields = gr.findFields(getClass());
        if (fieldNames != null && fieldNames.size() > 0) {
            for (String fieldName : fieldNames) {
                if (fieldName.contains(".")) {
                    String baseFieldName = fieldName.substring(0, fieldName.indexOf("."));
                    if (fields.containsKey(baseFieldName)) {
                        Field field = fields.get(baseFieldName);
                        boolean accessible = field.isAccessible();
                        field.setAccessible(true);
                        List ignore = new ArrayList<>();
                        ignore.add(fieldName.replaceFirst(baseFieldName + "\\.", "").replaceFirst("\\*\\.", ""));
                        try {
                            Object fieldValue = field.get(clone);
                            if (gr.isCollectionOrMap(field)) {
                                //set support not actually implemented yet, so set field types are simply not processed at this time.
                                if (Map.class.isAssignableFrom(field.getType())) {
                                    for (Object entryO : ((Map) fieldValue).entrySet()) {
                                        if (entryO instanceof Map.Entry) {
                                            Map.Entry entry = (Map.Entry) entryO;
                                            if (gr.isPrimitiveOrNumerical(entry.getValue())) {
                                                entry.setValue(gr.getDefaultPrimitive(entry.getValue().getClass()));
                                            } else {
                                                if (ignore.get(0).isEmpty()) {
                                                    entry.setValue(null);
                                                } else {
                                                    if (entry.getValue() instanceof Kid) {
                                                        entry.setValue(((Kid) entry.getValue()).cloneButIgnore(ignore));
                                                    } else {
                                                        LOG.warn("Clone but ignore", "sorry, I can only ignore nested fields in maps if the containing object is an instance of Kid: " + fieldName);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } else if (Collection.class.isAssignableFrom(field.getType())) {
                                    for (Object entry : (Collection) fieldValue) {
                                        if (gr.isPrimitiveOrNumerical(entry)) {
                                        } else {
                                            if (ignore.get(0).isEmpty()) {
                                            } else {
                                                if (entry instanceof Kid) {
                                                    String secondBase = ignore.get(0).split("\\.")[0];
                                                    Map secondField = gr.findFields(entry.getClass());//entry.setValue(((Kid) entry.getValue()).cloneButIgnore(ignore));
                                                    if (secondField.containsKey(secondBase)) {
                                                        Field field2 = secondField.get(secondBase);
                                                        boolean accessible2 = field2.isAccessible();
                                                        field2.setAccessible(true);
                                                        if (ignore.get(0).contains(".")) {
                                                            Object field2Value = field2.get(entry);
                                                            if (field2Value instanceof Kid) {
                                                                List ignore2 = new ArrayList<>();
                                                                ignore2.add(ignore.get(0).replaceFirst(secondBase + "\\.", ""));
                                                                field2.set(entry, ((Kid) field2Value).cloneButIgnore(ignore2));
                                                            }
                                                        } else {
                                                            if (gr.isPrimitiveNumber(field2.getType())) {
                                                                field2.set(entry, gr.getDefaultPrimitive(field2.getType()));
                                                            } else {
                                                                field2.set(entry, null);
                                                            }
                                                        }
                                                        field2.setAccessible(accessible2);
                                                    }
                                                } else {
                                                    LOG.warn("Clone but ignore", "sorry, I can only ignore nested fields in maps if the containing object is an instance of Kid: " + fieldName);
                                                }
                                            }
                                        }
                                    }
                                } else if (field.getType().isArray()) {
                                    for (Object entry : (Object[]) fieldValue) {
                                        if (gr.isPrimitiveOrNumerical(entry)) {
                                        } else {
                                            if (ignore.get(0).isEmpty()) {
                                            } else {
                                                if (entry instanceof Kid) {
                                                    String secondBase = ignore.get(0).split("\\.")[0];
                                                    Map secondField = gr.findFields(entry.getClass());//entry.setValue(((Kid) entry.getValue()).cloneButIgnore(ignore));
                                                    if (secondField.containsKey(secondBase)) {
                                                        Field field2 = secondField.get(secondBase);
                                                        boolean accessible2 = field2.isAccessible();
                                                        field2.setAccessible(true);
                                                        if (ignore.get(0).contains(".")) {
                                                            Object field2Value = field2.get(entry);
                                                            if (field2Value instanceof Kid) {
                                                                List ignore2 = new ArrayList<>();
                                                                ignore2.add(ignore.get(0).replaceFirst(secondBase + "\\.", ""));
                                                                field2.set(entry, ((Kid) field2Value).cloneButIgnore(ignore2));
                                                            }
                                                        } else {
                                                            if (gr.isPrimitiveNumber(field2.getType())) {
                                                                field2.set(entry, gr.getDefaultPrimitive(field2.getType()));
                                                            } else {
                                                                field2.set(entry, null);
                                                            }
                                                        }
                                                        field2.setAccessible(accessible2);
                                                    }
                                                } else {
                                                    LOG.warn("Clone but ignore", "sorry, I can only ignore nested fields in maps if the containing object is an instance of Kid: " + fieldName);
                                                }
                                            }
                                        }
                                    }
                                }
                            } else {
                                if (fieldValue instanceof Kid) {
                                    field.set(clone, ((Kid) fieldValue).cloneButIgnore(ignore));
                                } else {
                                    LOG.warn("Clone but ignore", "sorry, I can only ignore nested fields if the containing object is an instance of Kid: " + fieldName);
                                }
                            }
                        } catch (IllegalAccessException e) {
                            LOG.debug("Cloning and trying to ignore fields (primitive)", "This should never happen because it was forced to be accessible: " + e.getMessage(), e);
                        }
                        field.setAccessible(accessible);
                    }
                } else {
                    if (fields.containsKey(fieldName)) {
                        Field field = fields.get(fieldName);
                        boolean accessible = field.isAccessible();
                        field.setAccessible(true);
                        //this is a crude hack, instead of trying to programatically ignore fields, just set the ignored fields to default value.
                        if (gr.isPrimitive(field.getType())) {
                            try {
                                field.set(clone, gr.getDefaultPrimitive(gr.primitiveType(field.get(clone))));
                            } catch (IllegalAccessException e) {
                                LOG.debug("Cloning and trying to ignore fields (primitive)", "This should never happen because it was forced to be accessible: " + e.getMessage(), e);
                            }
                        } else {
                            try {
                                field.set(clone, null);
                            } catch (IllegalAccessException e) {
                                LOG.debug("Cloning and trying to ignore fields", "This should never happen because it was forced to be accessible: " + e.getMessage(), e);
                            }
                        }
                        field.setAccessible(accessible);
                    }
                }
            }
        }
        return clone;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy