/*
 * 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.sis.storage;

import java.util.logging.Logger;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.metadata.distribution.Format;
import org.apache.sis.internal.simple.SimpleFormat;
import org.apache.sis.internal.storage.URIDataStore;
import org.apache.sis.metadata.iso.citation.DefaultCitation;
import org.apache.sis.metadata.iso.distribution.DefaultFormat;
import org.apache.sis.measure.Range;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Version;


/**
 * Provides information about a specific {@link DataStore} implementation.
 * There is typically one {@code DataStoreProvider} instance for each format supported by a library.
 * Each {@code DataStoreProvider} instances provides the following services:
 *
 * <ul>
 *   <li>Provide generic information about the storage (name, <i>etc.</i>).</li>
 *   <li>Create instances of the {@link DataStore} implementation described by this provider.</li>
 *   <li>Test if a {@code DataStore} instance created by this provider would have reasonable chances
 *       to open a given {@link StorageConnector}.</li>
 * </ul>
 *
 * <div class="section">Packaging data stores</div>
 * JAR files that provide implementations of this class shall contain an entry with exactly the following path:
 *
 * {@preformat text
 *     META-INF/services/org.apache.sis.storage.DataStoreProvider
 * }
 *
 * The above entry shall contain one line for each {@code DataStoreProvider} implementation provided in the JAR file,
 * where each line is the fully qualified name of the implementation class.
 * See {@link java.util.ServiceLoader} for more general discussion about this lookup mechanism.
 *
 * <div class="section">Thread safety</div>
 * All {@code DataStoreProvider} implementations shall be thread-safe.
 * However the {@code DataStore} instances created by the providers do not need to be thread-safe.
 *
 * @author  Martin Desruisseaux (Geomatys)
 * @author  Johann Sorel (Geomatys)
 * @version 1.0
 * @since   0.3
 * @module
 */
public abstract class DataStoreProvider {
    /**
     * Name of the parameter that specifies the data store location.
     * A parameter named {@value} should be included in the group of parameters returned by {@link #getOpenParameters()}.
     * The parameter value is often a {@link java.net.URI} or a {@link java.nio.file.Path}, but other types are allowed.
     *
     * <p>Implementers are encouraged to define a parameter with this name
     * to ensure a common and consistent definition among providers.
     * The parameter should be defined as mandatory and typed with a well-known Java class such as
     * {@link java.net.URI}, {@link java.nio.file.Path}, JDBC {@linkplain javax.sql.DataSource}, <i>etc</i>.
     * The type should have a compact textual representation, for serialization in XML or configuration files.
     * Consequently {@link java.io.InputStream} and {@link java.nio.channels.Channel} should be avoided.</p>
     *
     * @see #CREATE
     * @see #getOpenParameters()
     */
    public static final String LOCATION = "location";

    /**
     * Name of the parameter that specifies whether to allow creation of a new {@code DataStore} if none exist
     * at the given location. A parameter named {@value} may be included in the group of parameters returned by
     * {@link #getOpenParameters()} if the data store supports write operations. The parameter value is often a
     * {@link Boolean} and the default value should be {@link Boolean#FALSE} or equivalent.
     *
     * <p>Implementers are encouraged to define an <em>optional</em> parameter with this name in complement to the
     * {@value #LOCATION} parameter <em>only if</em> write operations are supported. If this parameter value is not
     * set or is set to {@code false}, then the {@link #open(ParameterValueGroup)} method should fail if no file or
     * database exists at the URL or path given by the {@value #LOCATION} parameter. Otherwise if this parameter is
     * set to {@code true}, then the {@code open(…)} method may create files, a directory or a database at the given
     * location.</p>
     *
     * <div class="note"><b>Relationship with standard file open options</b>
     * <p>For data stores on file systems, a <code>{@value} = true</code> parameter value is equivalent to opening a file
     * with {@link java.nio.file.StandardOpenOption#CREATE} and {@link java.nio.file.StandardOpenOption#APPEND APPEND}.
     * The other file standard options like {@link java.nio.file.StandardOpenOption#CREATE_NEW CREATE_NEW} and
     * {@link java.nio.file.StandardOpenOption#TRUNCATE_EXISTING TRUNCATE_EXISTING} should not be accessible through
     * this {@value} parameter. The reason is that {@link ParameterValueGroup} may be used for storing parameters
     * permanently (for example in a configuration file or in a database) for reopening the same {@link DataStore}
     * many times. File options designed for being used only once like {@code CREATE_NEW} and {@code TRUNCATE_EXISTING}
     * are incompatible with this usage.</p></div>
     *
     * @see #LOCATION
     * @see #getOpenParameters()
     */
    public static final String CREATE = "create";

    /**
     * The logger where to reports warnings or change events. Created when first needed and kept
     * by strong reference for avoiding configuration lost if the logger if garbage collected.
     * This strategy assumes that {@code URIDataStore.Provider} instances are kept alive for
     * the duration of JVM lifetime, which is the case with {@link DataStoreRegistry}.
     *
     * @see #getLogger()
     */
    private volatile Logger logger;

    /**
     * Creates a new provider.
     */
    protected DataStoreProvider() {
    }

    /**
     * Returns a short name or abbreviation for the data format.
     * This name is used in some warnings or exception messages.
     * It may contain any characters, including white spaces
     * (i.e. this short name is <strong>not</strong> a format identifier).
     *
     * <div class="note"><b>Examples:</b>
     * {@code "CSV"}, {@code "GeoTIFF"}, {@code "GML"}, {@code "GPX"}, {@code "JPEG"}, {@code "JPEG 2000"},
     * {@code "NetCDF"}, {@code "PNG"}, {@code "Shapefile"}.
     * </div>
     *
     * For a more comprehensive format name, see {@link #getFormat()}.
     *
     * @return a short name or abbreviation for the data format.
     *
     * @see #getFormat()
     *
     * @since 0.8
     */
    public abstract String getShortName();

    /**
     * Returns a description of the data format. The description should contain (if available):
     *
     * <ul>
     *   <li>A reference to the {@linkplain DefaultFormat#getFormatSpecificationCitation()
     *       format specification citation}, including:
     *     <ul>
     *       <li>a format specification {@linkplain DefaultCitation#getTitle() title}
     *           (example: <cite>“PNG (Portable Network Graphics) Specification”</cite>),</li>
     *       <li>the format {@linkplain #getShortName() short name} as a citation
     *           {@linkplain DefaultCitation#getAlternateTitles() alternate title}
     *           (example: <cite>“PNG”</cite>),</li>
     *       <li>the format version as the citation {@linkplain DefaultCitation#getEdition() edition},</li>
     *       <li>link to an {@linkplain DefaultCitation#getOnlineResources() online} version of the specification.</li>
     *     </ul>
     *   </li>
     *   <li>The title of the {@linkplain DefaultFormat#getFileDecompressionTechnique() file decompression technique}
     *       used for reading the data.</li>
     * </ul>
     *
     * The default implementation returns a format containing only the value returned by {@link #getShortName()}.
     * Subclasses are encouraged to override this method for providing a more complete description, if available.
     *
     * @return a description of the data format.
     *
     * @see #getShortName()
     * @see DefaultFormat
     *
     * @since 0.8
     */
    public Format getFormat() {
        return new SimpleFormat(getShortName());
    }

    /**
     * Returns the range of versions supported by the data store, or {@code null} if unspecified.
     *
     * @return the range of supported versions, or {@code null} if unspecified.
     *
     * @since 0.8
     */
    public Range<Version> getSupportedVersions() {
        return null;
    }

    /**
     * Returns a description of all parameters accepted by this provider for opening a data store.
     * Those parameters provide an alternative to {@link StorageConnector} for opening a {@link DataStore}
     * from a path or URL, together with additional information like character encoding.
     *
     * <p>Implementers are responsible for declaring all parameters and whether they are mandatory or optional.
     * It is recommended to define at least a parameter named {@value #LOCATION}, completed by {@value #CREATE}
     * if the data store supports write operations.
     * Those parameters will be recognized by the default {@code DataStoreProvider} methods and used whenever a
     * {@link StorageConnector} is required.</p>
     *
     * <div class="note"><b>Alternative:</b>
     * the main differences between the use of {@code StorageConnector} and parameters are:
     * <ul class="verbose">
     *   <li>{@code StorageConnector} is designed for use with file or stream of unknown format;
     *       the format is automatically detected. By contrast, the use of parameters require to
     *       determine the format first (i.e. select a {@code DataStoreProvider}).</li>
     *   <li>Parameters can be used to dynamically generate user configuration interfaces
     *       and provide fine grain control over the store general behavior such as caching,
     *       time-outs, encoding, <i>etc</i>.</li>
     *   <li>Parameters can more easily be serialized in XML or configuration files.</li>
     * </ul></div>
     *
     * @return description of the parameters required or accepted for opening a {@link DataStore}.
     *
     * @see #LOCATION
     * @see #CREATE
     * @see #open(ParameterValueGroup)
     * @see DataStore#getOpenParameters()
     *
     * @since 0.8
     */
    public abstract ParameterDescriptorGroup getOpenParameters();

    /**
     * Indicates if the given storage appears to be supported by the {@code DataStore}s created by this provider.
     * The most typical return values are:
     *
     * <ul>
     *   <li>{@link ProbeResult#SUPPORTED} if the {@code DataStore}s created by this provider
     *       can open the given storage.</li>
     *   <li>{@link ProbeResult#UNSUPPORTED_STORAGE} if the given storage does not appear to be in a format
     *       supported by this {@code DataStoreProvider}.</li>
     * </ul>
     *
     * Note that the {@code SUPPORTED} value does not guarantee that reading or writing will succeed,
     * only that there appears to be a reasonable chance of success based on a brief inspection of the
     * {@linkplain StorageConnector#getStorage() storage object} or contents.
     *
     * <p>Implementers are responsible for restoring the input to its original stream position on return of this method.
     * Implementers can use a mark/reset pair for this purpose. Marks are available as
     * {@link java.nio.ByteBuffer#mark()}, {@link java.io.InputStream#mark(int)} and
     * {@link javax.imageio.stream.ImageInputStream#mark()}.</p>
     *
     * <div class="note"><b>Implementation example</b><br>
     * Implementations will typically check the first bytes of the stream for a "magic number" associated
     * with the format, as in the following example:
     *
     * {@preformat java
     *     public ProbeResult probeContent(StorageConnector storage) throws DataStoreException {
     *         final ByteBuffer buffer = storage.getStorageAs(ByteBuffer.class);
     *         if (buffer == null) {
     *             // If StorageConnector can not provide a ByteBuffer, then the storage is
     *             // probably not a File, URL, URI, InputStream neither a ReadableChannel.
     *             return ProbeResult.UNSUPPORTED_STORAGE;
     *         }
     *         if (buffer.remaining() < Integer.BYTES) {
     *             // If the buffer does not contain enough bytes for the integer type, this is not
     *             // necessarily because the file is truncated. It may be because the data were not
     *             // yet available at the time this method has been invoked.
     *             return ProbeResult.INSUFFICIENT_BYTES;
     *         }
     *         if (buffer.getInt(buffer.position()) != MAGIC_NUMBER) {
     *             // We used ByteBuffer.getInt(int) instead than ByteBuffer.getInt() above
     *             // in order to keep the buffer position unchanged after this method call.
     *             return ProbeResult.UNSUPPORTED_STORAGE;
     *         }
     *         return ProbeResult.SUPPORTED;
     *     }
     * }
     * </div>
     *
     * @param  connector information about the storage (URL, stream, JDBC connection, <i>etc</i>).
     * @return {@link ProbeResult#SUPPORTED} if the given storage seems to be readable by the {@code DataStore}
     *         instances created by this provider.
     * @throws DataStoreException if an I/O or SQL error occurred. The error shall be unrelated to the logical
     *         structure of the storage.
     */
    public abstract ProbeResult probeContent(StorageConnector connector) throws DataStoreException;

    /**
     * Returns a data store implementation associated with this provider.
     * This method is typically invoked when the format is not known in advance
     * (the {@link #probeContent(StorageConnector)} method can be tested on many providers)
     * or when the input is not a type accepted by {@link #open(ParameterValueGroup)}
     * (for example an {@link java.io.InputStream}).
     *
     * <div class="section">Implementation note</div>
     * Implementers shall invoke {@link StorageConnector#closeAllExcept(Object)} after {@code DataStore}
     * creation, keeping open only the needed resource.
     *
     * @param  connector  information about the storage (URL, stream, JDBC connection, <i>etc</i>).
     * @return a data store implementation associated with this provider for the given storage.
     * @throws DataStoreException if an error occurred while creating the data store instance.
     *
     * @see DataStores#open(Object)
     */
    public abstract DataStore open(StorageConnector connector) throws DataStoreException;

    /**
     * Returns a data store implementation associated with this provider for the given parameters.
     * The {@code DataStoreProvider} instance needs to be known before parameters are initialized,
     * since the parameters are implementation-dependent. Example:
     *
     * {@preformat java
     *     DataStoreProvider provider = ...;
     *     ParameterValueGroup pg = provider.getOpenParameters().createValue();
     *     pg.parameter(DataStoreProvider.LOCATION, myURL);
     *     // Set any other parameters if desired.
     *     try (DataStore ds = provider.open(pg)) {
     *         // Use the data store.
     *     }
     * }
     *
     * <div class="section">Implementation note</div>
     * The default implementation gets the value of a parameter named {@value #LOCATION}.
     * That value (typically a path or URL) is given to {@link StorageConnector} constructor,
     * which is then passed to {@link #open(StorageConnector)}.
     *
     * @param  parameters  opening parameters as defined by {@link #getOpenParameters()}.
     * @return a data store implementation associated with this provider for the given parameters.
     * @throws DataStoreException if an error occurred while creating the data store instance.
     *
     * @see #LOCATION
     * @see #CREATE
     * @see #getOpenParameters()
     *
     * @since 0.8
     */
    public DataStore open(final ParameterValueGroup parameters) throws DataStoreException {
        ArgumentChecks.ensureNonNull("parameter", parameters);
        return open(URIDataStore.Provider.connector(this, parameters));
    }

    /**
     * Returns the logger where to report warnings. This logger is used only if no
     * {@link org.apache.sis.storage.event.StoreListener} has been registered for
     * {@link org.apache.sis.storage.event.WarningEvent}.
     *
     * <p>The default implementation returns a logger with the same name as the package name
     * of the subclass of this {@code DataStoreProvider} instance. Subclasses should override
     * this method if they can provide a more specific logger.</p>
     *
     * @return the logger to use as a fallback (when there is no listeners) for warning messages.
     *
     * @since 1.0
     */
    public Logger getLogger() {
        Logger lg = logger;
        if (lg == null) {
            logger = lg = Logging.getLogger(getClass());
        }
        return lg;
    }
}
