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

edu.utdallas.objectutils.AbstractWrappedCompositeObject Maven / Gradle / Ivy

Go to download

A library of utilities for serialization and wrapping of arbitrary objects to have correctly implemented "equals" and "hashCode" methods.

There is a newer version: 1.3
Show newest version
package edu.utdallas.objectutils;

/*
 * #%L
 * object-utils
 * %%
 * Copyright (C) 2019 The University of Texas at Dallas
 * %%
 * 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.
 * #L%
 */

import edu.utdallas.objectutils.utils.OnDemandClass;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * Base class for object arrays and proper objects.
 * These objects are composite in a sense they have elements/fields that
 * might be objects in turn.
 *
 * @author Ali Ghanbari ([email protected])
 */
public abstract class AbstractWrappedCompositeObject extends AbstractWrappedReference {
    protected OnDemandClass type; // array element type or the object type

    protected Wrapped[] values; // field values or array elements

    /* We are using this to unwrap cyclic object graphs */
    /* We consult this hash-table to find out if we have already unwrapped a wrapped object */
    /* Note that we use object addresses as key for performance reasons */
    private static final Map UNWRAPPED_OBJECTS;

    static {
        UNWRAPPED_OBJECTS = new HashMap<>();
    }

    protected AbstractWrappedCompositeObject(Class type, Wrapped[] values) {
        super(); // obtain address
        this.values = values;
        this.type = type == null ? null : OnDemandClass.of(type);
    }

    /**
     * Returns the type of array elements, in case this
     * represents an object array, or simply object type that is wrapped
     * by this.
     *
     * @return Type
     */
    public Class getType() {
        return this.type.retrieveClass();
    }

    public void setType(Class type) {
        this.type = OnDemandClass.of(type);
    }

    @Override
    public String getTypeName() {
        return this.type.getName();
    }

    /**
     * Returns the values of array elements, in case this
     * represents an object array, or field values of the object that
     * is wrapped by this.
     * @return Field values or array element values
     */
    public Wrapped[] getValues() {
        return values;
    }

    public void setValues(Wrapped[] values) {
        this.values = values;
    }

    @Override
    public Object unwrap() throws Exception {
        final Object rawObject = createRawObject();
        return unwrap(rawObject);
    }

    @Override
    public Object unwrap(Object template) throws Exception {
        UNWRAPPED_OBJECTS.clear();
        template = rectifyTemplate(template);
        if (template == null) {
            throw new IllegalArgumentException("failed to rectify the given template");
        }
        return unwrap0(template);
    }

    // this is a newly added method. it is intended to create a raw object in case
    // template is null. it also creates a new array in case of arrays with
    // non-matching lengths. the method shall return null if it fails to rectify the
    // template.
    protected abstract Object rectifyTemplate(Object template);

    protected abstract Object createRawObject();

    protected abstract void resetCursor(); // objects create iterator; arrays just reset to -1

    protected abstract void advanceCursor();

    protected abstract boolean skippedAtCursor(); // arrays always return false

    protected abstract void setAtCursor(Object rawObject, Object value) throws Exception;

    protected abstract Object getAtCursor(Object rawObject) throws Exception;

    private Object unwrap0(final Object template) throws Exception {
        UNWRAPPED_OBJECTS.put(this.address, template);
        resetCursor();
        for (final Wrapped wrappedValue : this.values) {
            advanceCursor();
            while (skippedAtCursor()) {
                advanceCursor();
            }
            // we ignore some fields based on what the client has requested.
            // ignored fields shall continue to have their values they used to have
            // in the template object. this means that if the template object is not
            // provided by the client, those fields shall have their default values.
            // note that an array element cannot be 'null' as filtering only
            // applies to fields.
            if (wrappedValue == null) {
                continue;
            }
            final Object value;
            if (wrappedValue instanceof AbstractWrappedReference) {
                final AbstractWrappedReference wrappedReference =
                        (AbstractWrappedReference) wrappedValue;
                final Object targetObject = UNWRAPPED_OBJECTS.get(wrappedReference.address);
                if (targetObject != null) { // reusable object?
                    value = targetObject;
                } else if (wrappedReference instanceof AbstractWrappedCompositeObject) {
                    final AbstractWrappedCompositeObject compositeObject =
                            (AbstractWrappedCompositeObject) wrappedReference;
                    Object originalObject = getAtCursor(template);
                    // assert wrapped value does not represent null value
                    if (originalObject == null) {
                        originalObject = compositeObject.createRawObject();
                    } else {
                        final Class originalClass;
                        if (compositeObject instanceof WrappedObjectArray) {
                            originalClass = originalObject.getClass().getComponentType();
                        } else {
                            originalClass = originalObject.getClass();
                        }
                        if (originalClass != compositeObject.type.retrieveClass()) {
                            // should we change the type of object?
                            originalObject = compositeObject.createRawObject();
                        }
                    }
                    value = compositeObject.unwrap0(originalObject);
                } else { // basic-typed array
                    Object originalObject = getAtCursor(template);
                    if (originalObject == null) {
                        value = wrappedReference.unwrap();
                    } else {
                        value = wrappedReference.unwrap(originalObject);
                    }
                }
                UNWRAPPED_OBJECTS.put(wrappedReference.address, value);
            } else {
                // if the wrapped value represents null or a primitive-typed object
                value = wrappedValue.unwrap();
            }
            setAtCursor(template, value);
        }
        return template;
    }

    @Override
    public int hashCode() {
        return this.type.hashCode();
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object == null || getClass() != object.getClass()) {
            return false;
        }
        AbstractWrappedCompositeObject that = (AbstractWrappedCompositeObject) object;
        final Queue workList1 = new LinkedList<>();
        final Queue workList2 = new LinkedList<>();
        final Set visitedNodes1 = new HashSet<>();
        workList1.offer(this);
        workList2.offer(that);
        while (!workList1.isEmpty()) {
            final Wrapped node1 = workList1.poll();
            final Wrapped node2 = workList2.poll();
            if (node1 == null || node2 == null) {
                if (node1 == node2) { // fields in both objects are ignored
                    continue;
                } else {
                    return false;
                }
            }
            /* assume node1 != null && node2 != null */
            if (node1.getClass() != node2.getClass()) {
                return false;
            }
            if (node1 instanceof AbstractWrappedCompositeObject) {
                /* this implies node2 instanceof AbstractWrappedObject */
                final AbstractWrappedCompositeObject wrappedObject1 =
                        ((AbstractWrappedCompositeObject) node1);
                final AbstractWrappedCompositeObject wrappedObject2 =
                        ((AbstractWrappedCompositeObject) node2);
                if (!wrappedObject1.getTypeName().equals(wrappedObject2.getTypeName())) {
                    return false;
                }
                visitedNodes1.add(wrappedObject1.getAddress());
                final Wrapped[] values1 = wrappedObject1.getValues();
                final Wrapped[] values2 = wrappedObject2.getValues();
                if (values1.length != values2.length) {
                    return false;
                }
                for (int i = 0; i < values1.length; i++) {
                    final Wrapped value = values1[i];
                    if (value instanceof AbstractWrappedCompositeObject) {
                        if (visitedNodes1.contains(value.getAddress())) {
                            continue;
                        }
                    }
                    workList1.offer(value);
                    workList2.offer(values2[i]);
                }
            } else if (!node1.equals(node2)) {
                return false;
            }
        }
        return workList2.isEmpty();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy