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

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 1ed175315f The “Find Reference” feature in the File Explorer does not 
work on Windows #8062 (#8067)
1ed175315f is described below

commit 1ed175315f545f17e0b6f93f35eb9fd4778bcb6d
Author: Nicolas Adment <[email protected]>
AuthorDate: Wed Aug 26 14:29:50 2026 +0200

    The “Find Reference” feature in the File Explorer does not work on Windows 
#8062 (#8067)
    
    - Add normalizeFilePath() helper method in MetadataReferenceFinder to 
normalize file paths with forward slashes for cross-platform compatibility
    - Apply normalization in toDisplayPath() methods across ExplorerPerspective 
and MetadataPerspective
    - Use char literals instead of string literals for single character 
concatenation
---
 .../metadata/refactor/MetadataReferenceFinder.java | 55 ++++++++++++----------
 .../refactor/MetadataReferenceFinderTest.java      | 31 ++++++++++++
 .../perspective/explorer/ExplorerPerspective.java  |  4 +-
 .../perspective/metadata/MetadataPerspective.java  |  5 +-
 4 files changed, 65 insertions(+), 30 deletions(-)

diff --git 
a/engine/src/main/java/org/apache/hop/metadata/refactor/MetadataReferenceFinder.java
 
b/engine/src/main/java/org/apache/hop/metadata/refactor/MetadataReferenceFinder.java
index 3b56905ea7..5b0d9fdbdd 100644
--- 
a/engine/src/main/java/org/apache/hop/metadata/refactor/MetadataReferenceFinder.java
+++ 
b/engine/src/main/java/org/apache/hop/metadata/refactor/MetadataReferenceFinder.java
@@ -17,12 +17,12 @@
 
 package org.apache.hop.metadata.refactor;
 
+import java.io.File;
 import java.io.OutputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashSet;
@@ -36,9 +36,9 @@ import org.apache.commons.vfs2.FileSelector;
 import org.apache.commons.vfs2.FileType;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
-import org.apache.hop.core.exception.HopXmlException;
 import org.apache.hop.core.plugins.ActionPluginType;
 import org.apache.hop.core.plugins.IPlugin;
+import org.apache.hop.core.plugins.IPluginType;
 import org.apache.hop.core.plugins.PluginRegistry;
 import org.apache.hop.core.plugins.TransformPluginType;
 import org.apache.hop.core.variables.IVariables;
@@ -56,6 +56,7 @@ import org.apache.hop.pipeline.engine.IPipelineEngine;
 import org.apache.hop.pipeline.engine.PipelineEnginePluginType;
 import org.apache.hop.workflow.engine.IWorkflowEngine;
 import org.apache.hop.workflow.engine.WorkflowEnginePluginType;
+import org.jspecify.annotations.Nullable;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -207,8 +208,7 @@ public class MetadataReferenceFinder {
             // DataProbeLocation.sourcePipelineFilename)
             // are still discovered.
             Type[] typeArgs = ((ParameterizedType) 
field.getGenericType()).getActualTypeArguments();
-            if (typeArgs.length == 1 && typeArgs[0] instanceof Class) {
-              Class<?> itemClass = (Class<?>) typeArgs[0];
+            if (typeArgs.length == 1 && typeArgs[0] instanceof Class<?> 
itemClass) {
               if (fieldType != HopMetadataPropertyType.NONE) {
                 scanListItemClassForType(map, metadataKey, field.getName(), 
itemClass, fieldType);
               } else {
@@ -255,7 +255,7 @@ public class MetadataReferenceFinder {
   private <E> void scanEngineRunConfigPlugins(
       Map<HopMetadataPropertyType, List<MetadataClassField>> map,
       PluginRegistry registry,
-      Class<? extends org.apache.hop.core.plugins.IPluginType> pluginType,
+      Class<? extends IPluginType> pluginType,
       Class<E> engineClass,
       String containerMetadataKey,
       String nestedFieldName) {
@@ -325,6 +325,19 @@ public class MetadataReferenceFinder {
     }
   }
 
+  /**
+   * Normalizes a file path by trimming whitespace and converting all 
platform-specific file
+   * separators to forward slashes.
+   *
+   * @param path the file path to normalize
+   */
+  protected static @Nullable String normalizeFilePath(@Nullable String path) {
+    if (path == null) {
+      return null;
+    }
+    return path.trim().replace(File.separatorChar, '/');
+  }
+
   /**
    * Returns {@code true} when the stored field value equals {@code filePath}, 
optionally resolving
    * variables (e.g. {@code ${PROJECT_HOME}/pipeline.hpl}) before comparing.
@@ -333,14 +346,14 @@ public class MetadataReferenceFinder {
     if (value == null) {
       return false;
     }
-    String stored = value.toString().trim();
+    String stored = normalizeFilePath(value.toString());
     if (filePath.equals(stored)) {
       return true;
     }
     if (variables != null && !stored.isEmpty()) {
       try {
-        String resolved = variables.resolve(stored);
-        return resolved != null && resolved.trim().equals(filePath);
+        String resolved = normalizeFilePath(variables.resolve(stored));
+        return resolved.equals(filePath);
       } catch (Exception ignored) {
         // keep false
       }
@@ -359,7 +372,7 @@ public class MetadataReferenceFinder {
       if (projectHome != null && !projectHome.isEmpty() && 
newPath.startsWith(projectHome)) {
         String rel = newPath.substring(projectHome.length());
         if (!rel.startsWith("/")) {
-          rel = "/" + rel;
+          rel = '/' + rel;
         }
         return Const.VAR_PROJECT_HOME + rel;
       }
@@ -475,6 +488,8 @@ public class MetadataReferenceFinder {
     if (StringUtils.isEmpty(filePath)) {
       return Collections.emptyList();
     }
+    filePath = normalizeFilePath(filePath);
+
     Set<String> tagNames = getFileReferenceTagNames();
     if (tagNames.isEmpty()) {
       return Collections.emptyList();
@@ -633,7 +648,7 @@ public class MetadataReferenceFinder {
    * Used when searching metadata objects for references to a renamed 
pipeline/workflow file.
    */
   private static final List<HopMetadataPropertyType> FILE_PATH_PROPERTY_TYPES =
-      Arrays.asList(
+      List.of(
           HopMetadataPropertyType.PIPELINE_FILE,
           HopMetadataPropertyType.WORKFLOW_FILE,
           HopMetadataPropertyType.HOP_FILE);
@@ -655,6 +670,8 @@ public class MetadataReferenceFinder {
     if (StringUtils.isEmpty(filePath)) {
       return Collections.emptyList();
     }
+    filePath = normalizeFilePath(filePath);
+
     List<MetadataObjectReference> results = new ArrayList<>();
     Set<MetadataObjectReference> seen = new HashSet<>();
     try {
@@ -1129,8 +1146,6 @@ public class MetadataReferenceFinder {
       try (OutputStream out = 
HopVfs.getOutputStream(HopVfs.getFileObject(filePath), false)) {
         out.write(xml.getBytes(java.nio.charset.StandardCharsets.UTF_8));
       }
-    } catch (HopXmlException e) {
-      throw new HopException("Error updating references in file: " + filePath, 
e);
     } catch (Exception e) {
       throw new HopException("Error updating references in file: " + filePath, 
e);
     }
@@ -1158,8 +1173,6 @@ public class MetadataReferenceFinder {
       try (OutputStream out = 
HopVfs.getOutputStream(HopVfs.getFileObject(filePath), false)) {
         out.write(xml.getBytes(java.nio.charset.StandardCharsets.UTF_8));
       }
-    } catch (HopXmlException ex) {
-      throw new HopException("Error updating references in file: " + filePath, 
ex);
     } catch (Exception ex) {
       throw new HopException("Error updating references in file: " + filePath, 
ex);
     }
@@ -1189,20 +1202,10 @@ public class MetadataReferenceFinder {
       if (tagNames.contains(tagName)) {
         String value = XmlHandler.getNodeValue(node);
         if (value != null) {
-          String trimmed = value.trim();
-          boolean matches = trimmed.equals(elementName);
-          if (!matches && variables != null) {
-            try {
-              String resolved = variables.resolve(trimmed);
-              matches = resolved != null && 
resolved.trim().equals(elementName);
-            } catch (Exception ignored) {
-              // keep matches false
-            }
-          }
-          if (matches) {
+          if (matchesFilePath(value, elementName, variables)) {
             count[0]++;
             if (replace && node instanceof Element el && newNameForReplace != 
null) {
-              el.setTextContent(computeNewFilePath(trimmed, newNameForReplace, 
variables));
+              el.setTextContent(computeNewFilePath(value, newNameForReplace, 
variables));
             }
           }
         }
diff --git 
a/engine/src/test/java/org/apache/hop/metadata/refactor/MetadataReferenceFinderTest.java
 
b/engine/src/test/java/org/apache/hop/metadata/refactor/MetadataReferenceFinderTest.java
index 3c850bb4ff..7f652bbe5f 100644
--- 
a/engine/src/test/java/org/apache/hop/metadata/refactor/MetadataReferenceFinderTest.java
+++ 
b/engine/src/test/java/org/apache/hop/metadata/refactor/MetadataReferenceFinderTest.java
@@ -18,8 +18,11 @@
 package org.apache.hop.metadata.refactor;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -214,6 +217,34 @@ class MetadataReferenceFinderTest {
                 List.of(new 
MetadataObjectReference("pipeline-run-configuration", "Local"))));
   }
 
+  // 
---------------------------------------------------------------------------
+  // normalizeFilePath
+  // 
---------------------------------------------------------------------------
+
+  @Test
+  void normalizeFilePath_nullReturnsNull() throws Exception {
+    assertNull(MetadataReferenceFinder.normalizeFilePath(null));
+  }
+
+  @Test
+  void normalizeFilePath_blankStringIsTrimmedToEmpty() throws Exception {
+    assertEquals("", MetadataReferenceFinder.normalizeFilePath("   "));
+  }
+
+  @Test
+  void normalizeFilePath_trimsLeadingAndTrailingWhitespace() throws Exception {
+    assertEquals("/some/path.hpl", MetadataReferenceFinder.normalizeFilePath(" 
 /some/path.hpl  "));
+  }
+
+  @Test
+  void normalizeFilePath_trimsThenReplacesSeparators() throws Exception {
+    // This test is only relevant on Windows
+    assertEquals(
+        "some/nested/path.hpl",
+        MetadataReferenceFinder.normalizeFilePath(
+            "some" + File.separatorChar + "nested" + File.separatorChar + 
"path.hpl  "));
+  }
+
   // 
---------------------------------------------------------------------------
   // helpers
   // 
---------------------------------------------------------------------------
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index a43e14e68c..5601a535e6 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -1279,8 +1279,8 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
    */
   private static String toDisplayPath(String path, String projectHome) {
     if (!StringUtils.isEmpty(projectHome) && path.startsWith(projectHome)) {
-      String rel = path.substring(projectHome.length());
-      return Const.VAR_PROJECT_HOME + (rel.startsWith("/") ? rel : "/" + rel);
+      String rel = 
path.substring(projectHome.length()).replace(File.separatorChar, '/');
+      return Const.VAR_PROJECT_HOME + (rel.startsWith("/") ? rel : '/' + rel);
     }
     return path;
   }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
index e7443aa509..fcb3580bf0 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java
@@ -17,6 +17,7 @@
 
 package org.apache.hop.ui.hopgui.perspective.metadata;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -2161,8 +2162,8 @@ public class MetadataPerspective implements 
IHopPerspective, TabClosable, IMetad
    */
   private static String toDisplayPath(String path, String projectHome) {
     if (!Utils.isEmpty(projectHome) && path.startsWith(projectHome)) {
-      String rel = path.substring(projectHome.length());
-      return Const.VAR_PROJECT_HOME + (rel.startsWith("/") ? rel : "/" + rel);
+      String rel = 
path.substring(projectHome.length()).replace(File.separatorChar, '/');
+      return Const.VAR_PROJECT_HOME + (rel.startsWith("/") ? rel : '/' + rel);
     }
     return path;
   }

Reply via email to