blob: 24efcf6322bf2310401097b8674092ec2360587b [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.feature;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import java.util.Collections;
import java.util.HashMap;
import java.util.function.BiFunction;
import java.io.IOException;
import java.io.UncheckedIOException;
import org.opengis.util.GenericName;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.apache.sis.util.Classes;
/**
* Describes the behaviour of a feature type as a function or a method.
* Operations can:
*
* <ul>
* <li>Compute values from the attributes.</li>
* <li>Perform actions that change the attribute values.</li>
* </ul>
*
* <div class="note"><b>Example:</b> a mutator operation may raise the height of a dam. This changes
* may affect other properties like the watercourse and the reservoir associated with the dam.</div>
*
* The value is computed, or the operation is executed, by {@code apply(Feature, ParameterValueGroup)}.
* If the value is modifiable, new value can be set by call to {@code Attribute.setValue(Object)}.
*
* <div class="warning"><b>Warning:</b> this class is experimental and may change after we gained more
* experience on this aspect of ISO 19109.</div>
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
*
* @see DefaultFeatureType
*
* @since 0.6
*/
public abstract class AbstractOperation extends AbstractIdentifiedType
implements BiFunction<AbstractFeature, ParameterValueGroup, Object>
{
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -179930765502963170L;
/**
* The prefix for result identification entries in the {@code identification} map.
* This prefix is documented in {@link FeatureOperations} javadoc.
*/
static final String RESULT_PREFIX = "result.";
/**
* Constructs an operation from the given properties. The identification map is given unchanged to
* the {@linkplain AbstractIdentifiedType#AbstractIdentifiedType(Map) super-class constructor}.
* The following table is a reminder of main (not all) recognized map entries:
*
* <table class="sis">
* <caption>Recognized map entries (non exhaustive list)</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 #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DEFINITION_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getDefinition()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESIGNATION_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getDesignation()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DESCRIPTION_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getDescription()}</td>
* </tr>
* <tr>
* <td>{@value org.apache.sis.feature.AbstractIdentifiedType#DEPRECATED_KEY}</td>
* <td>{@link Boolean}</td>
* <td>{@link #isDeprecated()}</td>
* </tr>
* </table>
*
* @param identification the name and other information to be given to this operation.
*/
public AbstractOperation(final Map<String,?> identification) {
super(identification);
}
/**
* Returns a map that can be used for creating the {@link #getResult()} type.
* This method can be invoked for subclass constructor with the user supplied map in argument.
* If the given map contains at least one key prefixed by {@value #RESULT_PREFIX}, then the values
* associated to those keys will be used.
*
* @param identification the map given by user to sub-class constructor.
*/
final Map<String,Object> resultIdentification(final Map<String,?> identification) {
final Map<String,Object> properties = new HashMap<>(6);
for (final Map.Entry<String,?> entry : identification.entrySet()) {
final String key = entry.getKey();
if (key != null && key.startsWith(RESULT_PREFIX)) {
properties.put(key.substring(RESULT_PREFIX.length()), entry.getValue());
}
}
if (properties.isEmpty()) {
properties.put(NAME_KEY, super.getName()); // Do not invoke user-overrideable method.
properties.put(DEFINITION_KEY, super.getDefinition());
super.getDesignation().ifPresent((i18n) -> properties.put(DESIGNATION_KEY, i18n));
super.getDescription().ifPresent((i18n) -> properties.put(DESCRIPTION_KEY, i18n));
}
return properties;
}
/**
* Returns a description of the input parameters.
*
* @return description of the input parameters.
*/
public abstract ParameterDescriptorGroup getParameters();
/**
* Returns the expected result type, or {@code null} if none.
*
* <div class="warning"><b>Warning:</b> In a future SIS version, the return type may be changed
* to {@code org.opengis.feature.IdentifiedType}. This change is pending GeoAPI revision.</div>
*
* @return the type of the result, or {@code null} if none.
*/
public abstract AbstractIdentifiedType getResult();
/**
* Executes the operation on the specified feature with the specified parameters.
* The value returned by this method depends on the value returned by {@link #getResult()}:
*
* <ul>
* <li>If {@code getResult()} returns {@code null},
* then this method should return {@code null}.</li>
* <li>If {@code getResult()} returns an instance of {@code AttributeType},
* then this method shall return an instance of {@code Attribute}
* and the {@code Attribute.getType() == getResult()} relation should hold.</li>
* <li>If {@code getResult()} returns an instance of {@code FeatureAssociationRole},
* then this method shall return an instance of {@code FeatureAssociation}
* and the {@code FeatureAssociation.getRole() == getResult()} relation should hold.</li>
* </ul>
*
* <div class="note"><b>Analogy:</b>
* if we compare {@code Operation} to {@link java.lang.reflect.Method} in the Java language, then this method is equivalent
* to {@link java.lang.reflect.Method#invoke(Object, Object...)}. The {@code Feature} argument is equivalent to {@code this}
* in the Java language, and may be {@code null} if the operation does not need a feature instance
* (like static methods in the Java language).</div>
*
* <div class="warning"><b>Warning:</b> In a future SIS version, the parameter type and return value may
* be changed to {@code org.opengis.feature.Feature} and {@code org.opengis.feature.Property} respectively.
* This change is pending GeoAPI revision.</div>
*
* @param feature the feature on which to execute the operation.
* Can be {@code null} if the operation does not need feature instance.
* @param parameters the parameters to use for executing the operation.
* Can be {@code null} if the operation does not take any parameters.
* @return the operation result, or {@code null} if this operation does not produce any result.
*/
@Override
public abstract Object apply(AbstractFeature feature, ParameterValueGroup parameters);
/**
* Returns the names of feature properties that this operation needs for performing its task.
* This method does not resolve transitive dependencies, i.e. if a dependency is itself an operation having
* other dependencies, the returned set will contain the name of that operation but not the names of that
* operation dependencies (unless they are the same that the direct dependencies of {@code this}).
*
* <div class="note"><b>Rational:</b>
* this information is needed for writing the {@code SELECT} SQL statement to send to a database server.
* The requested columns will typically be all attributes declared in a {@code FeatureType}, but also
* any additional columns needed for the operation while not necessarily included in the {@code FeatureType}.
* </div>
*
* The default implementation returns an empty set.
*
* @return the names of feature properties needed by this operation for performing its task.
*/
public Set<String> getDependencies() {
return Collections.emptySet();
}
/**
* Returns a hash code value for this operation.
* The default implementation computes a hash code from the {@linkplain #getParameters() parameters descriptor}
* and {@linkplain #getResult() result type}.
*
* @return {@inheritDoc}
*/
@Override
public int hashCode() {
return super.hashCode() + Objects.hashCode(getParameters()) + Objects.hashCode(getResult());
}
/**
* Compares this operation with the given object for equality.
* The default implementation compares the {@linkplain #getParameters() parameters descriptor}
* and {@linkplain #getResult() result type}.
*
* @return {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (super.equals(obj)) {
final AbstractOperation that = (AbstractOperation) obj;
return Objects.equals(getParameters(), that.getParameters()) &&
Objects.equals(getResult(), that.getResult());
}
return false;
}
/**
* Returns a string representation of this operation.
* The returned string is for debugging purpose and may change in any future SIS version.
*
* @return a string representation of this operation for debugging purpose.
*/
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(40).append(Classes.getShortClassName(this)).append('[');
final GenericName name = getName();
if (name != null) {
buffer.append('“');
}
buffer.append(name);
if (name != null) {
buffer.append('”');
}
final AbstractIdentifiedType result = getResult();
if (result != null) {
final Object type;
if (result instanceof DefaultAttributeType<?>) {
type = Classes.getShortName(((DefaultAttributeType<?>) result).getValueClass());
} else {
type = result.getName();
}
buffer.append(" : ").append(type);
}
try {
formatResultFormula(buffer.append("] = "));
} catch (IOException e) {
throw new UncheckedIOException(e); // Should never happen since we write in a StringBuilder.
}
return buffer.toString();
}
/**
* Appends a string representation of the "formula" used for computing the result.
* The "formula" may be for example a link to another property.
*
* @param buffer where to format the "formula".
* @throws IOException if an error occurred while writing in {@code buffer}.
*/
void formatResultFormula(Appendable buffer) throws IOException {
defaultFormula(getParameters(), buffer);
}
/**
* Default implementation of {@link #formatResultFormula(Appendable)},
* to be used also for operations that are not instance of {@link AbstractOperation}.
*/
static void defaultFormula(final ParameterDescriptorGroup parameters, final Appendable buffer) throws IOException {
buffer.append(parameters != null ? name(parameters.getName()) : "operation").append('(');
if (parameters != null) {
boolean hasMore = false;
for (GeneralParameterDescriptor p : parameters.descriptors()) {
if (p != null) {
if (hasMore) buffer.append(", ");
buffer.append(name(p.getName()));
hasMore = true;
}
}
}
buffer.append(')');
}
/**
* Returns a short string representation of the given identifier, or {@code null} if none.
*/
private static String name(final Identifier id) {
return (id != null) ? id.getCode() : null;
}
}