org.grouplens.grapht.solver.BindRuleImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.solver.BindRuleImpl.java

Source

/*
 * Grapht, an open source dependency injector.
 * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
 * Copyright 2010-2014 Regents of the University of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.grapht.solver;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.grouplens.grapht.CachePolicy;
import org.grouplens.grapht.reflect.Desire;
import org.grouplens.grapht.reflect.QualifierMatcher;
import org.grouplens.grapht.reflect.Satisfaction;
import org.grouplens.grapht.util.ClassProxy;
import org.grouplens.grapht.util.Preconditions;
import org.grouplens.grapht.util.Types;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.EnumSet;

/**
 * Foundational implementation of {@link BindRule}.
 * 
 * @author <a href="http://grouplens.org">GroupLens Research</a>
 */
final class BindRuleImpl implements BindRule, Serializable {
    private static final long serialVersionUID = -1L;

    private final Satisfaction satisfaction;
    private final EnumSet<BindingFlag> flags;

    private final QualifierMatcher qualifier;
    private final Class<?> depType;
    private final Class<?> implType;

    private final CachePolicy policy;

    private transient volatile int hashCode;

    /**
     * Create a bind rule that matches a desire when the desired type equals
     * <tt>depType</tt> and the desire's qualifier matches <tt>qualifier</tt>
     * .
     * 
     * @param depType The dependency type this bind rule matches
     * @param satisfaction The Satisfaction used by applied desires
     * @param policy The CachePolicy for nodes created by this bind rule
     * @param qualifier The Qualifier the bind rule applies to
     * @param flags The flags to apply to this bind rule and its results.
     * @throws NullPointerException if arguments are null
     */
    public BindRuleImpl(@Nonnull Class<?> depType, @Nonnull Satisfaction satisfaction, @Nonnull CachePolicy policy,
            @Nonnull QualifierMatcher qualifier, EnumSet<BindingFlag> flags) {
        Preconditions.notNull("dependency type", depType);
        Preconditions.notNull("satisfaction", satisfaction);
        Preconditions.notNull("policy", policy);
        Preconditions.notNull("qualifier matcher", qualifier);

        this.qualifier = qualifier;
        this.satisfaction = satisfaction;
        this.implType = satisfaction.getErasedType();
        this.policy = policy;
        this.depType = Types.box(depType);
        this.flags = flags.clone();

        // verify that the satisfaction produces proper types
        Preconditions.isAssignable(this.depType, this.implType);
    }

    /**
     * As the other constructor, but this is used for type to type bindings
     * where the implementation type is not yet instantiable, so there is no
     * satisfaction for the applied desires.
     * 
     * @param depType The dependency type this bind rule matches
     * @param implType The implementation type that is bound
     * @param policy The CachePolicy for nodes created by this bind rule
     * @param qualifier The Qualifier the bind rule applies to
     * @param flags The flags to apply to this bind rule and its results.
     * @throws NullPointerException if arguments are null
     */
    public BindRuleImpl(@Nonnull Class<?> depType, @Nonnull Class<?> implType, @Nonnull CachePolicy policy,
            @Nonnull QualifierMatcher qualifier, EnumSet<BindingFlag> flags) {
        Preconditions.notNull("dependency type", depType);
        Preconditions.notNull("implementation type", implType);
        Preconditions.notNull("policy", policy);
        Preconditions.notNull("qualifier matcher", qualifier);

        this.qualifier = qualifier;
        this.satisfaction = null;
        this.implType = Types.box(implType);
        this.policy = policy;
        this.depType = Types.box(depType);
        this.flags = flags.clone();

        // verify that implType extends depType
        Preconditions.isAssignable(this.depType, this.implType);
    }

    /**
     * Get the rule's qualifier matcher.
     *
     * @return The annotation {@link QualifierMatcher} matched by this bind rule.
     */
    public QualifierMatcher getQualifierMatcher() {
        return qualifier;
    }

    @Override
    public CachePolicy getCachePolicy() {
        return policy;
    }

    @Override
    public Desire apply(Desire desire) {
        // TODO Separate bind rules into different classes based on sat vs. type targets
        if (satisfaction != null) {
            return desire.restrict(satisfaction);
        } else {
            return desire.restrict(implType);
        }
    }

    @Override
    public EnumSet<BindingFlag> getFlags() {
        return flags;
    }

    @Override
    public boolean isTerminal() {
        return flags.contains(BindingFlag.TERMINAL);
    }

    @Override
    public boolean matches(Desire desire) {
        // bind rules match type by equality
        if (desire.getDesiredType().equals(depType)) {
            // if the type is equal, then rely on the qualifier matcher
            return qualifier.matches(desire.getInjectionPoint().getQualifier());
        }

        // the type and {@link Qualifier}s are not a match, so return false
        return false;
    }

    @Override
    public BindRuleBuilder newCopyBuilder() {
        BindRuleBuilder bld = new BindRuleBuilder();
        bld.setDependencyType(depType).setQualifierMatcher(qualifier).setCachePolicy(policy).setFlags(flags);
        if (satisfaction != null) {
            bld.setSatisfaction(satisfaction);
        } else {
            bld.setImplementation(implType);
        }
        return bld;
    }

    @Override
    public int compareTo(BindRule other) {
        if (other instanceof BindRuleImpl) {
            return qualifier.compareTo(((BindRuleImpl) other).qualifier);
        } else {
            throw new IllegalArgumentException("incompatible bind rule");
        }
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof BindRuleImpl) {
            EqualsBuilder eq = new EqualsBuilder();
            BindRuleImpl or = (BindRuleImpl) o;
            return eq.append(depType, or.depType).append(implType, or.implType).append(flags, or.flags)
                    .append(qualifier, or.qualifier).append(policy, or.policy).append(satisfaction, or.satisfaction)
                    .isEquals();
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            HashCodeBuilder hcb = new HashCodeBuilder();
            hashCode = hcb.append(flags).append(depType).append(implType).append(qualifier).append(policy)
                    .append(satisfaction).toHashCode();
        }
        return hashCode;
    }

    @Override
    public String toString() {
        String i = (satisfaction == null ? implType.getSimpleName() : satisfaction.toString());
        return "Bind(" + qualifier + ":" + depType.getSimpleName() + " -> " + i + ", " + flags + ")";
    }

    private Object writeReplace() {
        return new SerialProxy(satisfaction, flags, qualifier, depType, implType, policy);
    }

    private void readObject(ObjectInputStream stream) throws ObjectStreamException {
        throw new InvalidObjectException("must use serialization proxy");
    }

    /**
     * Serialization proxy class.
     */
    private static class SerialProxy implements Serializable {
        private static final long serialVersionUID = 2L;

        private final ClassProxy depType;
        private final QualifierMatcher qualifier;
        private final ClassProxy implType;

        @Nullable
        private final Satisfaction satisfaction;
        private final EnumSet<BindingFlag> flags;
        private final CachePolicy cachePolicy;

        private SerialProxy(@Nullable Satisfaction sat, EnumSet<BindingFlag> flags, QualifierMatcher qmatch,
                Class<?> stype, Class<?> itype, CachePolicy policy) {
            satisfaction = sat;
            this.flags = flags;
            qualifier = qmatch;
            depType = ClassProxy.of(stype);
            implType = ClassProxy.of(itype);
            cachePolicy = policy;
        }

        private Object readResolve() throws ObjectStreamException {
            try {
                if (satisfaction == null) {
                    return new BindRuleImpl(depType.resolve(), implType.resolve(), cachePolicy, qualifier, flags);
                } else {
                    return new BindRuleImpl(depType.resolve(), satisfaction, cachePolicy, qualifier, flags);
                }
            } catch (ClassNotFoundException e) {
                InvalidObjectException ex = new InvalidObjectException("cannot resolve type");
                ex.initCause(e);
                throw ex;
            }
        }
    }
}