summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSEither.java
blob: 69714c2f213db62c19316a5118e66b11b6587fde (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package net.wotonomy.foundation;

import java.util.*;
import java.util.function.*;

/**
 * Represents a choice between objects of two types
 *
 * @author bjculkin
 *
 * @param <LeftType>  The type that could be on the left.
 *
 * @param <RightType> The type that could be on the right.
 *
 */
public class NSEither<LeftType, RightType> {
	/**
	 * Create a new either with the left value occupied.
	 *
	 * @param <LeftType>  The type of the left value.
	 *
	 * @param <RightType> The type of the empty right value.
	 *
	 * @param left        The value to put on the left.
	 *
	 * @return An either with the left side occupied.
	 */
	public static <LeftType, RightType> NSEither<LeftType, RightType> left(final LeftType left) {
		return new NSEither<>(left, null);
	}

	/**
	 * Create a new either with the right value occupied.
	 *
	 * @param <LeftType>  The type of the empty left value.
	 *
	 * @param <RightType> The type of the right value.
	 *
	 * @param right       The value to put on the right.
	 *
	 * @return An either with the right side occupied.
	 */
	public static <LeftType, RightType> NSEither<LeftType, RightType> right(final RightType right) {
		return new NSEither<>(null, right);
	}

	/* The left value of the either. */
	private LeftType leftVal;
	/* The right value of the either. */
	private RightType rightVal;
	/* Whether the left value is the one filled out. */
	private boolean isLeft;

	/* Create a new either with specifed values. */
	private NSEither(final LeftType left, final RightType right) {
		if (left == null) {
			rightVal = right;
		} else {
			leftVal = left;

			isLeft = true;
		}
	}

	/**
	 * Perform a mapping over this either.
	 * 
	 * @param <NewLeft>  The new left type.
	 * @param <NewRight> The new right type.
	 * 
	 * @param leftFunc   The function to apply if this is a left either.
	 * @param rightFunc  The function to apply if this is a right either.
	 * 
	 * @return A new either, containing a value transformed by the appropriate
	 *         function.
	 */
	public <NewLeft, NewRight> NSEither<NewLeft, NewRight> map(Function<LeftType, NewLeft> leftFunc,
			Function<RightType, NewRight> rightFunc) {
		return isLeft ? left(leftFunc.apply(leftVal)) : right(rightFunc.apply(rightVal));
	}

	/**
	 * Extract the value from this Either.
	 * 
	 * @param <Common>     The common type to extract.
	 * 
	 * @param leftHandler  The function to handle left-values.
	 * @param rightHandler The function to handle right-values.
	 * 
	 * @return The result of applying the proper function.
	 */
	public <Common> Common extract(Function<LeftType, Common> leftHandler, Function<RightType, Common> rightHandler) {
		return isLeft ? leftHandler.apply(leftVal) : rightHandler.apply(rightVal);
	}

	/**
	 * Perform an action on this either.
	 * 
	 * @param leftHandler  The handler of left values.
	 * @param rightHandler The handler of right values.
	 */
	public void pick(Consumer<LeftType> leftHandler, Consumer<RightType> rightHandler) {
		if (isLeft)
			leftHandler.accept(leftVal);
		else
			rightHandler.accept(rightVal);
	}

	/**
	 * Check if this either is left-aligned (has the left value filled, not the
	 * right value).
	 * 
	 * @return Whether this either is left-aligned.
	 */
	public boolean isLeft() {
		return isLeft;
	}

	/**
	 * Get the left value of this either if there is one.
	 * 
	 * @return An optional containing the left value, if there is one.
	 */
	public Optional<LeftType> getLeft() {
		return Optional.ofNullable(leftVal);
	}

	/**
	 * Get the left value of this either, or get a {@link NoSuchElementException} if
	 * there isn't one.
	 * 
	 * @return The left value of this either.
	 * 
	 * @throws NoSuchElementException If this either doesn't have a left value.
	 */
	public LeftType forceLeft() {
		if (isLeft) {
			return leftVal;
		}
		
		throw new NoSuchElementException("Either has no left value, is right value");
	}

	/**
	 * Get the right value of this either if there is one.
	 * 
	 * @return An optional containing the right value, if there is one.
	 */
	public Optional<RightType> getRight() {
		return Optional.ofNullable(rightVal);
	}

	/**
	 * Get the right value of this either, or get a {@link NoSuchElementException}
	 * if there isn't one.
	 * 
	 * @return The right value of this either.
	 * 
	 * @throws NoSuchElementException If this either doesn't have a right value.
	 */
	public RightType forceRight() {
		if (isLeft) {
			throw new NoSuchElementException("Either has no right value, has left value");
		}
		
		return rightVal;
	}

	/**
	 * Change the type of the right-side of this either.
	 * 
	 * Works only for left Eithers.
	 * 
	 * @param <T> The new type for the right side
	 * @return The either with the new type.
	 */
	@SuppressWarnings("unchecked")
	public <T> NSEither<LeftType, T> newRight() {
		if (isLeft) return (NSEither<LeftType, T>) this;
		
		throw new NoSuchElementException("Can't replace right type on right Either");
	}
	
	/**
	 * Change the type of the left-side of this either.
	 * 
	 * Works only for right Eithers.
	 * 
	 * @param <T> The new type for the left side
	 * @return The either with the new type.
	 */
	@SuppressWarnings("unchecked")
	public <T> NSEither<T, RightType> newLeft() {
		if (isLeft) 
			throw new NoSuchElementException("Can't replace left type on left Either");
		return (NSEither<T, RightType>) this;
	}
	
	/**
	 * Collapse an Either with the same type on both sides.
	 * 
	 * @param <T> The type of the either
	 * @param eth The either to collapse
	 * 
	 * @return The collapsed either
	 */
	public static <T> T collapse(NSEither<T, T> eth) {
		Function<T, T> id = (x) -> x;
		return eth.extract(id, id);
	}
	// Misc. overrides

	@Override
	public int hashCode() {
		return Objects.hash(isLeft, leftVal, rightVal);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;

		NSEither<?, ?> other = (NSEither<?, ?>) obj;

		return isLeft == other.isLeft && Objects.equals(leftVal, other.leftVal)
				&& Objects.equals(rightVal, other.rightVal);
	}

	@Override
	public String toString() {
		return String.format("Either [leftVal='%s', rightVal='%s', isLeft=%s]", leftVal, rightVal, isLeft);
	}
}