org.gachette.spi.graphprovider.collection.CollectionGraphProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.gachette.spi.graphprovider.collection.CollectionGraphProvider.java

Source

/*
 * Copyright 2013 Emeka Mosanya
 *
 * 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 org.gachette.spi.graphprovider.collection;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.gachette.instrumentation.GachetteProxy;
import org.gachette.spi.callprocessor.CallProcessor;
import org.gachette.spi.graphprovider.GraphProvider;
import org.gachette.value.Evaluator;
import org.gachette.value.MethodEvaluator;
import org.gachette.value.Value;
import org.gachette.value.ValueObserver;

import java.lang.reflect.Method;
import java.util.*;

public class CollectionGraphProvider implements GraphProvider {
    private final Map<Integer, Map<Integer, Set<Bind>>> binds = new HashMap<>();
    private final Map<Value, Integer> objects = new HashMap<>();
    private final Map<Integer, Set<Value>> values = new HashMap<>();

    private final Set<Value> nodes = new HashSet<>();
    private final Map<Integer, Value> nodeMap = new HashMap<>();
    private final Map<Value, Set<ValueObserver>> nodeObservers = new HashMap<>();

    private final Map<Value, Set<Value>> nodeParents = new HashMap<>();
    private final Map<Value, Set<Value>> nodeChildren = new HashMap<>();

    /*
    Class<?> cl = Class.forName("javax.swing.JLabel");
    Constructor<?> cons = cl.getConstructor(String.class);
    Object o = cons.newInstance("JLabel");
        
     */

    @Override
    public <M> void observe(Value<M> value, ValueObserver<M> observer) {
        Set<ValueObserver> observers = nodeObservers.get(value);
        if (observers == null) {
            observers = new HashSet<>();
            nodeObservers.put(value, observers);
        }
        if (!observers.contains(observer)) {
            observers.add(observer);
            observer.notify(value);
        }
    }

    @Override
    public Value value(Object object, Method method, Object[] parameters, CallProcessor callProcessor) {
        int evaluatorKey = calculateEvaluatorKey(object, method, parameters);
        Value value = nodeMap.get(evaluatorKey);
        if (value == null) {
            value = new Value(new MethodEvaluator(object, method, parameters, callProcessor));
            nodes.add(value);
            nodeMap.put(evaluatorKey, value);
        }

        //
        Integer objectHash = calculateHashCode(object);
        objects.put(value, objectHash);
        Set<Value> objectValues = values.get(objectHash);
        if (objectValues == null) {
            objectValues = new HashSet<>();
            values.put(objectHash, objectValues);
        }
        objectValues.add(value);

        return value;
    }

    private int calculateEvaluatorKey(Object object, Method method, Object[] parameters) {
        int hash = 1;
        hash = hash * 17 + object.hashCode();
        hash = hash * 31 + method.hashCode();
        if (parameters != null) {
            hash = hash * 13 + Arrays.hashCode(parameters);
        }
        return hash;
    }

    @Override
    public void bind(Value parent, Value child) {
        verify(parent);
        verify(child);

        Set<Value> children = nodeChildren.get(parent);
        if (children == null) {
            children = new HashSet<>();
            nodeChildren.put(parent, children);
        }

        children.add(child);

        Set<Value> parents = nodeParents.get(child);
        if (parents == null) {
            parents = new HashSet<>();
            nodeParents.put(child, parents);
        }

        parents.add(parent);

        registerBind(parent, child);
    }

    @Override
    public void unbind(Value parent, Value child) {
        verify(parent);
        verify(child);

        Set<Value> children = nodeChildren.get(parent);
        if (children != null) {
            children.remove(child);
        }

        Set<Value> parents = nodeParents.get(child);
        if (parents != null) {
            parents.remove(parent);
        }

        unRegisterBind(parent, child);
    }

    @Override
    public void notifyChange(Value value) {
        Set<ValueObserver> observers = nodeObservers.get(value);
        if (observers != null) {
            for (ValueObserver observer : observers) {
                observer.notify(value);
            }
        }
    }

    @Override
    public void invalidate(Value value) {
        verify(value);
        Set<Value> values = new HashSet<>();
        values.add(value);
        invalidate(values);
    }

    /**
     * This method will unbind a child object from its parent.  It uses the binds registered by the
     * <code>registerBind()</code> method.
     *
     * @param parent
     * @param child
     */
    @Override
    public void unbind(Object parent, Object child) {
        Integer parentHash = calculateHashCode(parent);
        Integer childHash = calculateHashCode(child);
        Map<Integer, Set<Bind>> children = binds.get(parentHash);
        if (children != null) {
            Set<Bind> parentChildBinds = children.get(childHash);
            if (parentChildBinds != null) {
                for (Bind bind : parentChildBinds) {
                    unbind(bind.getParent(), bind.getChild());
                    invalidate(bind.getParent());
                }
            }
        }
        if (child instanceof Collection) {
            for (Object collectionObject : ((Collection) child)) {
                unbind(parent, collectionObject);
            }
        }
    }

    /**
     * This method will invalidate every value of this object.  This is far of being optimal but it is necessary
     * when a field goes from null to a new value as no dependencies have been created from a null field.
     * This will not be necessary after implementing the object transformer with field read/write interception.
     *
     * @param object
     */
    @Override
    public void invalidate(Object object) {
        Set<Value> objectValues = values.get(calculateHashCode(object));
        if (objectValues != null) {
            for (Value value : objectValues) {
                invalidate(value);
            }
        }
    }

    private void invalidate(Set<Value> values) {
        if (values != null && !values.isEmpty()) {
            for (Value value : values) {
                value.setDirty();
                notifyChange(value);
                invalidate(nodeParents.get(value));
            }
        }
    }

    private void verify(Value value) {
        if (!nodes.contains(value)) {
            throw new RuntimeException(String.format("Unknown node:", value));
        }
    }

    /**
     * This implementation of the GachetteProvider require to remember every binds at object level in addition
     * to value level.  This is necessary because we do not have field level dependency management (only method level).
     * If we replace the value of an Object field, we need to be able to find every binds between the parent and the
     * child objects to severe them.
     * <p/>
     * This is not necessary if we implement a class transformer that allows us to intercept field read and write.
     *
     * @param parent
     * @param child
     */
    private void registerBind(Value parent, Value child) {
        Integer parentHash = objects.get(parent);
        Integer childHash = objects.get(child);
        Map<Integer, Set<Bind>> children = binds.get(parentHash);
        if (children == null) {
            children = new HashMap<>();
            binds.put(parentHash, children);
        }

        Set<Bind> parentChildrenBinds = children.get(childHash);
        if (parentChildrenBinds == null) {
            parentChildrenBinds = new HashSet<>();
            children.put(childHash, parentChildrenBinds);
        }

        parentChildrenBinds.add(new Bind(parent, child));
    }

    private void unRegisterBind(Value parent, Value child) {
        Integer parentHash = objects.get(parent);
        Integer childHash = objects.get(child);
        Map<Integer, Set<Bind>> children = binds.get(parentHash);

        if (children == null) {
            return;
        }

        Set<Bind> parentChildrenBinds = children.get(childHash);
        if (parentChildrenBinds == null) {
            return;
        }

        parentChildrenBinds.remove(new Bind(parent, child));
    }

    private static class Bind {
        private final Value parent;
        private final Value child;

        private Bind(Value parent, Value child) {
            this.parent = parent;
            this.child = child;
        }

        private Value getParent() {
            return parent;
        }

        private Value getChild() {
            return child;
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(17, 37).append(parent).append(child).toHashCode();
        }

        @Override
        public boolean equals(Object object) {
            if (object == this)
                return true;
            if (object == null)
                return false;
            if (this.getClass() != object.getClass())
                return false;
            Bind other = (Bind) object;
            return new EqualsBuilder().append(parent, other.parent).append(child, other.child).isEquals();
        }

    }

    private int calculateHashCode(Object object) {
        if (object instanceof GachetteProxy) {
            return System.identityHashCode(((GachetteProxy) object).getOriginalObject());
        } else {
            return System.identityHashCode(object);
        }
    }
}