SuppliedStringHolder.java

/*
 * stringhold
 *
 * Copyright 2022-2024 Christian Kohlschütter
 *
 * Licensed 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 com.kohlschutter.stringhold;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

import com.kohlschutter.annotations.compiletime.ExcludeFromCodeCoverageGeneratedReport;
import com.kohlschutter.stringhold.IOExceptionHandler.ExceptionResponse;

class SuppliedStringHolder extends AbstractStringHolder {

  private static final IOExceptionHandler UNREACHABLE_EXCEPTION_HANDLER = new IOExceptionHandler() {

    @Override
    @ExcludeFromCodeCoverageGeneratedReport(reason = "unreachable")
    public ExceptionResponse onException(IOException exception) {
      return ExceptionResponse.ILLEGAL_STATE;
    }
  };

  private final IOSupplier<?> supplier;
  private final IOExceptionHandler onError;
  private AtomicBoolean supplied = new AtomicBoolean(false);
  private Object object = null;

  SuppliedStringHolder(int minLen, int expLen, Supplier<?> supplier) {
    this(minLen, expLen, Objects.requireNonNull(supplier, "supplier")::get,
        UNREACHABLE_EXCEPTION_HANDLER);
  }

  SuppliedStringHolder(int minLen, int expLen, IOSupplier<?> supplier,
      IOExceptionHandler exceptionHandler) {
    super(minLen, expLen);
    this.supplier = supplier;
    this.onError = exceptionHandler;
  }

  private Object getSuppliedObject() {
    if (!supplied.compareAndSet(false, true)) {
      return object;
    }

    try {
      object = supplier.get();
    } catch (IOException e) {

      ExceptionResponse resp = onError.onException(e);
      if (resp == ExceptionResponse.ILLEGAL_STATE) {
        throw new IllegalStateException("Unexpected IOException", e);
      }

      setError();

      switch (onError.onException(e)) {
        case FLUSH:
          object = "";
          break;
        case EXCEPTION_MESSAGE:
          object = e.toString();
          break;
        case STACKTRACE:
          StringWriter sw = new StringWriter();
          e.printStackTrace(new PrintWriter(sw));
          object = sw.toString();
          break;
        default:
          object = "";
          break;
      }
    }
    if (object instanceof StringHolder) {
      StringHolder sh = (StringHolder) object;
      resizeTo(sh.getMinimumLength(), sh.getExpectedLength());
      object = sh.asContent();
    }

    if (object instanceof String) {
      toString();
    }

    return object;
  }

  @Override
  public Object asContent() {
    if (isString()) {
      return toString();
    }
    Object o = object;
    if (o instanceof StringHolder) {
      return ((StringHolder) o).asContent();
    } else {
      return this;
    }
  }

  @Override
  public char charAt(int index) {
    if (!isString()) {
      Object sup = getSuppliedObject();
      if (sup instanceof CharSequence) {
        return ((CharSequence) sup).charAt(index);
      }
    }
    return super.charAt(index);
  }

  @Override
  protected Reader newReader() throws IOException {
    Object obj = getSuppliedObject();
    if (obj instanceof StringHolder) {
      StringHolder sh = (StringHolder) obj;
      return sh.toReader();
    } else {
      return super.newReader();
    }
  }

  @Override
  protected int appendToAndReturnLengthDefaultImpl(Appendable out) throws IOException {
    Object obj = getSuppliedObject();
    if (obj instanceof StringHolder) {
      StringHolder sh = (StringHolder) obj;
      return sh.appendToAndReturnLength(out);
    } else if (obj instanceof CharSequence) {
      CharSequence cs = (CharSequence) obj;
      if (CharSequenceReleaseShim.isEmpty(cs)) {
        return 0;
      } else {
        out.append(cs);
        return cs.length();
      }
    } else {
      return super.appendToAndReturnLengthDefaultImpl(out);
    }
  }

  @Override
  protected int appendToAndReturnLengthImpl(StringBuilder out) {
    Object obj = getSuppliedObject();
    if (obj instanceof StringHolder) {
      StringHolder sh = (StringHolder) obj;
      return sh.appendToAndReturnLength(out);
    } else if (obj instanceof CharSequence) {
      CharSequence cs = (CharSequence) obj;
      if (CharSequenceReleaseShim.isEmpty(cs)) {
        return 0;
      } else {
        out.append(cs);
        return cs.length();
      }
    } else {
      return super.appendToAndReturnLengthImpl(out);
    }
  }

  @Override
  protected int appendToAndReturnLengthImpl(StringBuffer out) {
    Object obj = getSuppliedObject();
    if (obj instanceof StringHolder) {
      StringHolder sh = (StringHolder) obj;
      return sh.appendToAndReturnLength(out);
    } else if (obj instanceof CharSequence) {
      CharSequence cs = (CharSequence) obj;
      if (CharSequenceReleaseShim.isEmpty(cs)) {
        return 0;
      } else {
        out.append(cs);
        return cs.length();
      }
    } else {
      return super.appendToAndReturnLengthImpl(out);
    }
  }

  @Override
  protected int appendToAndReturnLengthImpl(Writer out) throws IOException {
    Object obj = getSuppliedObject();
    if (obj instanceof StringHolder) {
      StringHolder sh = (StringHolder) obj;
      return sh.appendToAndReturnLength(out);
    } else if (obj instanceof CharSequence) {
      CharSequence cs = (CharSequence) obj;
      if (CharSequenceReleaseShim.isEmpty(cs)) {
        return 0;
      } else {
        out.append(cs);
        return cs.length();
      }
    } else {
      return super.appendToAndReturnLengthImpl(out);
    }
  }

  @Override
  protected int computeLength() {
    Object obj = getSuppliedObject();
    if (obj instanceof CharSequence) {
      return ((CharSequence) obj).length();
    }
    return super.computeLength();
  }

  @Override
  public boolean isLengthKnown() {
    if (super.isLengthKnown()) {
      return true;
    }
    Object obj = object;
    if (obj instanceof StringHolder) {
      return ((StringHolder) obj).isLengthKnown();
    } else {
      return (obj instanceof CharSequence);
    }
  }

  @Override
  protected String getString() {
    Object obj = object == null ? getSuppliedObject() : object;
    if (obj instanceof CharSequence) {
      return ((CharSequence) obj).toString();
    } else {
      return String.valueOf(obj);
    }
  }

  @Override
  public SuppliedStringHolder clone() {
    SuppliedStringHolder clone = (SuppliedStringHolder) super.clone();
    clone.supplied = new AtomicBoolean(supplied.get());
    return clone;
  }
}