blob: 89e8243d682bec1b360b5e603304032b6d7bded0 [file] [log] [blame]
/*
* 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.setup;
import java.util.Map;
import java.util.HashMap;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.io.Serializable;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.internal.system.Modules;
/**
* Keys in a map of options for configuring various services
* ({@link org.apache.sis.storage.DataStore}, <i>etc</i>).
* {@code OptionKey}s are used for aspects that usually do not need to be configured, except in a few specialized cases.
* For example most data file formats read by SIS do not require the user to specify the character encoding, since the
* encoding it is often given in the file header or in the format specification. However if SIS may have to read plain
* text files <em>and</em> the default platform encoding is not suitable, then the user can specify the desired encoding
* explicitely using the {@link #ENCODING} option.
*
* <p>All options are <em>hints</em> and may be silently ignored. For example most {@code DataStore}s will ignore the
* {@code ENCODING} option if irrelevant to their format, or if the encoding is specified in the data file header.</p>
*
* <p>Options are <em>transitive</em>: if a service uses others services for its internal working, the given options
* may also be given to those dependencies, at implementation choice.</p>
*
* <div class="section">Defining new options</div>
* Developers who wish to define their own options can define static constants in a subclass,
* as in the following example:
*
* {@preformat java
* public final class MyOptionKey<T> extends OptionKey<T> {
* public static final OptionKey<String> MY_OPTION = new MyOptionKey<>("MY_OPTION", String.class);
*
* private MyOptionKey(final String name, final Class<T> type) {
* super(name, type);
* }
* }
* }
*
* @param <T> The type of option values.
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
* @version 0.4
* @module
*/
public class OptionKey<T> implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -7580514229639750246L;
/**
* The character encoding of document content.
* This option can be used when the file to read does not describe itself its encoding.
* For example this option can be used when reading plain text files, but is ignored when
* reading XML files having a {@code <?xml version="1.0" encoding="…"?>} declaration.
*
* <p>If this option is not provided, then the default value is the
* {@link Charset#defaultCharset() platform default}.</p>
*
* @since 0.4
*/
public static final OptionKey<Charset> ENCODING = new OptionKey<Charset>("ENCODING", Charset.class);
/**
* The encoding of a URL (<strong>not</strong> the encoding of the document content).
* This option may be used when converting a {@link String} or a {@link java.net.URL}
* to a {@link java.net.URI} or a {@link java.io.File}. The following rules apply:
*
* <ul>
* <li>URI are always encoded in UTF-8. Consequently this option is ignored for URI.</li>
* <li>URL are often encoded in UTF-8, but not necessarily. Other encodings are possible
* (while not recommended), or some URL may not be encoded at all.</li>
* </ul>
*
* If this option is not provided, then the URL is assumed <strong>not</strong> encoded.
*
* <p><b>Example:</b> Given the {@code "file:Map%20with%20spaces.png"} URL, then:</p>
* <ul>
* <li>If the URL encoding option is set to {@code "UTF-8"} or {@code "ISO-8859-1"}, then:<ul>
* <li>the encoded URI will be {@code "file:Map%20with%20spaces.png"};</li>
* <li>the decoded URI or the file will be {@code "file:Map with spaces.png"}.</li>
* </ul></li>
* <li>If the URL encoding option is set to {@code null} or is not provided, then:<ul>
* <li>the encoded URI will be {@code "file:Map%2520with%2520spaces.png"},
* i.e. the percent sign will be encoded as {@code "%25"};</li>
* <li>the decoded URI or the file will be {@code "file:Map%20with%20spaces.png"}.</li>
* </ul></li>
* </ul>
*
* This option has not effect on URI encoding, which is always UTF-8.
*
* @see java.net.URLDecoder
*/
public static final OptionKey<String> URL_ENCODING = new OptionKey<String>("URL_ENCODING", String.class);
/**
* Whether a storage object (e.g. a {@link org.apache.sis.storage.DataStore}) shall be opened in read,
* write, append or other modes. The main options that can be provided are:
*
* <table class="sis">
* <caption>Supported open options</caption>
* <tr><th>Value</th> <th>Meaning</th></tr>
* <tr><td>{@code "READ"}</td> <td>Open for reading data from the storage object.</td></tr>
* <tr><td>{@code "WRITE"}</td> <td>Open for modifying existing data in the storage object.</td></tr>
* <tr><td>{@code "APPEND"}</td> <td>Open for appending new data in the storage object.</td></tr>
* <tr><td>{@code "CREATE"}</td> <td>Creates a new storage object (file or database) if it does not exist.</td></tr>
* </table>
*
* <div class="section">Differences between the JDK6 and JDK7 branches of SIS</div>
* In the JDK7 branch of SIS, the array type for this key is {@code java.nio.file.OpenOption[]} instead than
* {@code Object[]} and the constants listed in the above table are {@code java.nio.file.StandardOpenOption}
* enumeration values.
*/
public static final OptionKey<Object[]> OPEN_OPTIONS = new OptionKey<Object[]>("OPEN_OPTIONS", Object[].class);
/**
* The byte buffer to use for input/output operations. Some {@link org.apache.sis.storage.DataStore}
* implementations allow a byte buffer to be specified, thus allowing users to choose the buffer
* {@linkplain ByteBuffer#capacity() capacity}, whether the buffer {@linkplain ByteBuffer#isDirect()
* is direct}, or to recycle existing buffers.
*
* <p>It is user's responsibility to ensure that:</p>
* <ul>
* <li>The buffer does not contains any valuable data, as it will be {@linkplain ByteBuffer#clear() cleared}.</li>
* <li>The same buffer is not used concurrently by two different {@code DataStore} instances.</li>
* </ul>
*/
public static final OptionKey<ByteBuffer> BYTE_BUFFER = new OptionKey<ByteBuffer>("BYTE_BUFFER", ByteBuffer.class);
/**
* The name of this key. For {@code OptionKey} instances, it shall be the name of the static constants.
* For subclasses of {@code OptionKey}, there is no restriction.
*/
private final String name;
/**
* The type of values.
*/
private final Class<T> type;
/**
* Creates a new key of the given name for values of the given type.
*
* @param name The key name.
* @param type The type of values.
*/
protected OptionKey(final String name, final Class<T> type) {
ArgumentChecks.ensureNonEmpty("name", name);
ArgumentChecks.ensureNonNull ("type", type);
this.name = name;
this.type = type;
}
/**
* Returns the name of this option key.
*
* @return The name of this option key.
*/
public String getName() {
return name;
}
/**
* Returns the type of values associated to this option key.
*
* @return The type of values.
*/
public final Class<T> getElementType() {
return type;
}
/**
* Returns the option value in the given map for this key, or {@code null} if none.
* This is a convenience method for implementors, which can be used as below:
*
* {@preformat java
* public <T> T getOption(final OptionKey<T> key) {
* ArgumentChecks.ensureNonNull("key", key);
* return key.getValueFrom(options);
* }
* }
*
* @param options The map where to search for the value, or {@code null} if not yet created.
* @return The current value in the map for the this option, or {@code null} if none.
*/
public T getValueFrom(final Map<OptionKey<?>,?> options) {
return (options != null) ? type.cast(options.get(this)) : null;
}
/**
* Sets a value for this option key in the given map, or in a new map if the given map is {@code null}.
* This is a convenience method for implementors, which can be used as below:
*
* {@preformat java
* public <T> void setOption(final OptionKey<T> key, final T value) {
* ArgumentChecks.ensureNonNull("key", key);
* options = key.setValueInto(options, value);
* }
* }
*
* @param options The map where to set the value, or {@code null} if not yet created.
* @param value The new value for the given option, or {@code null} for removing the value.
* @return The given map of options, or a new map if the given map was null. The returned value
* may be null if the given map and the given value are both null.
*/
public Map<OptionKey<?>,Object> setValueInto(Map<OptionKey<?>,Object> options, final T value) {
if (value != null) {
if (options == null) {
options = new HashMap<OptionKey<?>,Object>();
}
options.put(this, value);
} else if (options != null) {
options.remove(this);
}
return options;
}
/**
* Returns {@code true} if the given object is an instance of the same class having the same name and type.
*
* @param object The object to compare with this {@code OptionKey} for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (object != null && object.getClass() == getClass()) {
final OptionKey<?> that = (OptionKey<?>) object;
return name.equals(that.name) && type == that.type;
}
return false;
}
/**
* Returns a hash code value for this object.
*/
@Override
public int hashCode() {
return name.hashCode() ^ (int) serialVersionUID;
}
/**
* Returns a string representation of this option key.
* The default implementation returns the value of {@link #getName()}.
*/
@Override
public String toString() {
return getName();
}
/**
* Resolves this option key on deserialization. This method is invoked
* only for instance of the exact {@code OptionKey} class, not subclasses.
*
* @return The unique {@code OptionKey} instance.
*/
private Object readResolve() {
try {
return OptionKey.class.getField(name).get(null);
} catch (Exception e) { // (ReflectiveOperationException) on JDK7 branch.
/*
* This may happen if we are deserializing a stream produced by a more recent SIS library
* than the one running in this JVM. This class should be robust to this situation, since
* we override the 'equals' and 'hashCode' methods. This option is likely to be ignored,
* but options are expected to be optional...
*/
Logging.recoverableException(Logging.getLogger(Modules.UTILITIES), OptionKey.class, "readResolve", e);
return this;
}
}
}