(metadata.keySet());
}
@Override
- public String removeStringProperty(String propertyName) {
+ public synchronized String removeStringProperty(String propertyName) {
changed = true;
return metadata.remove(propertyName);
}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/UserDataPathTransformer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/UserDataPathTransformer.java
new file mode 100644
index 0000000000..e3fab3204b
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/sourcemap/UserDataPathTransformer.java
@@ -0,0 +1,249 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed 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 ghidra.program.database.sourcemap;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.StringUtils;
+
+import ghidra.framework.model.DomainObject;
+import ghidra.framework.model.DomainObjectClosedListener;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.listing.ProgramUserData;
+import ghidra.program.model.sourcemap.SourcePathTransformRecord;
+import ghidra.program.model.sourcemap.SourcePathTransformer;
+
+/**
+ * An implementation of {@link SourcePathTransformer} that stores transform information using
+ * {@link ProgramUserData}. This means that transform information will be stored locally
+ * but not checked in to a shared project.
+ *
+ * Use the static method {@link UserDataPathTransformer#getPathTransformer} to get the transformer
+ * for a program.
+ *
+ * Synchronization policy: {@code userData}, {@code pathMap}, {@code fileMap}, and
+ * {@code programsToTransformers} must be protected.
+ */
+public class UserDataPathTransformer implements SourcePathTransformer, DomainObjectClosedListener {
+
+ private Program program;
+ private static final String USER_FILE_TRANSFORM_PREFIX = "USER_FILE_TRANSFORM_";
+ private static final String USER_PATH_TRANSFORM_PREFIX = "USER_DIRECTORY_TRANSFORM_";
+ private TreeMap pathMap;
+ private Map fileMap;
+ private ProgramUserData userData;
+ private static final String FILE_SCHEME = "file";
+ private static HexFormat hexFormat = HexFormat.of();
+ private static HashMap programsToTransformers =
+ new HashMap<>();
+
+ /**
+ * Returns the path transformer for {@code program}
+ * @param program program
+ * @return path transformer
+ */
+ public static synchronized SourcePathTransformer getPathTransformer(Program program) {
+ if (program == null) {
+ return null;
+ }
+ return programsToTransformers.computeIfAbsent(program, p -> new UserDataPathTransformer(p));
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if {@code directory} is not
+ * a valid, normalized directory path (with forward slashes).
+ * @param directory path to validate
+ */
+ public static void validateDirectoryPath(String directory) {
+ if (StringUtils.isBlank(directory)) {
+ throw new IllegalArgumentException("Blank directory path");
+ }
+ URI uri;
+ try {
+ uri = new URI(FILE_SCHEME, null, directory, null).normalize();
+ }
+ catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ String normalizedPath = uri.getPath();
+ if (!normalizedPath.endsWith("/")) {
+ throw new IllegalArgumentException(directory + " is not a directory path");
+ }
+ if (!directory.equals(normalizedPath)) {
+ throw new IllegalArgumentException(directory + " is not normalized");
+ }
+ return;
+ }
+
+ private UserDataPathTransformer(Program program) {
+ this.program = program;
+ userData = program.getProgramUserData();
+ pathMap = new TreeMap<>(Collections.reverseOrder(UserDataPathTransformer::compareStrings));
+ fileMap = new HashMap<>();
+ reloadMaps();
+ program.addCloseListener(this);
+ }
+
+ @Override
+ public void domainObjectClosed(DomainObject dobj) {
+ synchronized (UserDataPathTransformer.class) {
+ programsToTransformers.remove(program);
+ }
+ }
+
+ @Override
+ public synchronized void addFileTransform(SourceFile sourceFile, String path) {
+ SourceFile validated = new SourceFile(path);
+ if (!validated.getPath().equals(path)) {
+ throw new IllegalArgumentException("path not normalized");
+ }
+ int txId = userData.startTransaction();
+ String sourceString = getString(sourceFile);
+ try {
+ userData.setStringProperty(USER_FILE_TRANSFORM_PREFIX + sourceString,
+ path);
+ }
+ finally {
+ userData.endTransaction(txId);
+ }
+ fileMap.put(sourceString, path);
+ }
+
+ @Override
+ public synchronized void removeFileTransform(SourceFile sourceFile) {
+ int txId = userData.startTransaction();
+ String sourceString = getString(sourceFile);
+ try {
+ userData.removeStringProperty(USER_FILE_TRANSFORM_PREFIX + getString(sourceFile));
+ }
+ finally {
+ userData.endTransaction(txId);
+ }
+ fileMap.remove(sourceString);
+ }
+
+ @Override
+ public synchronized void addDirectoryTransform(String sourceDir, String targetDir) {
+ validateDirectoryPath(sourceDir);
+ validateDirectoryPath(targetDir);
+ int txId = userData.startTransaction();
+ try {
+ userData.setStringProperty(USER_PATH_TRANSFORM_PREFIX + sourceDir, targetDir);
+ }
+ finally {
+ userData.endTransaction(txId);
+ }
+ pathMap.put(sourceDir, targetDir);
+ }
+
+ @Override
+ public synchronized void removeDirectoryTransform(String sourceDir) {
+ int txId = userData.startTransaction();
+ try {
+ userData.removeStringProperty(USER_PATH_TRANSFORM_PREFIX + sourceDir);
+ }
+ finally {
+ userData.endTransaction(txId);
+ }
+ pathMap.remove(sourceDir);
+ }
+
+ @Override
+ public synchronized String getTransformedPath(SourceFile sourceFile,
+ boolean useExistingAsDefault) {
+ String sourceFileString = getString(sourceFile);
+ String mappedFile = fileMap.get(sourceFileString);
+ if (mappedFile != null) {
+ return mappedFile;
+ }
+ String path = sourceFile.getPath();
+ for (String src : pathMap.keySet()) {
+ if (path.startsWith(src)) {
+ return pathMap.get(src) + path.substring(src.length());
+ }
+ }
+ return useExistingAsDefault ? path : null;
+ }
+
+ @Override
+ public synchronized List getTransformRecords() {
+ List transformRecords = new ArrayList<>();
+ for (Entry entry : pathMap.entrySet()) {
+ transformRecords
+ .add(new SourcePathTransformRecord(entry.getKey(), null, entry.getValue()));
+ }
+ for (Entry entry : fileMap.entrySet()) {
+ String sourceFileString = entry.getKey();
+ transformRecords.add(new SourcePathTransformRecord(sourceFileString,
+ getSourceFile(sourceFileString), entry.getValue()));
+ }
+ return transformRecords;
+ }
+
+ private static int compareStrings(String left, String right) {
+ int leftLength = left.length();
+ int rightLength = right.length();
+ if (leftLength != rightLength) {
+ return Integer.compare(leftLength, rightLength);
+ }
+ return StringUtils.compare(left, right);
+ }
+
+ private void reloadMaps() {
+ pathMap.clear();
+ fileMap.clear();
+ for (String key : userData.getStringPropertyNames()) {
+ if (key.startsWith(USER_PATH_TRANSFORM_PREFIX)) {
+ String value = userData.getStringProperty(key, null);
+ if (StringUtils.isBlank(value)) {
+ throw new AssertionError("blank value for path " + key);
+ }
+ pathMap.put(key.substring(USER_PATH_TRANSFORM_PREFIX.length()), value);
+ continue;
+ }
+ if (key.startsWith(USER_FILE_TRANSFORM_PREFIX)) {
+ String value = userData.getStringProperty(key, null);
+ if (StringUtils.isBlank(value)) {
+ throw new AssertionError("blank value for file " + key);
+ }
+ fileMap.put(key.substring(USER_FILE_TRANSFORM_PREFIX.length()), value);
+ }
+ }
+ }
+
+ private String getString(SourceFile sourceFile) {
+ StringBuilder sb = new StringBuilder(sourceFile.getIdType().name());
+ sb.append("#");
+ sb.append(hexFormat.formatHex(sourceFile.getIdentifier()));
+ sb.append("#");
+ sb.append(sourceFile.getPath());
+ return sb.toString();
+ }
+
+ private SourceFile getSourceFile(String sourceFileString) {
+ int firstHash = sourceFileString.indexOf("#");
+ SourceFileIdType type = SourceFileIdType.valueOf(sourceFileString.substring(0, firstHash));
+ int secondHash = sourceFileString.indexOf("#", firstHash + 1);
+ byte[] identifier =
+ hexFormat.parseHex(sourceFileString.subSequence(firstHash + 1, secondHash));
+ String path = sourceFileString.substring(secondHash + 1);
+ return new SourceFile(path, type, identifier);
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformRecord.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformRecord.java
new file mode 100644
index 0000000000..543821e71b
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformRecord.java
@@ -0,0 +1,33 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed 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 ghidra.program.model.sourcemap;
+
+import ghidra.program.database.sourcemap.SourceFile;
+
+/**
+ * A container for a source path transformation. No validation is performed on the inputs.
+ * @param source A path (directory transform) or a String of the form SourceFileIdName + "#" + ID +
+ * "#" + SourceFile path (file transform)
+ * @param sourceFile SourceFile (null for directory tranforms)
+ * @param target transformed path
+ */
+public record SourcePathTransformRecord(String source, SourceFile sourceFile, String target) {
+
+ public boolean isDirectoryTransform() {
+ return source.endsWith("/");
+ }
+
+}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformer.java
new file mode 100644
index 0000000000..a62bd06b37
--- /dev/null
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/sourcemap/SourcePathTransformer.java
@@ -0,0 +1,84 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed 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 ghidra.program.model.sourcemap;
+
+import java.util.List;
+
+import ghidra.program.database.sourcemap.SourceFile;
+
+/**
+ * SourcePathTransformers are used to transform {@link SourceFile} paths. The intended use is
+ * to transform the path of a {@link SourceFile} in a programs's {@link SourceFileManager}
+ * before sending the path to an IDE.
+ *
+ * There are two types of transformations: file and directory. File transforms
+ * map a particular {@link SourceFile} to an absolute file path. Directory transforms
+ * transform an initial segment of a path. For example, the directory transforms
+ * "/c:/users/" -> "/src/test/" sends "/c:/users/dir/file1.c" to "/src/test/dir/file1.c"
+ */
+public interface SourcePathTransformer {
+
+ /**
+ * Adds a new file transform. Any existing file transform for {@code sourceFile} is
+ * overwritten. {@code path} must be a valid, normalized file path (with forward slashes).
+ * @param sourceFile source file (can't be null).
+ * @param path new path
+ */
+ public void addFileTransform(SourceFile sourceFile, String path);
+
+ /**
+ * Removes any file transform for {@code sourceFile}.
+ * @param sourceFile source file
+ */
+ public void removeFileTransform(SourceFile sourceFile);
+
+ /**
+ * Adds a new directory transform. Any existing directory transform for {@code sourceDir}
+ * is overwritten. {@code sourceDir} and {@code targetDir} must be valid, normalized
+ * directory paths (with forward slashes).
+ * @param sourceDir source directory
+ * @param targetDir target directory
+ */
+ public void addDirectoryTransform(String sourceDir, String targetDir);
+
+ /**
+ * Removes any directory transform associated with {@code sourceDir}
+ * @param sourceDir source directory
+ */
+ public void removeDirectoryTransform(String sourceDir);
+
+ /**
+ * Returns the transformed path for {@code sourceFile}. The transformed path is determined as
+ * follows:
+ * - If there is a file transform for {@code sourceFile}, the file transform is applied.
+ * - Otherwise, the most specific directory transform (i.e., longest source directory string)
+ * is applied.
+ * - If no directory transform applies, the value of {@code useExistingAsDefault} determines
+ * whether the path of {@code sourceFile} or {@code null} is returned.
+ *
+ * @param sourceFile source file to transform
+ * @param useExistingAsDefault whether to return sourceFile's path if no transform applies
+ * @return transformed path or null
+ */
+ public String getTransformedPath(SourceFile sourceFile, boolean useExistingAsDefault);
+
+ /**
+ * Returns a list of all {@link SourcePathTransformRecord}s
+ * @return transform records
+ */
+ public List getTransformRecords();
+
+}
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/UserDataPathTransformerTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/UserDataPathTransformerTest.java
new file mode 100644
index 0000000000..ff805d568e
--- /dev/null
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/program/database/sourcemap/UserDataPathTransformerTest.java
@@ -0,0 +1,321 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed 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 ghidra.program.database.sourcemap;
+
+import static org.junit.Assert.*;
+
+import java.util.HexFormat;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.python.google.common.primitives.Longs;
+
+import generic.test.AbstractGenericTest;
+import ghidra.framework.store.LockException;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.sourcemap.*;
+import ghidra.test.ToyProgramBuilder;
+
+public class UserDataPathTransformerTest extends AbstractGenericTest {
+
+ private Program program;
+ private ToyProgramBuilder builder;
+ private SourceFileManager sourceManager;
+ private SourceFile linuxRoot;
+ private SourceFile windowsRoot;
+ private SourceFile linux1;
+ private SourceFile linux2;
+ private SourceFile linux3;
+ private SourceFile windows1;
+ private SourceFile windows2;
+ private SourceFile windows3;
+ private SourcePathTransformer pathTransformer;
+
+ @Before
+ public void setUp() throws Exception {
+ builder = new ToyProgramBuilder("testprogram", true, false, this);
+ program = builder.getProgram();
+ sourceManager = program.getSourceFileManager();
+ int txID = program.startTransaction("create source path transformer test program");
+ try {
+ linuxRoot = new SourceFile("/file1.c");
+ sourceManager.addSourceFile(linuxRoot);
+
+ windowsRoot = new SourceFile("/c:/file2.c");
+ sourceManager.addSourceFile(windowsRoot);
+
+ linux1 = new SourceFile("/src/dir1/file3.c");
+ sourceManager.addSourceFile(linux1);
+
+ linux2 = new SourceFile("/src/dir1/dir2/file4.c");
+ sourceManager.addSourceFile(linux2);
+
+ linux3 = new SourceFile("/src/dir1/dir3/file5.c");
+ sourceManager.addSourceFile(linux3);
+
+ windows1 = new SourceFile("/c:/src/dir1/file6.c");
+ sourceManager.addSourceFile(windows1);
+
+ windows2 = new SourceFile("/c:/src/dir1/dir2/file7.c");
+ sourceManager.addSourceFile(windows2);
+
+ windows3 = new SourceFile("/c:/src/dir1/dir3/file8.c");
+ sourceManager.addSourceFile(windows3);
+ }
+ finally {
+ program.endTransaction(txID, true);
+ }
+ pathTransformer = UserDataPathTransformer.getPathTransformer(program);
+
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullFileTransform() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(null, "/src/test.c");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformFileToDirectory() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(linux1, "/src/dir/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformFileToRelativePath() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(linux1, "src/dir/file.c");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformFileToNull() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(linux1, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryNullSource() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform(null, "/src/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryNullDest() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/test", null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryToFile() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/", linux1.getPath());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testApplyDirectoryTransformToFile() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform(linux1.getPath(), "/src/test");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryInvalidSource1() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("src/test/", "/source/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryInvalidSource2() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/test", "/source/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryInvalidDest1() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/source/", "/src/test");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testTransformDirectoryInvalidDest2() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/source/", "src/test/");
+ }
+
+ @Test
+ public void testNoDefault() {
+ assertNull(pathTransformer.getTransformedPath(linuxRoot, false));
+ assertNull(pathTransformer.getTransformedPath(linux1, false));
+ assertNull(pathTransformer.getTransformedPath(windowsRoot, false));
+ assertNull(pathTransformer.getTransformedPath(windows1, false));
+ }
+
+ @Test
+ public void testTransformFile() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(linux1, "/src/test/newfile.c");
+ assertEquals("/src/test/newfile.c", pathTransformer.getTransformedPath(linux1, true));
+ assertEquals(linux2.getPath(), pathTransformer.getTransformedPath(linux2, true));
+ assertEquals(windowsRoot.getPath(), pathTransformer.getTransformedPath(windowsRoot, true));
+ }
+
+ @Test
+ public void testTransformLinuxToWindows() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/dir1/", "/c:/source/");
+ assertEquals(linuxRoot.getPath(), pathTransformer.getTransformedPath(linuxRoot, true));
+ assertEquals("/c:/source/file3.c", pathTransformer.getTransformedPath(linux1, true));
+ assertEquals("/c:/source/dir2/file4.c", pathTransformer.getTransformedPath(linux2, true));
+ assertEquals("/c:/source/dir3/file5.c", pathTransformer.getTransformedPath(linux3, true));
+ }
+
+ @Test
+ public void testTransformWindowsToLinux() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/c:/src/dir1/", "/source/");
+ assertEquals(windowsRoot.getPath(), pathTransformer.getTransformedPath(windowsRoot, true));
+ assertEquals("/source/file6.c", pathTransformer.getTransformedPath(windows1, true));
+ assertEquals("/source/dir2/file7.c", pathTransformer.getTransformedPath(windows2, true));
+ assertEquals("/source/dir3/file8.c", pathTransformer.getTransformedPath(windows3, true));
+ }
+
+ @Test
+ public void testAddingMoreSpecificDirectoryTransform() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/", "/c:/source/");
+ assertEquals("/c:/source/dir1/file3.c", pathTransformer.getTransformedPath(linux1, true));
+ assertEquals("/c:/source/dir1/dir2/file4.c",
+ pathTransformer.getTransformedPath(linux2, true));
+ pathTransformer.addDirectoryTransform("/src/dir1/dir2/", "/d:/test/");
+ assertEquals("/c:/source/dir1/file3.c", pathTransformer.getTransformedPath(linux1, true));
+ assertEquals("/d:/test/file4.c", pathTransformer.getTransformedPath(linux2, true));
+ pathTransformer.removeDirectoryTransform("/src/");
+ assertEquals(linux1.getPath(), pathTransformer.getTransformedPath(linux1, true));
+ assertEquals("/d:/test/file4.c", pathTransformer.getTransformedPath(linux2, true));
+ pathTransformer.removeDirectoryTransform("/src/dir1/dir2/");
+ assertEquals(linux1.getPath(), pathTransformer.getTransformedPath(linux1, true));
+ assertEquals(linux2.getPath(), pathTransformer.getTransformedPath(linux2, true));
+ }
+
+ @Test
+ public void testAddingDirectoryThenFileTransform() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/", "/c:/source/");
+ assertEquals("/c:/source/dir1/file3.c", pathTransformer.getTransformedPath(linux1, true));
+ pathTransformer.addFileTransform(linux1, "/e:/testDirectory/testFile.c");
+ assertEquals("/e:/testDirectory/testFile.c",
+ pathTransformer.getTransformedPath(linux1, true));
+ pathTransformer.removeFileTransform(linux1);
+ assertEquals("/c:/source/dir1/file3.c", pathTransformer.getTransformedPath(linux1, true));
+ pathTransformer.removeDirectoryTransform("/src/");
+ assertEquals(linux1.getPath(), pathTransformer.getTransformedPath(linux1, true));
+ }
+
+ @Test
+ public void testUniqueness() {
+ SourcePathTransformer transformer2 = UserDataPathTransformer.getPathTransformer(program);
+ assertTrue(transformer2 == pathTransformer);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNormalizedFile() throws IllegalArgumentException {
+ pathTransformer.addFileTransform(linux1, "/src/dir1/../dir2/file.c");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNormalizedSourceDirectory() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/src/dir1/../dir2/", "/test/");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNormalizedDestDirectory() throws IllegalArgumentException {
+ pathTransformer.addDirectoryTransform("/test/", "src/dir1/../dir2/");
+ }
+
+ @Test
+ public void testGetTransformRecords() throws IllegalArgumentException {
+ assertEquals(0, pathTransformer.getTransformRecords().size());
+ pathTransformer.addFileTransform(linux1, "/test/file10.c");
+ List transformRecords = pathTransformer.getTransformRecords();
+ assertEquals(1, transformRecords.size());
+ assertEquals(linux1, transformRecords.get(0).sourceFile());
+ assertEquals("/test/file10.c", transformRecords.get(0).target());
+
+ pathTransformer.addFileTransform(linux1, "/test/file20.c");
+ transformRecords = pathTransformer.getTransformRecords();
+ assertEquals(1, transformRecords.size());
+ assertEquals(linux1, transformRecords.get(0).sourceFile());
+ assertEquals("/test/file20.c", transformRecords.get(0).target());
+
+ pathTransformer.addFileTransform(linux2, "/test/file30.c");
+ transformRecords = pathTransformer.getTransformRecords();
+ assertEquals(2, transformRecords.size());
+ SourcePathTransformRecord rec1 =
+ new SourcePathTransformRecord("NONE##" + linux1.getPath(), linux1, "/test/file20.c");
+ SourcePathTransformRecord rec2 =
+ new SourcePathTransformRecord("NONE##" + linux2.getPath(), linux2, "/test/file30.c");
+ assertTrue(transformRecords.contains(rec1));
+ assertTrue(transformRecords.contains(rec2));
+
+ pathTransformer.addDirectoryTransform("/a/b/c/", "/d/e/f/");
+ transformRecords = pathTransformer.getTransformRecords();
+ assertEquals(3, transformRecords.size());
+ SourcePathTransformRecord rec3 = new SourcePathTransformRecord("/a/b/c/", null, "/d/e/f/");
+ assertTrue(transformRecords.contains(rec1));
+ assertTrue(transformRecords.contains(rec2));
+ assertTrue(transformRecords.contains(rec3));
+
+ pathTransformer.addDirectoryTransform("/a/b/c/", "/g/h/i/");
+ transformRecords = pathTransformer.getTransformRecords();
+ assertEquals(3, transformRecords.size());
+ SourcePathTransformRecord rec4 = new SourcePathTransformRecord("/a/b/c/", null, "/g/h/i/");
+ assertTrue(transformRecords.contains(rec1));
+ assertTrue(transformRecords.contains(rec2));
+ assertTrue(transformRecords.contains(rec4));
+
+ }
+
+ @Test
+ public void testFileTransformsAndIdentifiers() throws LockException {
+ SourceFile source1 = new SourceFile("/src/file.c");
+ SourceFile source2 =
+ new SourceFile("/src/file.c", SourceFileIdType.TIMESTAMP_64, Longs.toByteArray(0));
+ SourceFile source3 = new SourceFile("/src/file.c", SourceFileIdType.MD5,
+ HexFormat.of().parseHex("0123456789abcdef0123456789abcdef"));
+
+ int txId = program.startTransaction("adding source files");
+ try {
+ sourceManager.addSourceFile(source1);
+ sourceManager.addSourceFile(source2);
+ sourceManager.addSourceFile(source3);
+ }
+ finally {
+ program.endTransaction(txId, true);
+ }
+
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source1, true));
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source2, true));
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source3, true));
+
+ pathTransformer.addFileTransform(source1, "/transformedFile/file.c");
+ assertEquals("/transformedFile/file.c", pathTransformer.getTransformedPath(source1, true));
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source2, true));
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source3, true));
+
+ pathTransformer.addFileTransform(source2, "/transformedFile/file2.c");
+ assertEquals("/transformedFile/file.c", pathTransformer.getTransformedPath(source1, true));
+ assertEquals("/transformedFile/file2.c", pathTransformer.getTransformedPath(source2, true));
+ assertEquals("/src/file.c", pathTransformer.getTransformedPath(source3, true));
+
+ pathTransformer.addDirectoryTransform("/src/", "/SOURCE/");
+ assertEquals("/transformedFile/file.c", pathTransformer.getTransformedPath(source1, true));
+ assertEquals("/transformedFile/file2.c", pathTransformer.getTransformedPath(source2, true));
+ assertEquals("/SOURCE/file.c", pathTransformer.getTransformedPath(source3, true));
+
+ pathTransformer.removeFileTransform(source2);
+ assertEquals("/transformedFile/file.c", pathTransformer.getTransformedPath(source1, true));
+ assertEquals("/SOURCE/file.c", pathTransformer.getTransformedPath(source2, true));
+ assertEquals("/SOURCE/file.c", pathTransformer.getTransformedPath(source3, true));
+ }
+
+ @Test
+ public void testTransformerForNullProgram() {
+ assertNull(UserDataPathTransformer.getPathTransformer(null));
+ }
+}