Merge remote-tracking branch 'origin/GP-3678_ghidra1_DomainFileURLs--SQUASHED'

This commit is contained in:
Ryan Kurtz 2023-08-03 12:31:04 -04:00
commit 2b8e19a27a
13 changed files with 178 additions and 76 deletions

View file

@ -540,7 +540,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
this.program = p; this.program = p;
this.domainFile = domainFile; this.domainFile = domainFile;
if (domainFile instanceof LinkedDomainFile linkedDomainFile) { if (domainFile instanceof LinkedDomainFile linkedDomainFile) {
this.ghidraURL = linkedDomainFile.getSharedProjectURL(); this.ghidraURL = linkedDomainFile.getSharedProjectURL(null);
} }
else { else {
this.ghidraURL = null; this.ghidraURL = null;

View file

@ -27,6 +27,7 @@ import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile; import db.buffers.ManagedBufferFile;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
@ -86,6 +87,15 @@ public class GhidraFileTest extends AbstractGhidraHeadedIntegrationTest {
deleteAll(sharedProjectDir); deleteAll(sharedProjectDir);
} }
@Test
public void testLocalURL() throws IOException {
createDB(privateFS, "/a", "file1");
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", "xyz"),
pfm.getFile("/a/file1").getLocalProjectURL("xyz"));
assertEquals(GhidraURL.makeURL(pfm.getProjectLocator(), "/a/file1", null),
pfm.getFile("/a/file1").getLocalProjectURL(null));
}
@Test @Test
public void testFileID() throws IOException { public void testFileID() throws IOException {
createDB(privateFS, "/a", "file1"); createDB(privateFS, "/a", "file1");

View file

@ -23,6 +23,8 @@ import java.util.*;
import javax.swing.Icon; import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.*; import ghidra.framework.client.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
@ -116,16 +118,13 @@ public class DomainFileProxy implements DomainFile {
return parentPath + DomainFolder.SEPARATOR + getName(); return parentPath + DomainFolder.SEPARATOR + getName();
} }
private URL getSharedFileURL(URL sharedProjectURL) { private URL getSharedFileURL(URL sharedProjectURL, String ref) {
try { try {
// Direct URL construction done so that ghidra protocol String spec = getPathname().substring(1); // remove leading '/'
// extension may be supported if (!StringUtils.isEmpty(ref)) {
String urlStr = sharedProjectURL.toExternalForm(); spec += "#" + ref;
if (urlStr.endsWith("/")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
} }
urlStr += getPathname(); return new URL(sharedProjectURL, spec);
return new URL(urlStr);
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
// ignore // ignore
@ -133,7 +132,7 @@ public class DomainFileProxy implements DomainFile {
return null; return null;
} }
private URL getSharedFileURL(Properties properties) { private URL getSharedFileURL(Properties properties, String ref) {
if (properties == null) { if (properties == null) {
return null; return null;
} }
@ -166,9 +165,8 @@ public class DomainFileProxy implements DomainFile {
return null; return null;
} }
ServerInfo serverInfo = repository.getServerInfo(); ServerInfo serverInfo = repository.getServerInfo();
return GhidraURL.makeURL(serverInfo.getServerName(), return GhidraURL.makeURL(serverInfo.getServerName(), serverInfo.getPortNumber(),
serverInfo.getPortNumber(), repository.getName(), repository.getName(), item.getPathName(), ref);
item.getPathName());
} }
catch (IOException e) { catch (IOException e) {
// ignore // ignore
@ -182,15 +180,27 @@ public class DomainFileProxy implements DomainFile {
} }
@Override @Override
public URL getSharedProjectURL() { public URL getSharedProjectURL(String ref) {
if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) { if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) {
URL projectURL = projectLocation.getURL(); URL projectURL = projectLocation.getURL();
if (GhidraURL.isServerRepositoryURL(projectURL)) { if (GhidraURL.isServerRepositoryURL(projectURL)) {
return getSharedFileURL(projectURL); return getSharedFileURL(projectURL, ref);
} }
Properties properties = Properties properties =
ProjectFileManager.readProjectProperties(projectLocation.getProjectDir()); ProjectFileManager.readProjectProperties(projectLocation.getProjectDir());
return getSharedFileURL(properties); return getSharedFileURL(properties, ref);
}
return null;
}
@Override
public URL getLocalProjectURL(String ref) {
if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) {
URL projectURL = projectLocation.getURL();
if (GhidraURL.isServerRepositoryURL(projectURL)) {
return null;
}
return GhidraURL.makeURL(projectLocation, getPathname(), ref);
} }
return null; return null;
} }

View file

@ -124,9 +124,20 @@ public class GhidraFile implements DomainFile {
} }
@Override @Override
public URL getSharedProjectURL() { public URL getSharedProjectURL(String ref) {
try { try {
return getFileData().getSharedProjectURL(); return getFileData().getSharedProjectURL(ref);
}
catch (IOException e) {
// ignore
}
return null;
}
@Override
public URL getLocalProjectURL(String ref) {
try {
return getFileData().getLocalProjectURL(ref);
} }
catch (IOException e) { catch (IOException e) {
// ignore // ignore
@ -456,10 +467,9 @@ public class GhidraFile implements DomainFile {
} }
@Override @Override
public boolean checkout(boolean exclusive, TaskMonitor monitor) throws IOException, public boolean checkout(boolean exclusive, TaskMonitor monitor)
CancelledException { throws IOException, CancelledException {
return getFileData().checkout(exclusive, return getFileData().checkout(exclusive, monitor != null ? monitor : TaskMonitor.DUMMY);
monitor != null ? monitor : TaskMonitor.DUMMY);
} }
@Override @Override
@ -470,10 +480,9 @@ public class GhidraFile implements DomainFile {
} }
@Override @Override
public void merge(boolean okToUpgrade, TaskMonitor monitor) throws IOException, public void merge(boolean okToUpgrade, TaskMonitor monitor)
VersionException, CancelledException { throws IOException, VersionException, CancelledException {
getFileData().merge(okToUpgrade, getFileData().merge(okToUpgrade, monitor != null ? monitor : TaskMonitor.DUMMY);
monitor != null ? monitor : TaskMonitor.DUMMY);
} }
@Override @Override
@ -521,8 +530,8 @@ public class GhidraFile implements DomainFile {
} }
@Override @Override
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor) throws IOException, public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor)
CancelledException { throws IOException, CancelledException {
if (!GhidraFolder.class.isAssignableFrom(newParent.getClass())) { if (!GhidraFolder.class.isAssignableFrom(newParent.getClass())) {
throw new UnsupportedOperationException("newParent does not support copyTo"); throw new UnsupportedOperationException("newParent does not support copyTo");
} }
@ -559,8 +568,7 @@ public class GhidraFile implements DomainFile {
* @throws CancelledException if task is cancelled * @throws CancelledException if task is cancelled
*/ */
void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException { void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException {
getFileData().convertToPrivateFile( getFileData().convertToPrivateFile(monitor != null ? monitor : TaskMonitor.DUMMY);
monitor != null ? monitor : TaskMonitor.DUMMY);
} }
@Override @Override

View file

@ -24,6 +24,8 @@ import java.util.Map;
import javax.swing.Icon; import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import db.DBHandle; import db.DBHandle;
import db.Field; import db.Field;
import db.buffers.*; import db.buffers.*;
@ -209,17 +211,21 @@ public class GhidraFileData {
/** /**
* Get a remote Ghidra URL for this domain file if available within a remote repository. * Get a remote Ghidra URL for this domain file if available within a remote repository.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return remote Ghidra URL for this file or null * @return remote Ghidra URL for this file or null
*/ */
URL getSharedProjectURL() { URL getSharedProjectURL(String ref) {
synchronized (fileSystem) { synchronized (fileSystem) {
RepositoryAdapter repository = parent.getProjectFileManager().getRepository(); RepositoryAdapter repository = parent.getProjectFileManager().getRepository();
if (versionedFolderItem != null && repository != null) { if (versionedFolderItem != null && repository != null) {
URL folderURL = parent.getDomainFolder().getSharedProjectURL(); URL folderURL = parent.getDomainFolder().getSharedProjectURL();
try { try {
// Direct URL construction done so that ghidra protocol String spec = name;
// extension may be supported if (!StringUtils.isEmpty(ref)) {
return new URL(folderURL.toExternalForm() + name); spec += "#" + ref;
}
return new URL(folderURL, spec);
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
// ignore // ignore
@ -229,6 +235,23 @@ public class GhidraFileData {
} }
} }
/**
* Get a local Ghidra URL for this domain file if available within a non-transient local
* project. A null value is returned for a transient project.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return local Ghidra URL for this file or null if transient or not applicable
*/
URL getLocalProjectURL(String ref) {
synchronized (fileSystem) {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), ref);
}
return null;
}
}
/** /**
* Reassign a new file-ID to resolve file-ID conflict. * Reassign a new file-ID to resolve file-ID conflict.
* Conflicts can occur as a result of a cancelled check-out. * Conflicts can occur as a result of a cancelled check-out.
@ -453,8 +476,7 @@ public class GhidraFileData {
return true; return true;
} }
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname()); DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (!(dobj instanceof DomainObjectAdapterDB) || if (!(dobj instanceof DomainObjectAdapterDB) || !dobj.isChanged()) {
!dobj.isChanged()) {
return true; return true;
} }
LockingTaskMonitor monitor = null; LockingTaskMonitor monitor = null;
@ -881,8 +903,8 @@ public class GhidraFileData {
: CheckoutType.NORMAL; : CheckoutType.NORMAL;
} }
ItemCheckoutStatus checkout = ItemCheckoutStatus checkout =
versionedFolderItem.checkout(checkoutType, user, ItemCheckoutStatus.getProjectPath( versionedFolderItem.checkout(checkoutType, user, ItemCheckoutStatus
projectLocator.toString(), projectLocator.isTransient())); .getProjectPath(projectLocator.toString(), projectLocator.isTransient()));
if (checkout == null) { if (checkout == null) {
return false; return false;
} }
@ -1690,10 +1712,8 @@ public class GhidraFileData {
BufferFile bufferFile = ((DatabaseItem) item).open(); BufferFile bufferFile = ((DatabaseItem) item).open();
try { try {
newParentData.getLocalFileSystem() newParentData.getLocalFileSystem()
.createDatabase(pathname, targetName, .createDatabase(pathname, targetName, FileIDFactory.createFileID(),
FileIDFactory.createFileID(), bufferFile, null, contentType, bufferFile, null, contentType, true, monitor, user);
true,
monitor, user);
} }
finally { finally {
bufferFile.dispose(); bufferFile.dispose();
@ -1703,8 +1723,8 @@ public class GhidraFileData {
InputStream istream = ((DataFileItem) item).getInputStream(); InputStream istream = ((DataFileItem) item).getInputStream();
try { try {
newParentData.getLocalFileSystem() newParentData.getLocalFileSystem()
.createDataFile(pathname, targetName, .createDataFile(pathname, targetName, istream, null, contentType,
istream, null, contentType, monitor); monitor);
} }
finally { finally {
istream.close(); istream.close();
@ -1745,10 +1765,8 @@ public class GhidraFileData {
} }
try { try {
destFolderData.getLocalFileSystem() destFolderData.getLocalFileSystem()
.createDatabase(pathname, targetName, .createDatabase(pathname, targetName, FileIDFactory.createFileID(),
FileIDFactory.createFileID(), bufferFile, null, contentType, true, bufferFile, null, contentType, true, monitor, user);
monitor,
user);
} }
finally { finally {
bufferFile.dispose(); bufferFile.dispose();

View file

@ -199,8 +199,7 @@ public class GhidraFolder implements DomainFolder {
repository.getName()); repository.getName());
} }
try { try {
// Direct URL construction done so that ghidra protocol // Direct URL construction done so that ghidra protocol extension may be supported
// extension may be supported
String urlStr = projectURL.toExternalForm(); String urlStr = projectURL.toExternalForm();
if (urlStr.endsWith(FileSystem.SEPARATOR)) { if (urlStr.endsWith(FileSystem.SEPARATOR)) {
urlStr = urlStr.substring(0, urlStr.length() - 1); urlStr = urlStr.substring(0, urlStr.length() - 1);
@ -217,6 +216,15 @@ public class GhidraFolder implements DomainFolder {
} }
} }
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
return null;
}
@Override @Override
public boolean isInWritableProject() { public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly(); return !getProjectData().getLocalFileSystem().isReadOnly();

View file

@ -43,7 +43,7 @@ import ghidra.util.task.TaskMonitor;
* @param <T> {@link URLLinkObject} implementation class * @param <T> {@link URLLinkObject} implementation class
*/ */
public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBContentHandler<T> { public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBContentHandler<T> {
public static final String URL_METADATA_KEY = "link.url"; public static final String URL_METADATA_KEY = "link.url";
// 16x16 link icon where link is placed in lower-left corner // 16x16 link icon where link is placed in lower-left corner
@ -93,11 +93,10 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private T getObject(FolderItem item, int version, Object consumer, TaskMonitor monitor, private T getObject(FolderItem item, int version, Object consumer, TaskMonitor monitor,
boolean immutable) boolean immutable) throws IOException, VersionException, CancelledException {
throws IOException, VersionException, CancelledException {
URL url = getURL(item); URL url = getURL(item);
Class<?> domainObjectClass = getDomainObjectClass(); Class<?> domainObjectClass = getDomainObjectClass();
if (domainObjectClass == null) { if (domainObjectClass == null) {
throw new UnsupportedOperationException(""); throw new UnsupportedOperationException("");
@ -105,6 +104,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
GhidraURLWrappedContent wrappedContent = null; GhidraURLWrappedContent wrappedContent = null;
Object content = null; Object content = null;
final Object transientConsumer = new Object();
try { try {
GhidraURLConnection c = (GhidraURLConnection) url.openConnection(); GhidraURLConnection c = (GhidraURLConnection) url.openConnection();
Object obj = c.getContent(); // read-only access Object obj = c.getContent(); // read-only access
@ -115,22 +115,21 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
throw new IOException("Unsupported linked content"); throw new IOException("Unsupported linked content");
} }
wrappedContent = (GhidraURLWrappedContent) obj; wrappedContent = (GhidraURLWrappedContent) obj;
content = wrappedContent.getContent(consumer); content = wrappedContent.getContent(transientConsumer);
if (!(content instanceof DomainFile)) { if (!(content instanceof DomainFile)) {
throw new IOException("Unsupported linked content: " + content.getClass()); throw new IOException("Unsupported linked content: " + content.getClass());
} }
DomainFile linkedFile = (DomainFile) content; DomainFile linkedFile = (DomainFile) content;
if (!getDomainObjectClass().isAssignableFrom(linkedFile.getDomainObjectClass())) { if (!getDomainObjectClass().isAssignableFrom(linkedFile.getDomainObjectClass())) {
throw new BadLinkException( throw new BadLinkException("Expected " + getDomainObjectClass() +
"Expected " + getDomainObjectClass() + " but linked to " + " but linked to " + linkedFile.getDomainObjectClass());
linkedFile.getDomainObjectClass());
} }
return immutable ? (T) linkedFile.getImmutableDomainObject(consumer, version, monitor) return immutable ? (T) linkedFile.getImmutableDomainObject(consumer, version, monitor)
: (T) linkedFile.getReadOnlyDomainObject(consumer, version, monitor); : (T) linkedFile.getReadOnlyDomainObject(consumer, version, monitor);
} }
finally { finally {
if (content != null) { if (content != null) {
wrappedContent.release(content, consumer); wrappedContent.release(content, transientConsumer);
} }
} }
} }
@ -151,8 +150,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> extends DBCon
@Override @Override
public final DomainObjectMergeManager getMergeManager(DomainObject resultsObj, public final DomainObjectMergeManager getMergeManager(DomainObject resultsObj,
DomainObject sourceObj, DomainObject sourceObj, DomainObject originalObj, DomainObject latestObj) {
DomainObject originalObj, DomainObject latestObj) {
return null; return null;
} }

View file

@ -25,6 +25,8 @@ import java.util.Map;
import javax.help.UnsupportedOperationException; import javax.help.UnsupportedOperationException;
import javax.swing.Icon; import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.store.*; import ghidra.framework.store.*;
@ -100,13 +102,15 @@ class LinkedGhidraFile implements LinkedDomainFile {
} }
@Override @Override
public URL getSharedProjectURL() { public URL getSharedProjectURL(String ref) {
URL folderURL = parent.getSharedProjectURL(); URL folderURL = parent.getSharedProjectURL();
if (GhidraURL.isServerRepositoryURL(folderURL)) { if (GhidraURL.isServerRepositoryURL(folderURL)) {
// Direct URL construction done so that ghidra protocol
// extension may be supported
try { try {
return new URL(folderURL.toExternalForm() + fileName); String spec = fileName;
if (!StringUtils.isEmpty(ref)) {
spec += "#" + ref;
}
return new URL(folderURL, spec);
} }
catch (MalformedURLException e) { catch (MalformedURLException e) {
// ignore // ignore
@ -115,6 +119,15 @@ class LinkedGhidraFile implements LinkedDomainFile {
return null; return null;
} }
@Override
public URL getLocalProjectURL(String ref) {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), ref);
}
return null;
}
@Override @Override
public ProjectLocator getProjectLocator() { public ProjectLocator getProjectLocator() {
return parent.getProjectLocator(); return parent.getProjectLocator();

View file

@ -107,6 +107,15 @@ class LinkedGhidraSubFolder implements LinkedDomainFolder {
return null; return null;
} }
@Override
public URL getLocalProjectURL() {
ProjectLocator projectLocator = parent.getProjectLocator();
if (!projectLocator.isTransient()) {
return GhidraURL.makeURL(projectLocator, getPathname(), null);
}
return null;
}
@Override @Override
public ProjectLocator getProjectLocator() { public ProjectLocator getProjectLocator() {
return parent.getProjectLocator(); return parent.getProjectLocator();

View file

@ -89,12 +89,23 @@ public interface DomainFile extends Comparable<DomainFile> {
public String getPathname(); public String getPathname();
/** /**
* Get a remote Ghidra URL for this domain file if available within the associated shared * Get a remote Ghidra URL for this domain file if available within an associated shared
* project repository. A null value will be returned if shared file does not exist and * project repository. A null value will be returned if shared file does not exist and
* may be returned if shared repository is not connected or a connection error occurs. * may also be returned if shared repository is not connected or a connection error occurs.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return remote Ghidra URL for this file or null * @return remote Ghidra URL for this file or null
*/ */
public URL getSharedProjectURL(); public URL getSharedProjectURL(String ref);
/**
* Get a local Ghidra URL for this domain file if available within the associated non-transient
* local project. A null value will be returned if project is transient.
* @param ref reference within a file, may be null. NOTE: such reference interpretation
* is specific to a domain object and tooling with limited support.
* @return local Ghidra URL for this file or null if transient or not applicable
*/
public URL getLocalProjectURL(String ref);
/** /**
* Returns the local storage location for the project that this DomainFile belongs to. * Returns the local storage location for the project that this DomainFile belongs to.

View file

@ -35,8 +35,7 @@ import ghidra.util.task.TaskMonitor;
*/ */
public interface DomainFolder extends Comparable<DomainFolder> { public interface DomainFolder extends Comparable<DomainFolder> {
public static final Icon OPEN_FOLDER_ICON = public static final Icon OPEN_FOLDER_ICON = new GIcon("icon.datatree.node.domain.folder.open");
new GIcon("icon.datatree.node.domain.folder.open");
public static final Icon CLOSED_FOLDER_ICON = public static final Icon CLOSED_FOLDER_ICON =
new GIcon("icon.datatree.node.domain.folder.closed"); new GIcon("icon.datatree.node.domain.folder.closed");
@ -90,13 +89,21 @@ public interface DomainFolder extends Comparable<DomainFolder> {
public String getPathname(); public String getPathname();
/** /**
* Get a remote Ghidra URL for this domain folder within the associated shared * Get a remote Ghidra URL for this domain folder if available within an associated shared
* project repository. URL path will end with "/". A null value will be returned if not * project repository. URL path will end with "/". A null value will be returned if shared
* associated with a shared project. * folder does not exist and may also be returned if shared repository is not connected or a
* connection error occurs.
* @return remote Ghidra URL for this folder or null * @return remote Ghidra URL for this folder or null
*/ */
public URL getSharedProjectURL(); public URL getSharedProjectURL();
/**
* Get a local Ghidra URL for this domain file if available within the associated non-transient
* local project. A null value will be returned if project is transient.
* @return local Ghidra URL for this folder or null if transient or not applicable
*/
public URL getLocalProjectURL();
/** /**
* Returns true if this file is in a writable project. * Returns true if this file is in a writable project.
* @return true if writable * @return true if writable
@ -219,8 +226,8 @@ public interface DomainFolder extends Comparable<DomainFolder> {
* @throws IOException thrown if an IO or access error occurs. * @throws IOException thrown if an IO or access error occurs.
* @throws CancelledException if task monitor cancelled operation. * @throws CancelledException if task monitor cancelled operation.
*/ */
public DomainFolder copyTo(DomainFolder newParent, TaskMonitor monitor) throws IOException, public DomainFolder copyTo(DomainFolder newParent, TaskMonitor monitor)
CancelledException; throws IOException, CancelledException;
/** /**
* Copy this folder into the newParent folder as a link file. Restrictions: * Copy this folder into the newParent folder as a link file. Restrictions:

View file

@ -102,7 +102,12 @@ public class TestDummyDomainFile implements DomainFile {
} }
@Override @Override
public URL getSharedProjectURL() { public URL getSharedProjectURL(String ref) {
throw new UnsupportedOperationException();
}
@Override
public URL getLocalProjectURL(String ref) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -88,6 +88,11 @@ public class TestDummyDomainFolder implements DomainFolder {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public URL getLocalProjectURL() {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean isInWritableProject() { public boolean isInWritableProject() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();