/**
 * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
 * 
 * Please see distribution for license.
 */
package com.opengamma.strata.collect.io;

import static com.google.common.base.MoreObjects.firstNonNull;

import java.io.File;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.joda.convert.FromString;
import org.joda.convert.ToString;

import com.google.common.io.ByteSource;
import com.google.common.io.CharSource;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import com.opengamma.strata.collect.ArgChecker;

/**
 * A locator for a resource, specified as a file or classpath resource.
 * <p>
 * An instance of this class provides access to a resource, such as a configuration file.
 * The resource data is accessed using {@link CharSource} or {@link ByteSource}.
 */
public final class ResourceLocator {

  /**
   * The prefix for classpath resource locators.
   */
  public static final String CLASSPATH_URL_PREFIX = "classpath:";
  /**
   * The prefix for file resource locators.
   */
  public static final String FILE_URL_PREFIX = "file:";

  /**
   * The resource locator.
   */
  private final String locator;
  /**
   * The source.
   */
  private final ByteSource source;

  //-------------------------------------------------------------------------
  /**
   * Creates a resource from a string locator.
   * <p>
   * This accepts locators starting with 'classpath:' or 'file:'.
   * It also accepts unprefixed locators, treated as 'file:'.
   * 
   * @param locator  the string form of the resource locator
   * @return the resource locator
   */
  @FromString
  public static ResourceLocator of(String locator) {
    ArgChecker.notNull(locator, "locator");
    try {
      if (locator.startsWith(CLASSPATH_URL_PREFIX)) {
        String urlStr = locator.substring(CLASSPATH_URL_PREFIX.length());
        return ofClasspath(urlStr);

      } else if (locator.startsWith(FILE_URL_PREFIX)) {
        String fileStr = locator.substring(FILE_URL_PREFIX.length());
        return ofFile(new File(fileStr));

      } else {
        return ofFile(new File(locator));
      }
    } catch (RuntimeException ex) {
      throw new IllegalArgumentException("Invalid resource locator: " + locator, ex);
    }
  }

  /**
   * Creates a resource from a {@code File}.
   * 
   * @param file  the file to wrap
   * @return the resource
   */
  public static ResourceLocator ofFile(File file) {
    ArgChecker.notNull(file, "file");
    String filename = file.toString();
    // convert Windows separators to unix
    filename = (File.separatorChar == '\\' ? filename.replace('\\', '/') : filename);
    return new ResourceLocator(FILE_URL_PREFIX + filename, Files.asByteSource(file));
  }

  /**
   * Creates a resource from a fully qualified resource name.
   * 
   * @param resourceName  the classpath resource name
   * @return the resource
   */
  public static ResourceLocator ofClasspath(String resourceName) {
    ArgChecker.notNull(resourceName, "classpathLocator");
    URL url = classLoader().getResource(resourceName);
    if (url == null) {
      throw new IllegalArgumentException("Resource not found: " + resourceName);
    }
    return ofClasspathUrl(url);
  }

  /**
   * Creates a resource locator for a classpath resource which is associated with a class.
   * <p>
   * The classpath is searched using the same method as {@code Class.getResource}.
   * <ul>
   *   <li>If the resource name starts with '/' it is treated as an absolute path relative to the classpath root</li>
   *   <li>Otherwise the resource name is treated as a path relative to the package containing the class</li>
   * </ul>
   *
   * @param cls  the class
   * @param resourceName  the resource name
   * @return the resource
   */
  public static ResourceLocator ofClasspath(Class cls, String resourceName) {
    ArgChecker.notNull(resourceName, "classpathLocator");
    URL url = cls.getResource(resourceName);
    if (url == null) {
      throw new IllegalArgumentException("Resource not found: " + resourceName);
    }
    return ofClasspathUrl(url);
  }

  /**
   * Creates a resource from a {@code URL}.
   * 
   * @param url  the URL to wrap
   * @return the resource
   */
  public static ResourceLocator ofClasspathUrl(URL url) {
    ArgChecker.notNull(url, "url");
    String locator = CLASSPATH_URL_PREFIX + url.toString();
    return new ResourceLocator(locator, Resources.asByteSource(url));
  }

  /**
   * Selects a suitable class loader.
   * 
   * @return the class loader
   */
  static ClassLoader classLoader() {
    return firstNonNull(Thread.currentThread().getContextClassLoader(), ResourceConfig.class.getClassLoader());
  }

  //-------------------------------------------------------------------------
  /**
   * Creates an instance of the locator.
   * 
   * @param locator  the locator
   * @param source  the byte source
   */
  private ResourceLocator(String locator, ByteSource source) {
    super();
    this.locator = locator;
    this.source = source;
  }

  //-------------------------------------------------------------------------
  /**
   * Gets the string form of the locator.
   * <p>
   * The string form of the locator describes the location of the resource.
   * 
   * @return the locator string
   */
  public String getLocator() {
    return locator;
  }

  /**
   * Gets the byte source to access the resource.
   * <p>
   * A byte source is a supplier of data.
   * The source itself is neither opened nor closed.
   * 
   * @return the byte source
   */
  public ByteSource getByteSource() {
    return source;
  }

  /**
   * Gets the char source to access the resource using UTF-8.
   * <p>
   * A char source is a supplier of data.
   * The source itself is neither opened nor closed.
   * 
   * @return the char source
   */
  public CharSource getCharSource() {
    return getCharSource(StandardCharsets.UTF_8);
  }

  /**
   * Gets the char source to access the resource specifying the character set.
   * <p>
   * A char source is a supplier of data.
   * The source itself is neither opened nor closed.
   * 
   * @param charset  the character set to use
   * @return the char source
   */
  public CharSource getCharSource(Charset charset) {
    return source.asCharSource(charset);
  }

  //-------------------------------------------------------------------------
  /**
   * Checks if this locator equals another locator.
   * <p>
   * The comparison checks the locator string.
   * 
   * @param obj  the other locator, null returns false
   * @return true if equal
   */
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj instanceof ResourceLocator) {
      return locator.equals(((ResourceLocator) obj).locator);
    }
    return false;
  }

  /**
   * Returns a suitable hash code for the locator.
   * 
   * @return the hash code
   */
  @Override
  public int hashCode() {
    return locator.hashCode();
  }

  /**
   * Returns a string describing the locator.
   * <p>
   * This can be parsed using {@link #of(String)}.
   * 
   * @return the descriptive string
   */
  @ToString
  @Override
  public String toString() {
    return locator;
  }

}

@@ -3884,16 +3884,19 @@
 th(Class
+%3C?%3E
  cls, St

Fix raw type