| /* |
| * 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.feature; |
| |
| import java.util.Map; |
| import org.opengis.util.GenericName; |
| import org.opengis.util.FactoryException; |
| import org.opengis.util.InternationalString; |
| import org.opengis.referencing.crs.CoordinateReferenceSystem; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.Static; |
| import org.apache.sis.util.UnconvertibleObjectException; |
| import org.apache.sis.util.collection.WeakHashSet; |
| import org.apache.sis.util.resources.Errors; |
| |
| |
| /** |
| * A set of pre-defined operations expecting a {@code Feature} as input and producing an {@code Attribute} as output. |
| * Those operations can be used for creating <cite>dynamic properties</cite> which compute their value on-the-fly |
| * from the values of other properties. |
| * |
| * <p>A flexible but relatively cumbersome way to define arbitrary computations is to subclass {@link AbstractOperation}. |
| * This {@code FeatureOperations} class provides a more convenient way to get a few commonly-used operations.</p> |
| * |
| * <h2>Operation name, designation and description</h2> |
| * All operations are identified by a programmatic name, but can also have a more human-readable designation |
| * for Graphical User Interfaces (GUI). Those identification information are specified in a {@code Map<String,?>}. |
| * The recognized entries are the same than the ones documented in {@link AbstractIdentifiedType}, augmented with |
| * entries that describe the operation <em>result</em>. Those entries are summarized below: |
| * |
| * <table class="sis"> |
| * <caption>Recognized map entries</caption> |
| * <tr> |
| * <th>Map key</th> |
| * <th>Value type</th> |
| * <th>Returned by</th> |
| * </tr> |
| * <tr> |
| * <td>{@value org.apache.sis.feature.AbstractIdentifiedType#NAME_KEY}</td> |
| * <td>{@link GenericName} or {@link String}</td> |
| * <td>{@link AbstractOperation#getName() Operation.getName()} (mandatory)</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DEFINITION_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link AbstractOperation#getDefinition() Operation.getDefinition()}</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESIGNATION_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link AbstractOperation#getDesignation() Operation.getDesignation()}</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESCRIPTION_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link AbstractOperation#getDescription() Operation.getDescription()}</td> |
| * </tr> |
| * <tr> |
| * <td>"result.name"</td> |
| * <td>{@link GenericName} or {@link String}</td> |
| * <td>{@link AbstractAttribute#getName() Attribute.getName()} on the {@linkplain AbstractOperation#getResult() result}</td> |
| * </tr> |
| * <tr> |
| * <td>"result.definition"</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link DefaultAttributeType#getDefinition() Attribute.getDefinition()} on the {@linkplain AbstractOperation#getResult() result}</td> |
| * </tr> |
| * <tr> |
| * <td>"result.designation"</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link DefaultAttributeType#getDesignation() Attribute.getDesignation()} on the {@linkplain AbstractOperation#getResult() result}</td> |
| * </tr> |
| * <tr> |
| * <td>"result.description"</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link DefaultAttributeType#getDescription() Attribute.getDescription()} on the {@linkplain AbstractOperation#getResult() result}</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.apache.sis.referencing.AbstractIdentifiedObject#LOCALE_KEY}</td> |
| * <td>{@link java.util.Locale}</td> |
| * <td>(none)</td> |
| * </tr> |
| * </table> |
| * |
| * If no {@code "result.*"} entry is provided, then the methods in this class will use some default name, designation |
| * and other information for the result type. Those defaults are operation specific; they are often, but not necessarily, |
| * the same than the operation name, designation, <i>etc.</i> |
| * |
| * @author Johann Sorel (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.0 |
| * @since 0.7 |
| * @module |
| */ |
| public final class FeatureOperations extends Static { |
| /** |
| * The pool of operations or operation dependencies created so far, for sharing exiting instances. |
| */ |
| static final WeakHashSet<AbstractIdentifiedType> POOL = new WeakHashSet<>(AbstractIdentifiedType.class); |
| |
| /** |
| * Do not allow instantiation of this class. |
| */ |
| private FeatureOperations() { |
| } |
| |
| /** |
| * Creates an operation which is only an alias for another property. |
| * |
| * <div class="note"><b>Example:</b> |
| * features often have a property that can be used as identifier or primary key. |
| * But the name of that property may vary between features of different types. |
| * For example features of type <b>Country</b> may have identifiers named “ISO country code” |
| * while features of type <b>Car</b> may have identifiers named “license plate number”. |
| * In order to simplify identifier usages regardless of their name, |
| * an application could choose to add in all features a virtual property named {@code "identifier"} |
| * which links to whatever property is used as an identifier in an arbitrary feature. |
| * So the definition of the <b>Car</b> feature could contain the following code: |
| * |
| * {@preformat java |
| * AttributeType licensePlateNumber = ...; // Attribute creation omitted for brevity |
| * FeatureType car = new DefaultFeatureType(..., // Arguments omitted for brevity |
| * licensePlateNumber, model, owner, |
| * FeatureOperations.link(singletonMap(NAME_KEY, "identifier"), licensePlateNumber); |
| * } |
| * </div> |
| * |
| * Since this method does not create new property (it only redirects to an existing property), |
| * this method ignores all {@code "result.*"} entries in the given {@code identification} map. |
| * |
| * <h4>Read/write behavior</h4> |
| * Since the {@link AbstractOperation#apply Operation.apply(…)} method returns directly the property |
| * identified by the {@code referent} argument, the returned property is writable if the referenced |
| * property is also writable. |
| * |
| * <div class="warning"><b>Warning:</b> |
| * The type of {@code referent} parameter will be changed to {@code PropertyType} |
| * if and when such interface will be defined in GeoAPI.</div> |
| * |
| * @param identification the name and other information to be given to the operation. |
| * @param referent the referenced attribute or feature association. |
| * @return an operation which is an alias for the {@code referent} property. |
| */ |
| public static AbstractOperation link(final Map<String,?> identification, final AbstractIdentifiedType referent) { |
| ArgumentChecks.ensureNonNull("referent", referent); |
| return POOL.unique(new LinkOperation(identification, referent)); |
| } |
| |
| /** |
| * Creates an operation concatenating the string representations of the values of multiple properties. |
| * This operation can be used for creating a <cite>compound key</cite> as a {@link String} that consists |
| * of two or more attribute values that uniquely identify a feature instance. |
| * |
| * <p>The {@code delimiter}, {@code prefix} and {@code suffix} arguments given to this method |
| * are used in the same way than {@link java.util.StringJoiner}, except for null values. |
| * Null prefix, suffix and property values are handled as if they were empty strings.</p> |
| * |
| * <p>If the same character sequences than the given delimiter appears in a property value, |
| * the {@code '\'} escape character will be inserted before that sequence. |
| * If the {@code '\'} character appears in a property value, it will be doubled.</p> |
| * |
| * <p><b>Restrictions:</b></p> |
| * <ul> |
| * <li>The single properties can be either attributes or operations that produce attributes; |
| * feature associations are not allowed, unless they have an {@code "sis:identifier"} property.</li> |
| * <li>Each attribute shall contain at most one value; multi-valued attributes are not allowed.</li> |
| * <li>The delimiter can not contain the {@code '\'} escape character.</li> |
| * </ul> |
| * |
| * <h4>Read/write behavior</h4> |
| * This operation supports both reading and writing. When setting a value on the attribute created by this |
| * operation, the given string value will be split around the {@code delimiter} and each substring will be |
| * forwarded to the corresponding single property. |
| * |
| * <div class="warning"><b>Warning:</b> |
| * The type of {@code singleAttributes} elements will be changed to {@code PropertyType} |
| * if and when such interface will be defined in GeoAPI.</div> |
| * |
| * @param identification the name and other information to be given to the operation. |
| * @param delimiter the characters to use as delimiter between each single property value. |
| * @param prefix characters to use at the beginning of the concatenated string, or {@code null} if none. |
| * @param suffix characters to use at the end of the concatenated string, or {@code null} if none. |
| * @param singleAttributes identification of the single attributes (or operations producing attributes) to concatenate. |
| * @return an operation which concatenates the string representations of all referenced single property values. |
| * @throws UnconvertibleObjectException if at least one of the given {@code singleAttributes} uses a |
| * {@linkplain DefaultAttributeType#getValueClass() value class} which is not convertible from a {@link String}. |
| * @throws IllegalArgumentException if {@code singleAttributes} is an empty sequence, or contains a property which |
| * is neither an {@code AttributeType} or an {@code Operation} computing an attribute, or an attribute has |
| * a {@linkplain DefaultAttributeType#getMaximumOccurs() maximum number of occurrences} greater than 1. |
| * |
| * @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key on Wikipedia</a> |
| */ |
| public static AbstractOperation compound(final Map<String,?> identification, final String delimiter, |
| final String prefix, final String suffix, final AbstractIdentifiedType... singleAttributes) |
| throws UnconvertibleObjectException |
| { |
| ArgumentChecks.ensureNonEmpty("delimiter", delimiter); |
| if (delimiter.indexOf(StringJoinOperation.ESCAPE) >= 0) { |
| throw new IllegalArgumentException(Errors.getResources(identification).getString( |
| Errors.Keys.IllegalCharacter_2, "delimiter", StringJoinOperation.ESCAPE)); |
| } |
| ArgumentChecks.ensureNonNull("singleAttributes", singleAttributes); |
| switch (singleAttributes.length) { |
| case 0: { |
| throw new IllegalArgumentException(Errors.getResources(identification) |
| .getString(Errors.Keys.EmptyArgument_1, "singleAttributes")); |
| } |
| case 1: { |
| if ((prefix == null || prefix.isEmpty()) && (suffix == null || suffix.isEmpty())) { |
| final AbstractIdentifiedType at = singleAttributes[0]; |
| if (!(at instanceof DefaultAssociationRole)) { |
| return link(identification, at); |
| } |
| } |
| break; |
| } |
| } |
| return POOL.unique(new StringJoinOperation(identification, delimiter, prefix, suffix, singleAttributes)); |
| } |
| |
| /** |
| * Creates an operation computing the envelope that encompass all geometries found in the given attributes. |
| * Geometries can be in different coordinate reference systems; they will be transformed to the first non-null |
| * CRS in the following choices: |
| * |
| * <ol> |
| * <li>the CRS specified to this method,</li> |
| * <li>the CRS of the default geometry, or</li> |
| * <li>the CRS of the first non-empty geometry.</li> |
| * </ol> |
| * |
| * The {@linkplain AbstractOperation#getResult() result} of this operation is an {@code Attribute} |
| * with values of type {@link org.opengis.geometry.Envelope}. If the {@code crs} argument given to |
| * this method is non-null, then the |
| * {@linkplain org.apache.sis.geometry.GeneralEnvelope#getCoordinateReferenceSystem() envelope CRS} |
| * will be that CRS. |
| * |
| * <h4>Limitations</h4> |
| * If a geometry contains other geometries, this operation queries only the envelope of the root geometry. |
| * It is the root geometry responsibility to take in account the envelope of all its children. |
| * |
| * <h4>Read/write behavior</h4> |
| * This operation is read-only. Calls to {@code Attribute.setValue(Envelope)} will result in an |
| * {@link IllegalStateException} to be thrown. |
| * |
| * <div class="warning"><b>Warning:</b> |
| * The type of {@code geometryAttributes} elements will be changed to {@code PropertyType} |
| * if and when such interface will be defined in GeoAPI.</div> |
| * |
| * @param identification the name and other information to be given to the operation. |
| * @param crs the Coordinate Reference System in which to express the envelope, or {@code null}. |
| * @param geometryAttributes the operation or attribute type from which to get geometry values. |
| * Any element which is {@code null} or has a non-geometric value class will be ignored. |
| * @return an operation which will compute the envelope encompassing all geometries in the given attributes. |
| * @throws FactoryException if a coordinate operation to the target CRS can not be created. |
| */ |
| public static AbstractOperation envelope(final Map<String,?> identification, final CoordinateReferenceSystem crs, |
| final AbstractIdentifiedType... geometryAttributes) throws FactoryException |
| { |
| ArgumentChecks.ensureNonNull("geometryAttributes", geometryAttributes); |
| return POOL.unique(new EnvelopeOperation(identification, crs, geometryAttributes)); |
| } |
| } |