ResourceManager.java

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

package ship.build;

import static hera.util.FilepathUtils.getCanonicalForm;
import static hera.util.ObjectUtils.equal;
import static hera.util.ObjectUtils.nvl;
import static java.util.Arrays.asList;
import static java.util.Collections.EMPTY_LIST;
import static java.util.Collections.unmodifiableSet;
import static org.slf4j.LoggerFactory.getLogger;

import hera.server.ServerEvent;
import hera.server.ServerListener;
import hera.util.FilepathUtils;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.slf4j.Logger;
import ship.ProjectFile;
import ship.build.res.BuildResource;
import ship.build.res.PackageResource;
import ship.build.res.Project;
import ship.build.res.Source;
import ship.build.res.TestResource;
import ship.util.FileWatcher;

@RequiredArgsConstructor
public class ResourceManager implements ServerListener {
  protected static final Set<Integer> eventFilter =
      unmodifiableSet(new HashSet(asList(FileWatcher.ANY_CHANGED)));

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

  protected final Map<String, Resource> cache = new HashMap<>();

  @Getter
  protected final Project project;

  @Getter
  @Setter
  protected PackageManager packageManager = new PackageManager();

  protected final Set<ResourceChangeListener> resourceChangeListeners = new HashSet<>();

  public void addResourceChangeListener(final ResourceChangeListener listener) {
    resourceChangeListeners.add(listener);
  }

  public void removeResourceChangeListener(final ResourceChangeListener listener) {
    resourceChangeListeners.remove(listener);
  }

  protected void fireEvent(final ResourceChangeEvent event) {
    for (final ResourceChangeListener listener : resourceChangeListeners) {
      listener.handle(event);
    }
  }

  /**
   * Return resource for {@code base}.
   *
   * @param path resource base
   *
   * @return resource for base
   */
  public synchronized Resource getResource(final String path) {
    logger.trace("Path: {}", path);
    final ProjectFile projectFile = project.getProjectFile();
    final String canonicalPath = getCanonicalForm(path);
    logger.trace("Canonical base: {}", canonicalPath);
    final Resource cached = cache.get(canonicalPath);
    if (null != cached) {
      return cached;
    }
    final Resource created = create(canonicalPath);
    cache.put(canonicalPath, created);
    logger.debug("{} added", canonicalPath);
    return created;
  }

  protected boolean isTarget(final String canonicalPath) {
    final ProjectFile projectFile = project.getProjectFile();
    return equal(canonicalPath, getCanonicalForm(projectFile.getTarget()));
  }

  protected boolean isTest(final String canonicalPath) {
    final ProjectFile projectFile = project.getProjectFile();
    return nvl(projectFile.getTests(), Collections.<String>emptyList())
        .stream().map(FilepathUtils::getCanonicalForm).anyMatch(canonicalPath::equals);
  }

  protected Resource create(final String canonicalPath) {
    if (isTarget(canonicalPath)) {
      return new BuildResource(project, canonicalPath);
    } else if (isTest(canonicalPath)) {
      return new TestResource(project, canonicalPath);
    } else if (canonicalPath.endsWith(".lua")) {
      return new Source(project, canonicalPath);
    } else {
      return new Resource(project, canonicalPath);
    }

  }

  public Resource getPackage(final String packageName) {
    final ResourceManager newResourceManager = packageManager.find(packageName);
    return new PackageResource(newResourceManager);
  }

  @Override
  public void handle(final ServerEvent event) {
    if (!eventFilter.contains(event.getType())) {
      logger.trace("Unhandled event: {}", event);
      return;
    }

    final Collection<File> files = nvl((Collection<File>) event.getNewData(), EMPTY_LIST);
    final Optional<Resource> cachedResourceOpt = files.stream().map(file -> {
      final String path = getCanonicalForm(file.getAbsolutePath());
      final String projectPath =
          getCanonicalForm(project.getPath().toAbsolutePath().toString());
      final Path relativePath =
          Paths.get(projectPath).relativize(Paths.get(path));
      logger.trace("Project path: {}, Path: {}, Relative path: {}",
          projectPath, path, relativePath);
      final String relativePathStr = relativePath.toString();
      logger.debug("Relative path: {}", relativePath);

      final String canonicalPath = getCanonicalForm(relativePathStr);
      logger.trace("Project relative path: {}", canonicalPath);
      return cache.get(canonicalPath);
    }).filter(Objects::nonNull).findFirst();
    if (cachedResourceOpt.isPresent()) {
      final Resource cached = cachedResourceOpt.get();
      logger.info("{} changed: {}", cached, event.getType());
      fireEvent(new ResourceChangeEvent(cached));
    }
  }
}