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;
}