001/*-
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.util.monad;
021
022import java.util.Optional;
023import java.util.function.Consumer;
024import java.util.function.Function;
025import java.util.function.Supplier;
026import java.util.stream.Stream;
027
028import static com.google.common.base.Preconditions.checkArgument;
029import static com.google.common.base.Preconditions.checkNotNull;
030import static com.google.common.base.Preconditions.checkState;
031
032/**
033 * Represents a value of one of three possible types (a disjoint union). An instance of Either3 is either an instance of
034 * Left, Middle, or Right, only one of which can be present at a time.
035 *
036 * This type is right-biased, meaning that operations like map and flatMap will apply to the right value if it is present.
037 * By convention, the preferred type for a given context should be the right value.
038 *
039 * By convention, the left value is used for error handling, when used in a context where one of the values is an error or exception.
040 *
041 * While this class does provide methods for accessing the left, middle, and right values, it is generally recommended to use
042 * the map, flatMap, and fold methods to work with the values. These methods are more idiomatic and less error-prone, and they
043 * are less likely to result in IllegalStateExceptions.
044 *
045 * @param <L> the left value type
046 * @param <M> the middle value type
047 * @param <R> the right value type
048 */
049public class Either3<L, M, R> {
050
051        private final L left;
052        private final M middle;
053        private final R right;
054
055        /*
056         * Private constructor. Use static factory methods for instantiation. See {@link Eithers}.
057         */
058        Either3(L left, M middle, R right) {
059                checkArgument(left != null ^ middle != null ^ right != null, "left, middle, and right are mutually exclusive");
060                this.left = left;
061                this.middle = middle;
062                this.right = right;
063        }
064
065        /**
066         * Swaps the position of the left and right types. The middle type is unchanged.
067         * The value is unchanged.
068         *
069         * @return a new Either3 with the left and right types swapped
070         */
071        public Either3<R, M, L> swap() {
072                if (isRight()) {
073                        return Eithers.forLeft3(right);
074                } else if (isMiddle()) {
075                        return Eithers.forMiddle3(middle);
076                } else {
077                        return Eithers.forRight3(left);
078                }
079        }
080
081        /**
082         * Rotates the types of the Either3 to the right. The right type becomes the left type, the left type becomes the middle type,
083         * and the middle type becomes the right type. The values are unchanged.
084         *
085         * @return new Either3 with the types rotated
086         */
087        public Either3<R, L, M> rotate() {
088                if (isRight()) {
089                        return Eithers.forLeft3(right);
090                } else if (isMiddle()) {
091                        return Eithers.forRight3(middle);
092                } else {
093                        return Eithers.forMiddle3(left);
094                }
095        }
096
097        /**
098         * Returns the left value. Throws an exception if the left value is not present.
099         *
100         * It's generally preferred to pass functions to the Either using {@link #fold(Function, Function, Function)}
101         *
102         * @throws IllegalStateException if the left value is not present
103         * @return the left value
104         */
105        public L leftOrThrow() {
106                checkState(isLeft());
107                return left;
108        }
109
110        /**
111         * Returns the middle value. Throws an exception if the middle value is not present.
112         *
113         * It's generally preferred to pass functions to the Either using {@link #fold(Function, Function, Function)}
114         *
115         * @throws IllegalStateException if the middle value is not present
116         * @return the middle value
117         */
118        public M middleOrThrow() {
119                checkState(isMiddle());
120                return middle;
121        }
122
123        /**
124         * Returns the right value. Throws an exception if the right value is not present.
125         *
126         * It's generally preferred to pass functions to the Either using {@link #fold(Function, Function, Function)}
127         * Alternatively, use {@link #orElse(Object)} or {@link #orElseGet(Supplier)}
128         *
129         * @throws IllegalStateException if the right value is not present
130         * @return the right value
131         */
132        public R rightOrThrow() {
133                checkState(isRight());
134                return right;
135        }
136
137        /**
138         * Alias for {@link #rightOrThrow()}.
139         *
140         * It's generally preferred to pass functions to the Either using {@link #fold(Function, Function, Function)}
141         * Alternatively, use {@link #orElse(Object)} or {@link #orElseGet(Supplier)}
142         *
143         * @throws IllegalStateException if the right value is not present
144         * @return the right value
145         */
146        public R getOrThrow() {
147                return rightOrThrow();
148        }
149
150        /**
151         * Returns the right value if present, otherwise return the provided default value.
152         *
153         * @param defaultValue the value to return if the right value is not present
154         * @return the right value if present, otherwise the default value
155         */
156        public R orElse(R defaultValue) {
157                if (isRight()) {
158                        return right;
159                } else {
160                        return defaultValue;
161                }
162        }
163
164        /**
165         * Returns the right value if present, otherwise return the result of the provided supplier.
166         *
167         * @param defaultSupplier the supplier to provide a default value if the right value is not present
168         * @return the right value if present, otherwise the result of the supplier
169         */
170        public R orElseGet(Supplier<R> defaultSupplier) {
171                if (isRight()) {
172                        return right;
173                } else {
174                        return defaultSupplier.get();
175                }
176        }
177
178        /**
179         * Returns true if the value is the left type. Otherwise, returns false.
180         *
181         * @return true if left is present
182         */
183        public boolean isLeft() {
184                return left != null;
185        }
186
187        /**
188         * Returns true if value is the middle type. Otherwise, returns false.
189         *
190         * @return true if middle is present
191         */
192        public boolean isMiddle() {
193                return middle != null;
194        }
195
196        /**
197         * Returns true if the value is the right type. Otherwise, returns false.
198         *
199         * @return true if right is present
200         */
201        public boolean isRight() {
202                return right != null;
203        }
204
205        /**
206         * Executes the provided consumer if the right value is present.
207         *
208         * @param forRight the consumer to execute if the right value is present
209         */
210        public void forEach(Consumer<? super R> forRight) {
211                checkNotNull(forRight);
212                if (isRight()) {
213                        forRight.accept(right);
214                }
215        }
216
217        /**
218         * Executes the provided consumer if the right value is present, returning the Either3 unchanged.
219         *
220         * @param forRight the consumer to execute if the right value is present
221         * @return the Either3 unchanged
222         */
223        public Either3<L, M, R> peek(Consumer<? super R> forRight) {
224                checkNotNull(forRight);
225                if (isRight()) {
226                        forRight.accept(right);
227                }
228
229                return this;
230        }
231
232        /**
233         * Maps the right value to a new value using the provided function.
234         *
235         * If the right value is not present, returns the left or middle value unchanged.
236         *
237         * @param <T> the type of the new value
238         * @param mapRight the function to map the right value to a new value
239         * @return a new Either3 with the right value mapped to a new value, or the left or middle value
240         */
241        public <T> Either3<L, M, T> map(Function<? super R, ? extends T> mapRight) {
242                checkNotNull(mapRight);
243                if (isRight()) {
244                        return Eithers.forRight3(mapRight.apply(right));
245                }
246
247                return propagate();
248        }
249
250        /**
251         * Maps the Either to a new Either using the provided function.
252         *
253         * If the right value is present, the function is applied to the right value.
254         *
255         * If the right value is not present, the left or middle value is returned.
256         *
257         * @param <T> the type of the new right value
258         * @param flatMapRight the function to map the Either to a new Either
259         * @return a new Either3 with the right value mapped to a new Either, or the left or middle value
260         */
261        public <T> Either3<L, M, T> flatMap(Function<? super R, ? extends Either3<L, M, ? extends T>> flatMapRight) {
262                checkNotNull(flatMapRight);
263                if (isRight()) {
264                        return narrow(flatMapRight.apply(right));
265                }
266
267                return propagate();
268        }
269
270        /**
271         * Maps the left, middle, or right value to a new value using the provided functions.
272         *
273         * The function is sometimes known as "reduce".
274         *
275         * If the right value is present, the foldRight function is applied to the right value.
276         * If the middle value is present, the foldMiddle function is applied to the middle value.
277         * If the left value is present, the foldLeft function is applied to the left value.
278         *
279         * @param <T> the type of the new value
280         * @param foldLeft the function to map the left value to a new value, if present
281         * @param foldMiddle the function to map the middle value to a new value, if present
282         * @param foldRight the function to map the right value to a new value, if present
283         * @return the new value
284         */
285        public <T> T fold(
286                        Function<? super L, ? extends T> foldLeft,
287                        Function<? super M, ? extends T> foldMiddle,
288                        Function<? super R, ? extends T> foldRight) {
289                checkNotNull(foldLeft);
290                checkNotNull(foldMiddle);
291                checkNotNull(foldRight);
292                if (isRight()) {
293                        return foldRight.apply(right);
294                } else if (isMiddle()) {
295                        return foldMiddle.apply(middle);
296                } else {
297                        return foldLeft.apply(left);
298                }
299        }
300
301        /**
302         * Transforms the Either to a new value using the provided function. The function is applied to the entire Either,
303         * regardless of which value is present. This is in contrast to the map, flatMap, and fold functions, which only apply
304         * when the right value is present.
305         *
306         * @param <U> the type of the new value
307         * @param transform the function to transform the Either to a new value
308         * @return the new value
309         */
310        public <U> U transform(Function<? super Either3<? super L, ? super M, ? super R>, ? extends U> transform) {
311                return transform.apply(this);
312        }
313
314        /**
315         * Returns a stream of the right value if present, otherwise an empty stream.
316         * Mainly useful for converting to a stream for further processing with standard Java
317         * APIs like Stream.map, Stream.filter, etc.
318         *
319         * @return the stream of the right value if present
320         */
321        public Stream<R> stream() {
322                if (isRight()) {
323                        return Stream.of(right);
324                } else {
325                        return Stream.of();
326                }
327        }
328
329        /**
330         * Returns an Optional containing the right value if present, otherwise an empty Optional.
331         * Mainly useful for converting to an Optional for further processing with standard Java
332         * APIs, like Optional.map, Optional.filter, etc.
333         *
334         * @return an Optional containing the right value if present
335         */
336        public Optional<R> optional() {
337                if (isRight()) {
338                        return Optional.of(right);
339                }
340
341                return Optional.empty();
342        }
343
344        @SuppressWarnings("unchecked")
345        protected <T> Either3<L, M, T> narrow(Either3<L, M, ? extends T> wide) {
346                return (Either3<L, M, T>) wide;
347        }
348
349        @SuppressWarnings("unchecked")
350        protected <T> Either3<L, M, T> propagate() {
351                return (Either3<L, M, T>) this;
352        }
353
354        @Override
355        public boolean equals(Object obj) {
356                if (obj instanceof Either3<?, ?, ?>) {
357                        Either3<?, ?, ?> other = (Either3<?, ?, ?>) obj;
358                        return (this.left == other.left && this.right == other.right && this.middle == other.middle)
359                                        || (this.left != null && other.left != null && this.left.equals(other.left))
360                                        || (this.middle != null && other.middle != null && this.middle.equals(other.middle))
361                                        || (this.right != null && other.right != null && this.right.equals(other.right));
362                }
363
364                return false;
365        }
366
367        @Override
368        public int hashCode() {
369                if (this.left != null) {
370                        return this.left.hashCode();
371                }
372
373                if (this.middle != null) {
374                        return this.middle.hashCode();
375                }
376
377                return this.right.hashCode();
378        }
379}