blob: 546f0f2a9ad2c2ee841572a129b2c5a5fb3bf5d9 [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.io.wkt;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import java.util.function.Consumer;
import java.io.Serializable;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.referencing.privy.WKTKeywords;
/**
* A tree of {@link Element}s saved for later use. {@code StoredTree}s are created in following situations:
*
* <ul>
* <li>{@link WKTFormat#addFragment(String, String)} for defining shortcuts
* to be inserted into an arbitrary number of other WKT strings.</li>
* <li>{@link WKTDictionary#addDefinitions(Stream)} for preparing WKT definitions to be parsed
* only when first needed. While WKT trees are waiting, they may share references to same
* {@code Node} instances for reducing memory usage.</li>
* </ul>
*
* This class does not store {@link Element} instances directly because {@code Element}s are not easily shareable.
* Contrarily to {@code Element} design, {@code StoredTree} needs unmodifiable {@link Element#children} list and
* needs to store {@link Element#offset} values in separated arrays. Those changes make possible to have many
* {@code StoredTree} instances sharing the same {@code Node} instances in the common case where some WKT elements
* are repeated in many trees.
*
* @author Martin Desruisseaux (Geomatys)
*/
final class StoredTree implements Serializable {
/**
* Indirectly for {@link WKTFormat} serialization compatibility.
*/
private static final long serialVersionUID = 8436779786449395346L;
/**
* Unmodifiable copy of {@link Element} without contextual information such as {@link Element#offset}.
* The removal of contextual information increase greatly the possibility to reuse the same {@code Node}
* instances in many {@link StoredTree}s. For example, the {@code UNIT["degrees", 0.0174532925199433]} node
* is repeated a lot, so we want to share only one {@code Node} instance for every places in the WKT tree
* where degrees unit is declared, even if they appear at different offsets in the WKT string.
*
* @see StoredTree#root
*/
private static final class Node implements Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 1463070931527783896L;
/**
* Copy of {@link Element#keyword} reference, or {@code null} if this node is anonymous.
* Anonymous nodes are used only as wrappers for array of roots in the corner cases
* documented by {@link StoredTree#root}.
*
* @see StoredTree#keyword()
*/
final String keyword;
/**
* Snapshot of {@link Element#children} list. Array content shall not be modified.
* This array is {@code null} if the keyword was not followed by a pair of brackets
* (e.g. "north"). A null value is not equivalent to an empty list. For example, the
* list is null when parsing {@code "FOO"} but is empty when parsing {@code "FOO[]"}.
*/
@SuppressWarnings("serial") // Not statically typed as Serializable.
private final Object[] children;
/**
* Creates an anonymous node for an array of roots. This constructor is only for the corner
* case documented in <q>Multi roots</q> section of {@link StoredTree#root} javadoc.
*
* @see StoredTree#StoredTree(List, Map)
*/
Node(final Deflater deflater, final List<Element> elements) {
keyword = null;
children = new Node[elements.size()];
for (int i=0; i<children.length; i++) {
children[i] = deflater.unique(new Node(deflater, elements.get(i)));
}
}
/**
* Creates an immutable copy of the given element. Keywords and children references
* are copied in this new {@code Node} but {@link Element#offset}s are copied in a
* separated array for making possible to share {@code Node} instances.
*
* @see StoredTree#StoredTree(Element, Map)
*/
Node(final Deflater deflater, final Element element) {
keyword = (String) deflater.unique(element.keyword);
children = element.getChildren();
if (children != null) {
for (int i=0; i<children.length; i++) {
Object child = children[i];
if (child instanceof Element) {
child = new Node(deflater, (Element) child);
}
children[i] = deflater.unique(child);
}
}
deflater.addOffset(element);
}
/**
* Copies this node in modifiable {@link Element}s and add them to the given list.
* This is the converse of the {@link #Node(Deflater, List)} constructor.
* This method usually adds exactly one element to the given list, except
* for the "multi-roots" corner case documented in {@link StoredTree#root}.
*
* @see StoredTree#toElements(AbstractParser, Collection, int)
*/
final void toElements(final Inflater inflater, final Collection<? super Element> addTo) {
if (keyword != null) {
addTo.add(toElement(inflater)); // Standard case.
} else {
for (final Node child : (Node[]) children) {
addTo.add(child.toElement(inflater));
}
}
}
/**
* Copies this node in a modifiable {@link Element}.
* This is the converse of the {@link #Node(Deflater, Element)} constructor.
*/
private Element toElement(final Inflater inflater) {
final LinkedList<Object> list;
if (children == null) {
list = null;
} else {
list = new LinkedList<>();
for (Object child : children) {
if (child instanceof Node) {
child = ((Node) child).toElement(inflater);
}
list.add(child);
}
}
// Offsets must be read in the same order as they have been written.
return new Element(keyword, list, inflater.nextOffset(), inflater.errorLocale);
}
/**
* Returns the last element of the given names.
* This method searches only in children of this node.
* It does not search recursively in children of children.
*
* @param keys the element names (e.g. {@code "ID"}).
* @return the last {@link Node} of the given names found in the children, or {@code null} if none.
*/
final Node peekLastElement(final String... keys) {
if (children != null) {
for (int i = children.length; --i >= 0;) {
final Object object = children[i];
if (object instanceof Node) {
final Node node = (Node) object;
if (node.children != null) {
for (final String key : keys) {
// Keyword is never null for children.
if (node.keyword.equalsIgnoreCase(key)) {
return node;
}
}
}
}
}
}
return null;
}
/**
* Returns the next values (not child elements).
* The maximum number of values fetched is the length of the given array.
* If there is less WKT elements, remaining array elements are unchanged.
*
* @param addTo non-empty array where to store the values.
* @param index index where to store the first element in given array.
*/
final void peekValues(final Object[] addTo, int index) {
for (final Object object : children) {
if (!(object instanceof Node)) {
addTo[index] = object;
if (++index >= addTo.length) break;
}
}
}
/**
* Adds keyword and children to the given supplier.
* Children of children are added recursively.
* This method is for testing purposes only.
*
* @see StoredTree#forEachValue(Consumer)
*/
final void forEachValue(final Consumer<Object> addTo) {
if (keyword != null) {
addTo.accept(keyword);
}
if (children != null) {
for (final Object child : children) {
addTo.accept(child);
if (child instanceof Node) {
((Node) child).forEachValue(addTo);
}
}
}
}
/**
* Returns the string representation of the first value, which is usually the element name.
* For example, in {@code DATUM["WGS 84", …]} this is "WGS 84". If there are no children then
* this method returns the keyword, which is usually an enumeration value (for example "NORTH"}).
*
* @see StoredTree#toString()
*/
@Override
public String toString() {
return (children != null && children.length != 0) ? String.valueOf(children[0]) : keyword;
}
/**
* Returns a hash code value for this node. It uses hash codes of child elements, except
* for children that are instances of {@link Node} for which identity hash codes are used.
* We avoid requesting "normal" hash code of child {@link Node} because the tree structure
* may cause the same hash codes to be computed many times. The use of identity hash codes
* is sufficient if children have been replaced by unique instances before to compute the
* hash code of this {@link Node}. This replacement is done by {@link Deflater#unique(Node)}.
*
* @see Deflater#unique(Node)
*/
@Override
public int hashCode() {
// We never use hashCode()/equals(Object) with anonymous node (null keyword).
int hash = keyword.hashCode();
if (children != null) {
for (final Object value : children) {
hash = 31*hash + ((value instanceof Node) ? System.identityHashCode(value) : value.hashCode());
}
}
return hash;
}
/**
* Returns whether the given object is equal to this {@code Node}, comparing keyword and children.
* Nested {@link Node}s are compared by identity comparisons; see {@link #hashCode()} for rational.
*
* @see #hashCode()
* @see Deflater#unique(Node)
*/
@Override
public boolean equals(final Object other) {
if (other instanceof Node) {
final Node that = (Node) other;
// We never use hashCode()/equals(Object) with anonymous node (null keyword).
if (keyword.equals(that.keyword)) {
if (children == that.children) {
return true;
}
if (children != null && that.children != null && children.length == that.children.length) {
for (int i=0; i<children.length; i++) {
final Object value = children[i];
final Object otherValue = that.children[i];
if (!(value instanceof Node ? (value == otherValue) : value.equals(otherValue))) {
// Identity comparison of `Node` instances for consistency with `hashCode()`.
return false;
}
}
return true;
}
}
}
return false;
}
}
/**
* Root of a tree of {@link Element} snapshots.
*
* <h4>Multi-roots</h4>
* There is exactly one root in the vast majority of cases. However, there is a situation
* where we need to allow more roots: when user wants to represent a coordinate system.
* A WKT 2 coordinate system looks like:
*
* {@snippet lang="wkt" :
* CS[Cartesian, 2],
* Axis["Easting (E)", east],
* Axis["Northing (N)", north],
* Unit["metre", 1]
* }
*
* While axes are conceptually parts of coordinate system, they are not declared inside the {@code CS[…]}
* element for historical reasons (for compatibility with WKT 1). For representing such "flattened tree",
* we need an array of roots. We do that by wrapping that array in a synthetic {@link Node} with null
* {@link Node#keyword} (an "anonymous node").
*/
private final Node root;
/**
* Indices in the WKT string where elements have been found. If negative, the actual offset
* value is {@code ~offset} and {@link Element#isFragment} shall be set to {@code true}.
* This array shall not be modified because it may be shared by many {@link StoredTree}s.
*
* @see Deflater#addOffset(Element)
* @see Inflater#nextOffset()
*/
private final short[] offsets;
/**
* Creates a new {@code StoredTree} with a snapshot of given tree of elements.
*
* @param tree root of the tree of WKT elements.
* @param sharedValues pool to use for sharing unique instances of values.
*/
StoredTree(final Element tree, final Map<Object,Object> sharedValues) {
final Deflater deflater = new Deflater(sharedValues);
root = (Node) deflater.unique(new Node(deflater, tree));
offsets = deflater.offsets();
}
/**
* Creates a new {@code StoredTree} with a snapshot of given trees of elements.
* This is for a corner case only; see <q>Multi roots</q> in {@link #root}.
*
* @param trees roots of the trees of WKT elements.
* @param sharedValues pool to use for sharing unique instances of values.
*/
StoredTree(final List<Element> trees, final Map<Object,Object> sharedValues) {
final Deflater deflater = new Deflater(sharedValues);
root = new Node(deflater, trees); // Do not invoke `unique(…)` on anymous node.
offsets = deflater.offsets();
}
/**
* Recreates {@link Element} trees. This method is the converse of the constructor.
* This method usually adds exactly one element to the given list, except
* for the "multi-roots" corner case documented in {@link #root}.
*
* @param parser the parser which will be used for parsing the tree.
* @param addTo where to add the elements.
* @param isFragment non-zero if and only if {@link Element#isFragment} shall be {@code true}.
* In such case, this value must be <code>~{@linkplain Element#offset}</code>.
*/
final void toElements(final AbstractParser parser, final Collection<? super Element> addTo, final int isFragment) {
root.toElements(new Inflater(parser, offsets, isFragment), addTo);
}
/**
* A helper class for compressing a tree of {@link Element}s as a tree of {@link Node}s.
* Contrarily to {@code Element} instances, {@code Node}s instances can be shared between many trees.
* Each instances shall be used for constructing only one {@link Node}. After node construction, this
* instance lives longer in the {@link #sharedValues} map for sharing {@link #offsets} arrays.
*
* @see StoredTree#StoredTree(List, Map)
*/
private static final class Deflater {
/**
* Pool to use for sharing unique instances of values.
* This is a copy of {@link WKTFormat#sharedValues} map.
* This is reset to {@code null} when not needed anymore.
*/
private Map<Object,Object> sharedValues;
/**
* The {@link Element#offset} value of {@link StoredTree#root} together with offsets of all
* {@link Element#children} in iteration order. Order is defined by {@link Node} constructor.
* This array is expanded as needed. Shall not be modified after call to {@link #offsets()}.
*/
private short[] offsets;
/**
* Number of valid elements in {@link #offsets}.
*/
private int count;
/**
* Pool of previously constructed values used for replacing equal instances by unique instances.
* May contain {@link String}, {@link Long}, {@link Double} and {@link Node} instances among others.
*
* @param sharedValues pool of previously created objects.
*/
Deflater(final Map<Object,Object> sharedValues) {
this.sharedValues = sharedValues;
offsets = new short[24];
}
/**
* Returns a unique instance of given object. The given value can be a {@link Node} instance
* provided that it is not an anonymous node (i.e. {@link Node#keyword} shall be non-null).
*
* @param value the value for which to get a unique instance.
* @return a previous instance from the pool, or {@code value} if none.
*
* @see Node#hashCode()
* @see Node#equals(Object)
*/
final Object unique(final Object value) {
final Object existing = sharedValues.putIfAbsent(value, value);
return (existing != null) ? existing : value;
}
/**
* Adds the given {@link Element#offset} value.
*
* @see Inflater#nextOffset()
*/
final void addOffset(final Element element) {
if (count >= offsets.length) {
offsets = Arrays.copyOf(offsets, count * 2);
}
int offset = Math.min(Short.MAX_VALUE, Math.max(0, element.offset));
if (element.isFragment) offset = ~offset;
offsets[count++] = (short) offset;
}
/**
* Returns all {@link Element#offset} values in iteration order.
* This method may return an array shared by different {@link Node} instances; do not modify.
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
final short[] offsets() {
offsets = ArraysExt.resize(offsets, count);
final short[] other = (short[]) sharedValues.putIfAbsent(this, offsets);
sharedValues = null;
if (other != null) {
offsets = other;
}
return offsets;
}
/**
* Compares the {@link #offsets} arrays for equality. This is used by {@link #offsets()}
* (indirectly, through {@link Map}) as a workaround for Java arrays not overriding
* {@code equals(Object)} method.
*/
@Override
public boolean equals(final Object other) {
assert offsets.length == count;
return (other instanceof Deflater) && Arrays.equals(offsets, ((Deflater) other).offsets);
}
/**
* Computes a hash code value based only on the {@link #offsets} array.
* See {@link #equals(Object)} for rational.
*/
@Override
public int hashCode() {
assert offsets.length == count;
return Arrays.hashCode(offsets);
}
}
/**
* A helper class for decompressing a tree of {@link Element}s from a tree of {@link Node}s.
* This is the converse of {@link Deflater}.
*
* @see StoredTree#toElements(AbstractParser, Collection, int)
*/
private static final class Inflater {
/**
* If {@link Element#offset} must be fixed to a value, the bitwise NOT value of that offset.
* Otherwise 0. This field packs two information:
*
* <ul>
* <li>{@link Element#isFragment} = ({@code isFragment} != 0)</li>
* <li>If {@code isFragment} is {@code true}, then:
* <ul><li>{@link Element#offset} = {@code ~isFragment}</li></ul>
* </li>
* </ul>
*/
private final int isFragment;
/**
* The {@link StoredTree#offsets} array. Shall not be modified because potentially shared.
* Ignored if {@link #isFragment} != 0.
*/
private final short[] offsets;
/**
* Index of the next offset to return in the {@link #offsets} array.
* Ignored if {@link #isFragment} != 0.
*/
private int index;
/**
* Locale to use for producing error message.
*/
final Locale errorLocale;
/**
* Creates a new inflater.
*
* @param parser the parser which will be used for parsing the tree.
* @param offsets the {@link StoredTree#offsets} array. Will not be modified.
* @param isFragment non-zero if and only if {@link Element#isFragment} is {@code true}.
* In such case, this value must be <code>~{@linkplain Element#offset}</code>.
*/
Inflater(final AbstractParser parser, final short[] offsets, final int isFragment) {
this.errorLocale = parser.errorLocale;
this.isFragment = isFragment;
this.offsets = offsets;
}
/**
* Returns the value to assign to {@link Element#offset} for the next element.
*/
final int nextOffset() {
return (isFragment != 0) ? isFragment : offsets[index++];
}
}
/**
* Stores identifier information in the given array. This method locates the last {@code "ID"} (WKT 2)
* or {@code "AUTHORITY"} (WKT 1) node and optionally the {@code "CITATION"} sub-node. Values are copied
* in the given array, in that order!
*
* <ol>
* <li>Code space</li>
* <li>Code</li>
* <li>Version if present</li>
* <li>Authority if present (skipped if the array length is less than 4)</li>
* </ol>
*
* If any of above values is missing, the corresponding array element is left unchanged.
* Callers should set all array elements to {@code null} before to invoke this method.
*
* @param fullId where to store code space, code, version, authority.
*/
final void peekIdentifiers(final Object[] fullId) {
Node id = root.peekLastElement(MathTransformParser.ID_KEYWORDS);
if (id != null) {
id.peekValues(fullId, 0);
if (fullId.length >= 4) {
id = id.peekLastElement(WKTKeywords.Citation);
if (id != null) {
id.peekValues(fullId, 3);
}
}
}
}
/**
* Adds keywords and children to the given supplier. This is for testing purposes only.
*
* @see WKTDictionary#forEachValue(Consumer)
*/
final void forEachValue(final Consumer<Object> addTo) {
root.forEachValue(addTo);
}
/**
* Returns the keyword of the root element.
*/
final String keyword() {
return root.keyword;
}
/**
* Returns the string representation of the first value of the root element, which is usually the element name.
* For example, in {@code DATUM["WGS 84", …]} this is "WGS 84". If there are no children then this method returns
* the keyword, which is usually an enumeration value (for example "NORTH"}).
*/
@Override
public String toString() {
return root.toString();
}
}