M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

すごい久しぶりに自分でクラスを書く

RubyやFunctional Jλvλのこととかをぼんやり考えていたら書きたくなってしまった。とくに使い道はない。

package com.m12i.java.range;

import java.util.ArrayList;
import java.util.List;

/**
 * 始点と終点の2値により決定されるある範囲をあらわすクラス.<br>
 * 始点と終点は同じクラスである必要がある。 このクラスはイミュータブルなクラスである。
 * 
 * @param <T>
 *            Comparableの実装クラス
 */
public class Range<T extends Comparable<T>> {

	/** 範囲の始点 */
	private final T start;
	/** 範囲の終点 */
	private final T end;

	/**
	 * 始点を取得する.
	 * 
	 * @return 始点
	 */
	public final T getStart() {
		return this.start;
	}

	/**
	 * 終点を取得する.
	 * 
	 * @return 終点
	 */
	public final T getEnd() {
		return this.end;
	}

	/**
	 * 2値から新しい範囲オブジェクトを生成する.<br>
	 * 2値のうちいずれかでもnullである場合や、一方のクラスと他方のクラスが異なる場合、
	 * {@link IllegalArgumentException}をスローする。<br>
	 * 
	 * @param value1
	 *            範囲の始点(もしくは終点)
	 * @param value2
	 *            範囲の終点(もしくは始点)
	 */
	public Range(final T value1, final T value2) {
		Range.checkNullArguments(value1, value2);
		if (!value1.getClass().equals(value2.getClass())) {
			throw new IllegalArgumentException();
		}
		if (value1.compareTo(value2) <= 0) {
			this.start = value1;
			this.end = value2;
		} else {
			this.start = value2;
			this.end = value1;
		}
	}

	/**
	 * 2値から新しい範囲オブジェクトを生成する.<br>
	 * 2値のうちいずれかでもnullである場合{@link IllegalArgumentException}をスローする。
	 * 
	 * @param value1
	 *            範囲の始点(もしくは終点)
	 * @param value2
	 *            範囲の終点(もしくは始点)
	 * @return 新しい範囲オブジェクト
	 */
	public static <T extends Comparable<T>> Range<T> between(final T value1,
			final T value2) {
		return new Range<T>(value1, value2);
	}

	/**
	 * 2つの範囲の積(共通部分)の範囲オブジェクトを求める.<br>
	 * 2つの範囲が共通部分を持たない場合、{@link Range.NotOverlapException}がスローされる。
	 * 
	 * @param range1
	 *            範囲1
	 * @param range2
	 *            範囲2
	 * @return 共通部分をあらわす範囲オブジェクト
	 * @throws NotOverlapException
	 *             2つの範囲オブジェクトに重なる部分がない
	 */
	public static <T extends Comparable<T>> Range<T> productOf(
			final Range<T> range1, final Range<T> range2) {
		Range.checkNullArguments(range1, range2);
		if (!range1.overlaps(range2)) {
			throw new NotOverlapException();
		}
		final T start = range1.getStart().compareTo(range2.getStart()) >= 0 ? range1
				.getStart() : range2.getStart();
		final T end = range1.getEnd().compareTo(range2.getEnd()) <= 0 ? range1
				.getEnd() : range2.getEnd();
		return Range.between(start, end);
	}

	/**
	 * 2つの範囲の和(共通部分で連結したもの)の範囲オブジェクトを求める.<br>
	 * 2つの範囲が共通部分を持たない場合、{@link Range.NotOverlapException}がスローされる。
	 * 
	 * @param range1
	 *            範囲1
	 * @param range2
	 *            範囲2
	 * @return 連結された範囲オブジェクト
	 * @throws NotOverlapException
	 *             2つの範囲オブジェクトに重なる部分がない
	 */
	public static <T extends Comparable<T>> Range<T> sumOf(
			final Range<T> range1, final Range<T> range2) {
		Range.checkNullArguments(range1, range2);
		if (!range1.overlaps(range2)) {
			throw new NotOverlapException();
		}
		final T start = range1.getStart().compareTo(range2.getStart()) <= 0 ? range1
				.getStart() : range2.getStart();
		final T end = range1.getEnd().compareTo(range2.getEnd()) >= 0 ? range1
				.getEnd() : range2.getEnd();
		return Range.between(start, end);
	}

	/**
	 * 2つの範囲の積(共通部分)の範囲オブジェクトを求める.<br>
	 * 
	 * @param other
	 *            対象の範囲
	 * @return 共通部分をあらわす範囲オブジェクト
	 * @see Range#productOf(Range, Range)
	 */
	public final Range<T> and(Range<T> other) {
		return Range.productOf(this, other);
	}

	/**
	 * 2つの範囲の和(共通部分で連結したもの)の範囲オブジェクトを求める.
	 * 
	 * @param other
	 *            対象の範囲
	 * @return 連結された範囲オブジェクト
	 * @see Range#sumOf(Range, Range)
	 */
	public final Range<T> or(Range<T> other) {
		return Range.sumOf(this, other);
	}

	/**
	 * 2つの範囲オブジェクトに重なるところがあるかチェックする.<br>
	 * 「重なっている」と見なせるケースは以下の通り。
	 * <ol>
	 * <li>thisとotherが等しい</li>
	 * <li>thisがotherを内包している</li>
	 * <li>thisがotherに内包されている</li>
	 * <li>thisの始点がotherの始点以上かつ終点以下</li>
	 * <li>thisの終点がotherの始点以上かつ終点以下</li>
	 * </ol>
	 * 
	 * @param other
	 *            チェック対象の範囲オブジェクト
	 * @return 重なる(true)/重ならない(false)
	 */
	public final boolean overlaps(final Range<T> other) {
		Range.checkNullArguments(other);
		return this.equals(other)
				|| this.include(other)
				|| other.include(this)
				|| (this.getStart().compareTo(other.getStart()) >= 0 && this
						.getStart().compareTo(other.getEnd()) <= 0)
				|| (this.getEnd().compareTo(other.getStart()) >= 0 && this
						.getEnd().compareTo(other.getEnd()) <= 0);
	}

	/**
	 * 引数で与えられた値が範囲に含まれるかどうかチェックする.
	 * 
	 * @param value
	 *            チェック対象の値
	 * @return 含まれる(true)/含まれない(false)
	 */
	public final boolean include(final T value) {
		Range.checkNullArguments(value);
		return this.getStart().compareTo(value) <= 0
				&& value.compareTo(this.getEnd()) <= 0;
	}

	/**
	 * 引数で与えられた範囲がレシーバ側に含まれるものであるかどうかチェックする.
	 * 
	 * @param other
	 *            チェック対象の値
	 * @return 含まれる(true)/含まれない(false)
	 */
	public final boolean include(final Range<T> other) {
		Range.checkNullArguments(other);
		return this.getStart().compareTo(other.getStart()) <= 0
				&& other.getEnd().compareTo(this.getEnd()) <= 0;
	}

	/**
	 * 2つの範囲が意味的・機能的にまったく等しいかどうかをチェックする.<br>
	 * 2つの範囲の始点と終点がそれぞれ等しい(equalsメソッドにより判定される)場合、範囲オブジェクトもまた等しいと判定される。
	 * 
	 * @param other
	 *            チェック対象の値
	 * @return 等しい(true)/等しくない(false)
	 */
	public final boolean equals(final Object other) {
		if(other != null && other instanceof Range){
			final Range<?> otherRange = (Range<?>) other;
			return this.getStart().equals(otherRange.getStart())
				&& this.getEnd().equals(otherRange.getEnd());
		}else{
			return false;
		}
	}

	/**
	 * 引数で与えられたリストのうち、範囲に適合する要素のみからなる新しいリストを生成する.
	 * 
	 * @param target
	 *            もとのリスト
	 * @return 新しいリスト
	 */
	public final List<T> selectFrom(final Iterable<T> target) {
		final List<T> result = new ArrayList<T>();
		for (final T element : target) {
			if (this.include(element)) {
				result.add(element);
			}
		}
		return result;
	}

	/**
	 * 引数で与えられたリストのうち、範囲に適合しない要素のみからなる新しいリストを生成する.
	 * 
	 * @param target
	 *            もとのリスト
	 * @return 新しいリスト
	 */
	public final List<T> rejectFrom(final Iterable<T> target) {
		final List<T> result = new ArrayList<T>();
		for (final T element : target) {
			if (!this.include(element)) {
				result.add(element);
			}
		}
		return result;
	}

	@Override
	public final String toString() {
		return this.getStart().toString() + ".." + this.getEnd().toString();
	}

	private static final void checkNullArguments(Object... args) {
		for (final Object obj : args) {
			if (obj == null) {
				throw new IllegalArgumentException();
			}
		}
	}

	/**
	 * 2つの範囲オブジェクトに、重なる部分のないことをあらわす例外
	 */
	private static final class NotOverlapException extends RuntimeException {
		private static final long serialVersionUID = -7712951059270375953L;
	}

}