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

net.sf.staccatocommons.lang.value.RelevantState Maven / Gradle / Ivy

/*
 Copyright (c) 2011, The Staccato-Commons Team

 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; version 3 of the License.

 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 Lesser General Public License for more details.
 */
package net.sf.staccatocommons.lang.value;

import java.util.Comparator;

import net.sf.staccatocommons.lang.predicate.AbstractPredicate2;
import net.sf.staccatocommons.lang.value.RelevantState.StateCollector;
import net.sf.staccatocommons.restrictions.check.NonNull;
import net.sf.staccatocommons.restrictions.effect.Transparent;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

/**
 * 
 * An abstract class that is capable of identifying the relevant attributes of
 * an object and implementing with them value comparisons, hashing and string
 * representation in a consisten way.
 * 

* Normal usage of {@link RelevantState} is declaring an implementor per class * of type <A>, having a singleton instance of it, and then delegating * {@link Object#equals(Object)}, {@link Object#hashCode()} etc, to it. For * example: * *

 * class Customer {
 * 
 *  private String name;
 *  private Date birthday;
 *  private Date joinDate;
 * 
 *  //...
 * 
 *  public int hashCode() {
 *   return state.hashCode(this);
 *  }
 * 
 *  public boolean equals(Object obj) {
 *   return state.equals(this, obj);
 *  }
 * 
 *  public String toString() {
 *   return state.toString(this);
 *  }
 * 
 *  private static RelevantState state = new RelevantState(3) {
 *   protected void collectState(Customer o, StateCollector c) {
 *    c.add(o.name).add(o.birthday).add(o.joinDate);
 *   }
 *  };
 * 
 * 
* * In order to do that, {@link RelevantState} instances must be * {@link Transparent}, allowing them to be shared by instances and threads. *

*

* {@link RelevantState} is completely reflection-free, and is built on top of * {@link EqualsBuilder}, {@link ToStringBuilder}, {@link HashCodeBuilder} and * {@link CompareToBuilder}. *

* * * @see Object#equals(Object) * @see Object#hashCode() * @see Object#toString() * @see Comparable#compareTo(Object) * * @param * the type of object this {@link RelevantState} applies to * @author flbulgarelli */ @Transparent public abstract class RelevantState extends AbstractPredicate2 implements Comparator { private final int relevantAttributesCount; private final ToStringStyle toStringStyle; /** * Creates a new {@link RelevantState} * * @param relevantAttributesCount * number of significant properties that determine the value of an * object of type A * @param toStringStyle */ public RelevantState(int relevantAttributesCount, @NonNull ToStringStyle toStringStyle) { super(); this.relevantAttributesCount = relevantAttributesCount; this.toStringStyle = toStringStyle; } /** * Creates a new {@link RelevantState}, using {@link NamedTupleToStringStyle} * as toStringStyle * * * @param relevantAttributesCount * number of significant properties that determine the value of an * object of type A */ public RelevantState(int relevantAttributesCount) { this(relevantAttributesCount, NamedTupleToStringStyle.getInstance()); } /** * Answers the hashcode for the given object, based on the attributes * collected by {@link #collectState(Object, StateCollector)} * * It is safe to use the result as an implementation of * {@link Object#hashCode()} for object * * @param object * @return a hash code computed using the relevant fields of the given object */ public int hashCode(@NonNull A object) { HashCodeStateBuilder b = new HashCodeStateBuilder(); collectState(object, b); return b.toHashCode(); } /*** * Answers a string representation for the given object, based on the * attributes collected by {@link #collectState(Object, StateCollector)} * * It is safe to use the result as an implementation of * {@link Object#toString()} for object * * @param object * @return a to string implementation for object, using the style * provided by {@link #getToStringStyle()} */ public String toString(@NonNull A object) { ToStringCriteria b = new ToStringCriteria(object, toStringStyle); collectState(object, b); return b.toString(); } /** * Compares the given object with other, based on * the fields collected by {@link #collectState(Object, StateCollector)}. * * It is safe to use the result as an implementation of * {@link Comparable#compareTo(Object)} for object * * @param object * @param other * @return 0 if both arguments are the same, or the result of a field by field * comparison */ public int compareTo(@NonNull A object, A other) { if (object == other) return 0; CompareStateBuilder b = new CompareStateBuilder(relevantAttributesCount); collectStateInTwoPhases(object, other, b); return b.toComparison(); } public int compare(A o1, A o2) { return compareTo(o1, o2); } /** * Answers if the given object is equal to other, * based on the fields collected by * {@link #collectState(Object, StateCollector)} * * It is safe to use the result as an implementation of * {@link Object#equals(Object)} for object * * @param object * @param other * @return the result of a {@link BasicEquals} test if it is enough to * determine equality, or a field by field comparison, otherwise * @see BasicEquals */ public boolean equals(@NonNull A object, Object other) { BasicEquals be = BasicEquals.from(object, other); if (be.isEqualsDone()) return be.toEquals(); EqualsStateBuilder sb = new EqualsStateBuilder(relevantAttributesCount); collectStateInTwoPhases(object, other, sb); return sb.isEquals(); } public boolean eval(A arg0, A arg1) { if (arg0 == null) return arg1 == null; return equals(arg0, arg1); } private void collectStateInTwoPhases(A object, Object other, TwoPhaseStateBuilder sb) { collectState(object, sb); sb.setPropertyIndex(0); sb.setState(TwoPhaseStateBuilderState.SECOND_RUN); collectState((A) other, sb); } /** * Collects the attributes that conform the relevant state of the given * object, by adding them to the given {@link StateCollector} * * @param object * @param s * the {@link StateCollector} to which */ protected abstract void collectState(@NonNull A object, @NonNull StateCollector s); /** * The {@link ToStringStyle} used to create toString representations * * @return the toStringStyle */ public ToStringStyle getToStringStyle() { return toStringStyle; } /** * An object for collecting the attributes of an object that are part of its * relevant state * * @author flbulgarelli */ public interface StateCollector { /** * Adds an attribute to the object's state * * @param attribute * an attribute to add to the relevant state * @return this builder */ StateCollector add(Object attribute); /** * Adds an int attribute to the object's state * * @param attribute * an attribute to add to the relevant state * @return this builder */ StateCollector add(int attribute); /** * Adds a long attribute to the object's state * * @param attribute * an attribute to add to the relevant state * @return this builder */ StateCollector add(long attribute); /** * Adds a boolean attribute to the object's state * * @param attribute * an attribute to add to the relevant state * @return this builder */ StateCollector add(boolean attribute); } } interface TwoPhaseStateBuilder extends StateCollector { void setState(TwoPhaseStateBuilderState state); void setPropertyIndex(int index); Object append(Object o1, Object o2); Object append(long o1, long o2); Object append(int o1, int o2); Object append(boolean o1, boolean o2); Object[] getProperties(); int getPropertyIndex(); } final class CompareStateBuilder extends CompareToBuilder implements TwoPhaseStateBuilder { private TwoPhaseStateBuilderState state; private int propertyIndex; private Object[] properties; /** * Creates a new {@link RelevantState.CompareStateBuilder} */ public CompareStateBuilder(int propsCount) { properties = new Object[propsCount]; state = TwoPhaseStateBuilderState.FIRST_RUN; propertyIndex = 0; } public Object[] getProperties() { return properties; } public int getPropertyIndex() { return propertyIndex; } public void setPropertyIndex(int propertyIndex) { this.propertyIndex = propertyIndex; } public void setState(TwoPhaseStateBuilderState state) { this.state = state; } public StateCollector add(Object o) { state.with(o, this); return this; } public StateCollector add(boolean attribute) { state.with(attribute, this); return this; } public StateCollector add(int attribute) { state.with(attribute, this); return this; } public StateCollector add(long attribute) { state.with(attribute, this); return this; } } final class ToStringCriteria extends ToStringBuilder implements StateCollector { /** * Creates a new {@link ToStringCriteria} * * @param toStrinStyle */ public ToStringCriteria(Object object, ToStringStyle toStrinStyle) { super(object, toStrinStyle); } public StateCollector add(Object o) { append(o); return this; } public StateCollector add(boolean attribute) { append(attribute); return this; } public StateCollector add(int attribute) { append(attribute); return this; } public StateCollector add(long attribute) { append(attribute); return this; } } final class HashCodeStateBuilder extends HashCodeBuilder implements StateCollector { public StateCollector add(Object o) { append(o); return this; } public StateCollector add(int attribute) { append(attribute); return this; } public StateCollector add(boolean attribute) { append(attribute); return this; } public StateCollector add(long attribute) { append(attribute); return this; } } final class EqualsStateBuilder extends EqualsBuilder implements TwoPhaseStateBuilder { private TwoPhaseStateBuilderState state; private int propertyIndex; private Object[] properties; /** * Creates a new {@link RelevantState.EqualsStateBuilder} */ public EqualsStateBuilder(int propsCount) { properties = new Object[propsCount]; state = TwoPhaseStateBuilderState.FIRST_RUN; propertyIndex = 0; } public int getPropertyIndex() { return propertyIndex; } public void setPropertyIndex(int propertyIndex) { this.propertyIndex = propertyIndex; } public Object[] getProperties() { return properties; } public void setState(TwoPhaseStateBuilderState state) { this.state = state; } public StateCollector add(Object o) { state.with(o, this); return this; } public StateCollector add(int attribute) { state.with(attribute, this); return this; } public StateCollector add(boolean attribute) { state.with(attribute, this); return this; } public StateCollector add(long attribute) { state.with(attribute, this); return this; } } enum TwoPhaseStateBuilderState { FIRST_RUN { void with(Object o, TwoPhaseStateBuilder eb) { eb.getProperties()[nextIndex(eb)] = o; } }, SECOND_RUN { void with(Object o, TwoPhaseStateBuilder eb) { eb.append(eb.getProperties()[nextIndex(eb)], o); } void with(boolean o, TwoPhaseStateBuilder eb) { eb.append(((Boolean) eb.getProperties()[nextIndex(eb)]).booleanValue(), o); } void with(int o, TwoPhaseStateBuilder eb) { eb.append(((Integer) eb.getProperties()[nextIndex(eb)]).intValue(), o); } void with(long o, TwoPhaseStateBuilder eb) { eb.append(((Long) eb.getProperties()[nextIndex(eb)]).longValue(), o); } }; abstract void with(Object o, TwoPhaseStateBuilder eb); void with(boolean o, TwoPhaseStateBuilder eb) { with((Boolean) o, eb); }; void with(int o, TwoPhaseStateBuilder eb) { with((Integer) o, eb); } void with(long o, TwoPhaseStateBuilder eb) { with((Long) o, eb); } protected int nextIndex(TwoPhaseStateBuilder eb) { int i = eb.getPropertyIndex(); eb.setPropertyIndex(i + 1); return i; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy