This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 6b91e57ba71127557bac87844c45f276c6b3e1ab
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Nov 23 20:01:21 2023 +0100

    `PropertyValue.getXPath()` shall reformat the property name to XPath using 
"Q{namespace}" syntax when necessary.
    Conversely, `FeatureQuery` needs to convert XPath to property name.
---
 .../org/apache/sis/feature/internal/Resources.java |  5 ++
 .../sis/feature/internal/Resources.properties      |  1 +
 .../sis/feature/internal/Resources_fr.properties   |  1 +
 .../org/apache/sis/filter/AssociationValue.java    |  7 +-
 .../main/org/apache/sis/filter/PropertyValue.java  | 18 +++++-
 .../main/org/apache/sis/filter/internal/XPath.java | 74 +++++++++++++++++-----
 .../test/org/apache/sis/filter/XPathTest.java      | 20 ++++++
 .../main/org/apache/sis/storage/FeatureQuery.java  | 18 +++---
 8 files changed, 110 insertions(+), 34 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
index b6b8dfc93c..769fa14109 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.java
@@ -399,6 +399,11 @@ public class Resources extends IndexedResourceBundle {
          */
         public static final short PropertyAlreadyExists_2 = 58;
 
+        /**
+         * Property name “{0}” is invalid because names cannot be XPath.
+         */
+        public static final short PropertyNameCannotBeXPath_1 = 89;
+
         /**
          * No property named “{1}” has been found in “{0}” feature.
          */
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
index f74775328c..6f1591f2f9 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources.properties
@@ -86,6 +86,7 @@ OptionalLibraryNotFound_2         = The {0} optional library 
is not available. G
 OutOfIteratorDomain_2             = The ({0,number}, {1,number}) pixel 
coordinate is outside iterator domain.
 PointOutsideCoverageDomain_1      = Point ({0}) is outside the coverage domain.
 PropertyAlreadyExists_2           = Property \u201c{1}\u201d already exists in 
feature \u201c{0}\u201d.
+PropertyNameCannotBeXPath_1       = Property name \u201c{0}\u201d is invalid 
because names cannot be XPath.
 PropertyNotFound_2                = No property named \u201c{1}\u201d has been 
found in \u201c{0}\u201d feature.
 SourceImagesDoNotIntersect        = Source images do not intersect.
 TileErrorFlagSet_2                = Tile ({0}, {1}) is unavailable because of 
error in a previous calculation attempt.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
index 1c52e7374d..cdfb25398e 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/internal/Resources_fr.properties
@@ -91,6 +91,7 @@ OptionalLibraryNotFound_2         = La biblioth\u00e8que 
optionnelle {0} n\u2019
 OutOfIteratorDomain_2             = La coordonn\u00e9e pixel ({0,number}, 
{1,number}) est en dehors du domaine de l\u2019it\u00e9rateur.
 PointOutsideCoverageDomain_1      = Le point ({0}) est en dehors du domaine de 
la couverture de donn\u00e9es.
 PropertyAlreadyExists_2           = La propri\u00e9t\u00e9 
\u00ab\u202f{1}\u202f\u00bb existe d\u00e9j\u00e0 dans l\u2019entit\u00e9 
\u00ab\u202f{0}\u202f\u00bb.
+PropertyNameCannotBeXPath_1       = Le nom de propri\u00e9t\u00e9 
\u00ab\u202f{0}\u202f\u00bb est invalide parce que ces noms ne doivent pas 
\u00eatre des XPaths.
 PropertyNotFound_2                = Aucune propri\u00e9t\u00e9 nomm\u00e9e 
\u00ab\u202f{1}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9e dans 
l\u2019entit\u00e9 \u00ab\u202f{0}\u202f\u00bb.
 SourceImagesDoNotIntersect        = Des images sources ne s\u2019intersectent 
pas.
 TileErrorFlagSet_2                = La tuile ({0}, {1}) est indisponible pour 
cause d\u2019erreur lors d\u2019une tentative ant\u00e9rieure de calcul.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
index 3b8242bf4c..71fd0754e3 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/AssociationValue.java
@@ -24,7 +24,6 @@ import java.util.Optional;
 import org.apache.sis.feature.Features;
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.feature.builder.PropertyTypeBuilder;
-import org.apache.sis.filter.internal.XPath;
 import org.apache.sis.math.FunctionProperty;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -124,11 +123,7 @@ final class AssociationValue<V> extends 
LeafExpression<Feature, V>
      */
     @Override
     public final String getXPath() {
-        String s = new XPath(path, accessor.name).toString();
-        if (accessor.isVirtual) {
-            s = PropertyValue.VIRTUAL_PREFIX.concat(s);
-        }
-        return s;
+        return accessor.getXPath(path);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 6b77bf77e6..1f550a9bfa 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -76,7 +76,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
     /**
      * The prefix in a XPath for considering a property as virtual.
      */
-    static final String VIRTUAL_PREFIX = "/*/";
+    private static final String VIRTUAL_PREFIX = "/*/";
 
     /**
      * Creates a new expression retrieving values from a property of the given 
name.
@@ -142,11 +142,23 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
     }
 
     /**
-     * Returns the name of the property whose value will be returned by the 
{@link #apply(Object)} method.
+     * Returns the XPath to the property whose value will be returned by the 
{@link #apply(Object)} method.
+     *
+     * @return XPath to the property.
      */
     @Override
     public final String getXPath() {
-        return isVirtual ? VIRTUAL_PREFIX.concat(name) : name;
+        return getXPath(null);
+    }
+
+    /**
+     * Returns the XPath to the property, prefixed by the given path.
+     *
+     * @param  path  the path to append as a prefix.
+     * @return XPath to the property.
+     */
+    final String getXPath(final String[] path) {
+        return XPath.toString(isVirtual ? VIRTUAL_PREFIX : null, path, name);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
index d8efcd3069..d85f1b3d82 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/internal/XPath.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.DefaultNameSpace;
 import static org.apache.sis.util.CharSequences.*;
+import org.apache.sis.feature.internal.Resources;
 
 
 /**
@@ -35,6 +36,8 @@ public final class XPath {
      * The separator between path components.
      * Should not be used for URL or Unix name separator, even if the 
character is the same.
      * We use this constant for identifying locations in the code where there 
is some XPath parsing.
+     *
+     * @see #toSimpleString()
      */
     public static final char SEPARATOR = '/';
 
@@ -72,17 +75,9 @@ public final class XPath {
     public boolean isAbsolute;
 
     /**
-     * Creates a XPath with the given path components.
-     * The components are assumed already parsed (no braced URI literals).
-     *
-     * @param  path  components of the XPath before the tip, or {@code null} 
if none.
-     * @param  tip   the last component of the XPath.
+     * Creates an initially empty XPath. Caller is responsible for 
initializing the fields.
      */
-    public XPath(final String[] path, final String tip) {
-        if (path != null) {
-            this.path = Arrays.asList(path);
-        }
-        this.tip = tip;
+    private XPath() {
     }
 
     /**
@@ -187,6 +182,22 @@ public final class XPath {
         }
     }
 
+    /**
+     * Appends a string representation of the XPath in the given buffer.
+     *
+     * @param  sb  where to write the string representation.
+     * @return the string builder, for chained method calls.
+     */
+    private StringBuilder toString(final StringBuilder sb) {
+        if (isAbsolute) sb.append(SEPARATOR);
+        if (path != null) {
+            for (final String component : path) {
+                toBracedURI(component, sb).append(SEPARATOR);
+            }
+        }
+        return toBracedURI(tip, sb);
+    }
+
     /**
      * Rewrites the XPath from its components and the tip.
      *
@@ -197,13 +208,44 @@ public final class XPath {
         if (!isAbsolute && path == null && tip.indexOf(SEPARATOR) < 0) {
             return tip;
         }
-        final var sb = new StringBuilder(40);
-        if (isAbsolute) sb.append(SEPARATOR);
+        return toString(new StringBuilder(tip.length() + 10)).toString();
+    }
+
+    /**
+     * Rewrites a property name as an XPath.
+     * The path components are assumed already parsed (no braced URI literals).
+     *
+     * @param  prefix  a prefix, or {@code null} if none.
+     * @param  path    components of the XPath before the tip, or {@code null} 
if none.
+     * @param  tip     the last component of the XPath.
+     * @return the given path and tip reformatted as an XPath.
+     */
+    public static String toString(final String prefix, final String[] path, 
final String tip) {
+        final var x = new XPath();
         if (path != null) {
-            for (final String component : path) {
-                toBracedURI(component, sb).append(SEPARATOR);
-            }
+            x.path = Arrays.asList(path);
+        }
+        x.tip = tip;
+        if (prefix != null) {
+            return x.toString(new StringBuilder(tip.length() + 
10).append(prefix)).toString();
+        } else {
+            return x.toString();
+        }
+    }
+
+    /**
+     * Rewrites the XPath in a form accepted by feature properties.
+     * This is the tip, without {@code "Q{namespace}"} escaping.
+     *
+     * @param  xpath  the XPath to convert to a property name.
+     * @return the XPath as a property name without escape syntax for 
qualified URIs.
+     * @throws IllegalArgumentException if the given XPath contains a path 
instead of only a tip.
+     */
+    public static String toPropertyName(final String xpath) {
+        final var x = new XPath(xpath);
+        if (x.path == null) {
+            return x.tip;
         }
-        return toBracedURI(tip, sb).toString();
+        throw new 
IllegalArgumentException(Resources.format(Resources.Keys.PropertyNameCannotBeXPath_1,
 xpath));
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
index 77e5277285..d7d9fb94a5 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/XPathTest.java
@@ -74,4 +74,24 @@ public final class XPathTest extends TestCase {
         split("/Q{http://example.com/foo/bar}property";,      true,  null,      
   "http://example.com/foo/bar:property";);
         split("Q{http://example.com/foo/bar}property/child";, false, new 
String[] {"http://example.com/foo/bar:property"}, "child");
     }
+
+    /**
+     * Tests {@link XPath#toString(String)}.
+     */
+    @Test
+    public void testToString() {
+        assertEquals("Q{http://example.com/foo/bar}property";,
+                XPath.toString(null, null, 
"http://example.com/foo/bar:property";));
+        assertEquals("/*/Q{http://example.com/foo/bar}property/child";,
+                XPath.toString("/*/", new String[] 
{"http://example.com/foo/bar:property"}, "child"));
+    }
+
+    /**
+     * Tests {@link XPath#toPropertyName(String)}.
+     */
+    @Test
+    public void testToPropertyName() {
+        assertEquals("http://example.com/foo/bar:property";,
+                XPath.toPropertyName("Q{http://example.com/foo/bar}property";));
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
index ae094209a9..c841ea117e 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/FeatureQuery.java
@@ -84,7 +84,7 @@ import org.opengis.filter.InvalidFilterValueException;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.1
  */
 public class FeatureQuery extends Query implements Cloneable, Serializable {
@@ -767,21 +767,20 @@ public class FeatureQuery extends Query implements 
Cloneable, Serializable {
                  * from the source feature (the Apache SIS implementation does 
just that). If the name is set,
                  * then we assume that it is correct. Otherwise we take the 
tip of the XPath.
                  */
-                CharSequence text = null;
+                String tip = null;
                 if (expression instanceof ValueReference<?,?>) {
-                    String xpath = ((ValueReference<?,?>) 
expression).getXPath().trim();
+                    tip = XPath.toPropertyName(((ValueReference<?,?>) 
expression).getXPath());
                     /*
-                     * Before to take the tip, take the existing `GenericName` 
instance from the property.
-                     * It should be equivalent, except that it may be a 
`ScopedName` instead of `LocalName`.
+                     * Take the existing `GenericName` instance from the 
property. It should be equivalent to
+                     * creating a name from the tip, except that it may be a 
`ScopedName` instead of `LocalName`.
                      * We do not take `resultType.getName()` because the 
latter is different if the property
                      * is itself a link to another property (in which case 
`resultType` is the final target).
                      */
-                    name = valueType.getProperty(xpath).getName();
+                    name = valueType.getProperty(tip).getName();
                     if (name == null || !names.add(name.toString())) {
                         name = null;
-                        xpath = new XPath(xpath).tip;
-                        if (!(xpath.isEmpty() || names.contains(xpath))) {
-                            text = xpath;
+                        if (tip.isEmpty() || names.contains(tip)) {
+                            tip = null;
                         }
                     }
                 }
@@ -792,6 +791,7 @@ public class FeatureQuery extends Query implements 
Cloneable, Serializable {
                  * providing localized names only if explicitly requested.
                  */
                 if (name == null) {
+                    CharSequence text = tip;
                     if (text == null) do {
                         text = 
Vocabulary.formatInternational(Vocabulary.Keys.Unnamed_1, ++unnamedNumber);
                     } while (!names.add(text.toString()));

Reply via email to