Daniel Gazineu

20Jul/090

Exposing bitwise operations in a fluent interface

Some days ago I had to develop a class that would represent an event and should contain a weekly recurrence attribute. First idea that came to mind was to use seven lower representative bits of a byte as days of a week and flagging them on and off using bitwise operators. That’s a pretty common resolution for such kind of problem and what I want to share here is not this solution, but the interface I defined to expose it.
I tried to define a fluent interface for it and right now, that's the way a client uses this class:

WeeklyRecurrence rec = new WeeklyRecurrence().repeatOn(EVERY_DAY).but(TUESDAY,THURSDAY);
assertTrue(rec.occursOn(WEDNESDAY));
assertFalse(rec.doesNotOccurOn(FRIDAY));

Moreover, I created an escape() method to point days where the recurrence doesn’t occur:

rec = rec.escape(WEDNESDAY).but(TUESDAY);
assertTrue(rec.doesNotOccurOn(WEDNESDAY));
assertTrue(rec.occursOn(TUESDAY));

As you can see, but() method is contextual and works adding or removing days from a recurrence, always in the opposite way of the last operation.
You may also notice that WeeklyRecurrence is immutable, in order to avoid complications with changed states. This way, the following is correct:

WeeklyRecurrence r1 = new WeeklyRecurrence();
WeeklyRecurrence r2 = r1.repeatOn(EVERY_DAY);
assertFalse(r1.equals(r2));
 
r1 = new WeeklyRecurrence().repeatOn(MONDAY);
r2 = new WeeklyRecurrence().repeatOn(MONDAY);
assertTrue(r1.equals(r2));

The following box contains the complete code for WeeklyRecurrence. The idea of this post is to show that even "low-level" solutions can expose fluent and clean interfaces to the other application layers.

/**
 * Represents recurrence of something in a week.
 */
public class WeeklyRecurrence {
 
	// the value
	private byte recurrence;
 
	/**
	 * Utility method to merge n days with an original recurrence value
	 */
	private static byte merge(byte original, Day... days) {
		for (Day day : days) {
			original |= day.value;
		}
		return original;
	}
 
	/**
	 * Utility method to remove n days from an original recurrence
	 */
	private static byte diff(byte original, Day... days) {
		byte escape = merge((byte) 0, days);
		return (byte) (original & (Day.EVERY_DAY.value - escape));
	}
 
	/**
	 * Enum that represents days of a week
	 */
	public enum Day {
 
		// 10000000
		SUNDAY((byte) 1),
 
		// 10000000
		MONDAY((byte) 2),
 
		// 01000000
		TUESDAY((byte) 4),
 
		// 00010000
		WEDNESDAY((byte) 8),
 
		// 00001000
		THURSDAY((byte) 16),
 
		// 00000100
		FRIDAY((byte) 32),
 
		// 00000010
		SATURDAY((byte) 64),
 
		// 11111110
		EVERY_DAY((byte) 127);
 
		private final byte value;
 
		private Day(byte value) {
			this.value = value;
		}
	}
 
	/**
	 * Just for internal use, initializes a weeklyrecurrence with a byte value
	 */
	private WeeklyRecurrence(byte recurrence) {
		this.recurrence = recurrence;
	}
 
	/**
	 * Initializes a <code>WeeklyRecurrence</code> with no recurrence.
	 */
	public WeeklyRecurrence() {
		this((byte) 0);
	}
 
	/**
	 * Discovers if given recurrence is set. It returns <code>true</code> even
	 * if there are more days in the current recurrence.
	 */
	private boolean isSet(byte otherRecurrence) {
		return (recurrence &amp; otherRecurrence) == otherRecurrence;
	}
 
	/**
	 * Creates a new <code>WeeklyRecurrence</code> merging given days with the
	 * one in the current recurrence instance.
	 */
	public WeeklyRecurrence repeatOn(Day... days) {
		byte newRecurr = merge(this.recurrence, days);
 
		return new WeeklyRecurrence(newRecurr) {
			public WeeklyRecurrence but(Day... days) {
				return escape(days);
			}
		};
	}
 
	/**
	 * Creates a new <code>WeeklyRecurrence</code> instance removing given days
	 * from the current recurrence instance.
	 */
	public WeeklyRecurrence escape(Day... days) {
		byte newRecurr = diff(this.recurrence, days);
		return new WeeklyRecurrence(newRecurr) {
			public WeeklyRecurrence but(Day... days) {
				return repeatOn(days);
			}
		};
	}
 
	/**
	 * Creates a new <code>WeeklyRecurrence</code> including or removing given
	 * days to the current instance, always in the opposite way of the last
	 * operation. If no recurrence was set, it throws an
	 * <code>IllegalStateException</code>
	 */
	public WeeklyRecurrence but(Day... days) {
		throw new IllegalStateException("No recurrence was set yet");
	}
 
	/**
	 * Informs if none of the given days are in the current recurrence.
	 */
	public Boolean doesNotOccurOn(Day... days) {
		for (Day day : days) {
			if (isSet(day.value))
				return false;
		}
		return true;
	}
 
	/**
	 * Informs if every given days are in the current recurrence.
	 */
	public Boolean occursOn(Day... days) {
		byte rec = merge((byte) 0, days);
		return isSet(rec);
	}
 
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + recurrence;
		return result;
	}
 
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		WeeklyRecurrence other = (WeeklyRecurrence) obj;
		if (recurrence != other.recurrence)
			return false;
		return true;
	}
}
  • Share/Bookmark