Source.java

/*
 * @copyright defined in LICENSE.txt
 */

package ship.build.res;

import static hera.util.FilepathUtils.append;
import static hera.util.FilepathUtils.getParentPath;
import static java.lang.Character.isWhitespace;
import static java.util.Collections.emptyList;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static org.slf4j.LoggerFactory.getLogger;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.StringJoiner;
import org.slf4j.Logger;
import ship.build.Resource;
import ship.build.ResourceManager;

public class Source extends File {

  protected static final String IMPORT_PREFIX = "import";

  protected static final String[] QUOTES = new String[] { "\"", "'" };

  protected static Optional<String> extractLiteralPart(final String line) {
    if (!line.startsWith(IMPORT_PREFIX) || !isWhitespace(line.charAt(IMPORT_PREFIX.length()))) {
      return empty();
    }
    return of(line.substring(IMPORT_PREFIX.length()).trim());
  }

  protected static String fromImport(final String line) {
    return extractLiteralPart(line).flatMap(literal -> {
      for (final String quote : QUOTES) {
        if (literal.startsWith(quote) && literal.endsWith(quote)) {
          return of(literal.substring(quote.length(), literal.length() - quote.length()).trim());
        }
      }
      return empty();
    }).orElse(null);
  }

  class BodyCollector {

    protected boolean isBodyPart = false;

    protected final StringJoiner joiner = new StringJoiner("\n");

    public void add(final String line) {
      if (isBodyPart) {
        joiner.add(line);
      } else if (null == fromImport(line)) {
        joiner.add(line);
        isBodyPart = true;
      }
    }

    @Override
    public String toString() {
      return joiner.toString();
    }
  }

  protected final transient Logger logger = getLogger(getClass());

  public Source(final Project project, final String location) {
    super(project, location);
  }

  @Override
  public List<Resource> getDependencies(final ResourceManager resourceManager) throws Exception {
    final ArrayList<Resource> dependencies = new ArrayList<>();
    dependencies.addAll(super.getDependencies(resourceManager));
    readImports().stream()
        .map(importPath -> this.bind(resourceManager, importPath))
        .forEach(dependencies::add);
    return dependencies;
  }

  protected List<String> readNextImports(final BufferedReader bufferedReader) throws IOException {
    final String line = bufferedReader.readLine();
    if (null == line) {
      return new ArrayList<>();
    }
    if (line.trim().isEmpty()) {
      return readNextImports(bufferedReader);
    }
    final String importStr = fromImport(line.trim());
    if (null == importStr) {
      return new ArrayList<>();
    }
    final List<String> results = readNextImports(bufferedReader);
    results.add(importStr);
    return results;
  }

  /**
   * Read import clause and convert to import target.
   *
   * @return import target list
   *
   * @throws IOException Fail to read source file.
   */
  public List<String> readImports() throws IOException {
    final List<String> imports = new ArrayList<>();
    try (final BufferedReader sourceIn = open()) {
      // Read line by line
      return readNextImports(sourceIn);
    } catch (final NoSuchFileException e) {
      logger.trace("{} not found", location);
      return emptyList();
    }
  }

  protected Resource bind(final ResourceManager resourceManager, final String importPath) {
    if (importPath.startsWith("./") || importPath.startsWith("../")) {
      return resourceManager.getResource(append(getParentPath(getLocation()), importPath));
    } else {
      return resourceManager.getPackage(importPath);
    }
  }

  /**
   * Return text without upper import part.
   *
   * @return body text
   */
  public Text getBody() {
    return new Text(() -> {
      final BodyCollector collector = new BodyCollector();
      try (final BufferedReader sourceIn = open()) {
        // Read line by line
        String line = null;
        while (null != (line = sourceIn.readLine())) {
          logger.trace("Line: {}", line);
          collector.add(line);
        }
        return new ByteArrayInputStream(collector.toString().getBytes());
      }
    });
  }

}