/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.gobblin.util;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;

import com.google.common.base.Optional;


/**
 * Represents a value of one of two possible types. Similar to Scala's Either.
 * Each instance of {@link Either} is of type {@link Left} or {@link Right} depending of the class of its value.
 *
 * <p>
 *   This class is useful when a class has two different states, each of which depends on a variable of a different
 *   type (for example a String name or Numeric id). While the normal pattern is to have two variables, only one of
 *   which is set, or two Optionals, only one of which is present, that pattern does not protect from having none
 *   or both variables set by programming errors. Instead, a single instance of Either can be used, which guarantees
 *   exactly one type will be set.
 * </p>
 *
 * @param <S> first possible type for the value.
 * @param <T> second possible type for the value.
 */
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class Either<S, T> {

  protected final Optional<S> s;
  protected final Optional<T> t;

  /**
   * An instance of {@link Either} with value of type {@link S}.
   */
  public static class Left<S, T> extends Either<S, T> {
    public Left(S s) {
      super(Optional.fromNullable(s), Optional.<T>absent());
    }

    /**
     * @return value of type {@link S}.
     */
    public S getLeft() {
      return this.s.orNull();
    }
  }

  /**
   * An instance of {@link Either} with value of type {@link T}.
   */
  public static class Right<S, T> extends Either<S, T> {
    public Right(T t) {
      super(Optional.<S>absent(), Optional.fromNullable(t));
    }

    /**
     * @return value of type {@link T}.
     */
    public T getRight() {
      return this.t.orNull();
    }
  }

  /**
   * Create an instance of {@link Either} with value of type {@link S}.
   * @param left value of this instance.
   * @return an instance of {@link Left}.
   */
  public static <S, T> Either<S, T> left(S left) {
    return new Left<>(left);
  }

  /**
   * Create an instance of {@link Either} with value of type {@link T}.
   * @param right value of this instance.
   * @return an instance of {@link Right}.
   */
  public static <S, T> Either<S, T> right(T right) {
    return new Right<>(right);
  }

  /**
   * Get the value of this {@link Either} instance, returned as class {@link Object}. To get a strongly typed return,
   * check the specific type of {@link Either} and call {@link Left#getLeft} or {@link Right#getRight}.
   * @return value as an instance of {@link Object}.
   */
  public Object get() {
    if (this instanceof Left) {
      return this.s.orNull();
    }
    return this.t.orNull();
  }

}
