summaryrefslogtreecommitdiff
path: root/src/main/java/bjc/data/Either.java
blob: 07845f6f6a4f31a8d20297d07f889b96f189ffd2 (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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
/* 
 * esodata - data structures and other things, of varying utility
 * Copyright 2022, Ben Culkin
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 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, see <https://www.gnu.org/licenses/>.
 */
package bjc.data;

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

import bjc.functypes.ID;

/**
 * Represents a pair where only one side has a value.
 *
 * @author ben
 *
 * @param <LeftType>  The type that could be on the left.
 *
 * @param <RightType> The type that could be on the right.
 *
 */
public class Either<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> Either<LeftType, RightType> left(final LeftType left) {
		return new Either<>(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> Either<LeftType, RightType> right(final RightType right) {
		return new Either<>(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 Either(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> Either<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> Either<LeftType, T> newRight() {
		if (isLeft) return (Either<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> Either<T, RightType> newLeft() {
		if (isLeft) 
			throw new NoSuchElementException("Can't replace left type on left Either");
		return (Either<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(Either<T, T> eth) {
		return eth.extract(ID.id(), 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;

		Either<?, ?> other = (Either<?, ?>) 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);
	}
}