-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
URI and Path specifier classes for input/output resources. #34
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package org.htsjdk.core.api; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say that a better place will be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.net.URI; | ||
import java.nio.file.Path; | ||
|
||
/** | ||
* Interface representing htsjdk-next input/output resources as URIs. | ||
*/ | ||
public interface PathURI { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this will represent a resource, what's about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, I don't love the names. IOResource might be an improvement - we'd still need a name for the default implementation class (currently called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed to IOResource; still not sure about PathSpecifier. |
||
|
||
/** | ||
* @return true if this URI has a scheme that has an installed NIO file system provider. This does not | ||
* guarantee the URI can be converted into a {@code java.nio.file.Path}, since the URI can be syntactically | ||
* valid, and specify a valid file system provider, but still fail to be semantically meaningful. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to have properly formatted javadocs. Most of the information should be at the begining, and the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
*/ | ||
boolean isNIO(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly to the new disq, I don't like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clients can ignore this and do exactly as you describe (just call toPath, which will throw). |
||
|
||
/** | ||
* Return true if this {code PathURI} can be resolved to an NIO Path. If true, {@code #toPath()} can be | ||
* safely called. | ||
* | ||
* There are cases where a valid URI with a valid scheme backed by an installed NIO File System | ||
* still can't be turned into a {@code java.nio.file.Path}, i.e., the following specifies an invalid | ||
* authority "namenode": | ||
* | ||
* hdfs://namenode/to/file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are going to include specific file-system in the docs, I would recommend to add a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The details of HDFS aren't relevant - its just an example URI. Changed to "file://". |
||
* | ||
* The current implementation returns false for these cases (toPath will fail, getInvalidPathReason | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no current implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
* returns the reason code). | ||
*/ | ||
boolean isPath(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Callers can still fo the path conversion themselves if desired. |
||
|
||
/** | ||
* Get a {@code java.net.URI} object for this {@code PathURI}. Will not be null. | ||
* @return The {@code URI} object for this PathURI. | ||
*/ | ||
URI getURI(); | ||
|
||
/** | ||
* Returns the string from which this {@code PathURI} was originally created. This string may differ from | ||
* the normalized string returned from a Path that has been object resolved from this PathURI. | ||
* @return string from which this URI as originally created. Will not be null, and will always | ||
* include a URI scheme. | ||
*/ | ||
String getURIString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is a sugar-syntax for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default implementation added. |
||
|
||
/** | ||
* Return the raw input string provided to the constructor. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assumes that the constructor will be always a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True; this interface was factored out of the implementation, so the constructor reference isn't relevant. Updated. |
||
*/ | ||
String getRawInputString(); | ||
|
||
/** | ||
* Resolve this PathURI to an NIO Path. Can be safely called only if {@code #isPath()} returns true. | ||
*/ | ||
Path toPath(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As explained before, I think that it is better to remove that many |
||
|
||
/** | ||
* Return a string message describing why this URI cannot be converted to a {@code java.nio.file.Path} | ||
* ({@code #isPath()} returns false). | ||
* @return Message explaining toPath failure reason, since it can fail for various reasons. | ||
*/ | ||
String getToPathFailureReason(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case of removing the |
||
|
||
/** | ||
* Return the scheme for this PathURI. For file URIs (URIs that have no explicit scheme), this will return | ||
* the scheme "file". | ||
* @return the scheme String or this URI, if any. May be null. | ||
*/ | ||
default String getScheme() { | ||
return getURI().getScheme(); | ||
} | ||
|
||
/** | ||
* Get a {@code InputStream} for this URI if a provider is for the URI's scheme is available. | ||
* @return {@code InputStream} for this URI. | ||
*/ | ||
InputStream getInputStream(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
/** | ||
* Get an {@code OutputStream} for this URI if a provider is for the URI's scheme. | ||
* @return {@code OutputStream} for this URI. | ||
*/ | ||
OutputStream getOutputStream(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as before. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.htsjdk.core.utils; | ||
|
||
import java.io.File; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point - done. |
||
import java.io.IOException; | ||
|
||
public class IOUtils { | ||
|
||
/** | ||
* Create a temporary file using a given name prefix and name suffix. | ||
* @param prefix | ||
* @param suffix | ||
* @return temp File that will be deleted on exit | ||
* @throws IOException | ||
*/ | ||
public static File createTempFile(final String prefix, final String suffix) throws IOException { | ||
final File tempFile = File.createTempFile("testOutputStream", ".txt"); | ||
tempFile.deleteOnExit(); | ||
return tempFile; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,238 @@ | ||||||||||
package org.htsjdk.core.utils; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||||||
|
||||||||||
import org.htsjdk.core.api.PathURI; | ||||||||||
import org.htsjdk.core.exception.HtsjdkException; | ||||||||||
|
||||||||||
import java.io.IOException; | ||||||||||
import java.io.InputStream; | ||||||||||
import java.io.OutputStream; | ||||||||||
import java.io.Serializable; | ||||||||||
import java.net.URI; | ||||||||||
import java.net.URISyntaxException; | ||||||||||
import java.nio.file.*; | ||||||||||
import java.nio.file.spi.FileSystemProvider; | ||||||||||
|
||||||||||
/** | ||||||||||
* Default implementation for PathURI. | ||||||||||
* | ||||||||||
* This class takes a raw string that is to be interpreted as a path specifier, and converts it internally to a URI or | ||||||||||
* Path object. If no scheme is provided as part of the raw string used in the constructor(s), the URI is assumed | ||||||||||
* to represent a file on the local file system, and will be backed by a URI with a "file:/" scheme and a path | ||||||||||
* part that is automatically encoded/escaped to ensure it is a valid URI. If the raw string contains a scheme, | ||||||||||
* it will be backed by a URI formed from the raw string as presented, with no further encoding/escaping. | ||||||||||
* | ||||||||||
* For example, a URI that contains a scheme, and has an embedded "#" in the path will be treated as having a | ||||||||||
* fragment delimiter. If the URI contains an scheme, the "#" will be escaped. | ||||||||||
* | ||||||||||
* There are 3 succeeding levels of validation: | ||||||||||
* | ||||||||||
* 1) PathSpecifier constructor - requires a syntactically valid URI, possibly containing a scheme (if no scheme | ||||||||||
* is present the path part will be escaped/encoded) | ||||||||||
* 2) isNio - returns true if the URI is syntactically valid, and there is an installed NIO provider that matches | ||||||||||
* the URI scheme | ||||||||||
* 3) isPath - syntactically valid URI that can be resolved to a java.io.Path by the provider | ||||||||||
* | ||||||||||
* <scheme>:<scheme-specific-part> | ||||||||||
* <scheme>://<authority><path>?<query> | ||||||||||
* absoluteURI = scheme ":" ( hier_part | opaque_part ) | ||||||||||
* hier_part = ( net_path | abs_path ) [ "?" query ] | ||||||||||
* net_path = "//" authority [ abs_path ] | ||||||||||
* abs_path = "/" path_segments | ||||||||||
* | ||||||||||
* A URI is absolute if, and only if, it has a scheme component. | ||||||||||
* A URI is opaque if, and only if, it is absolute (has a scheme) and its | ||||||||||
* scheme-specific part does not begin with a slash character ('/') | ||||||||||
* | ||||||||||
* A relative reference that does not begin with a scheme name or a | ||||||||||
* slash character is termed a relative-path reference. | ||||||||||
* | ||||||||||
* A relative reference beginning with a single slash character is | ||||||||||
* termed an absolute-path reference, as defined by <abs_path> in | ||||||||||
* Section 3. | ||||||||||
* | ||||||||||
* URI that do not make use of the slash "/" character for separating | ||||||||||
* hierarchical components are considered opaque by the generic URI | ||||||||||
* parser. | ||||||||||
*/ | ||||||||||
public class PathSpecifier implements PathURI, Serializable { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we should make our defaults implementations final, unless required. The same as before, we should not allow to extend classes unless we explicitly see a widely spread use-case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, should There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question on |
||||||||||
private static final long serialVersionUID = 1L; | ||||||||||
|
||||||||||
private final String rawInputString; // raw input string provided by th user; may or may not have a scheme | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
private final URI uri; // working URI; always has a scheme (assume "file" if not provided) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be transient too, as the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The class maintains the invariant that there is always a backing URI, whereas the cached path is just an optimization. So the cache is truly transient. |
||||||||||
private String pathFailureReason; // cache the reason for "toPath" conversion failure | ||||||||||
|
||||||||||
private transient Path cachedPath; // cache the Path associated with this URI if its "Path-able" | ||||||||||
|
||||||||||
public PathSpecifier(final String rawInputString) { | ||||||||||
ParamUtils.nonNull(rawInputString); | ||||||||||
this.rawInputString = rawInputString; | ||||||||||
|
||||||||||
URI tempURI; | ||||||||||
try { | ||||||||||
// If the input URI already has a scheme (including a "file" scheme), we assume its already properly | ||||||||||
// escaped. If no scheme component is present, then assume its a raw path on the local file system, so | ||||||||||
// try to get a Path first, and then recreate the URI by retrieving it from the resulting Path. This | ||||||||||
// ensures that input strings that contain embedded characters that would otherwise be interpreted as | ||||||||||
// URI syntax (such as embedded fragment specifiers ("#") that are valid in file names) are properly | ||||||||||
// escaped. | ||||||||||
tempURI = new URI(rawInputString); | ||||||||||
if (!tempURI.isAbsolute()) { | ||||||||||
// NOTE: This case (no scheme) is the only one where we resolve the URI to a Path at construction time. | ||||||||||
try { | ||||||||||
setCachedPath(Paths.get(rawInputString)); | ||||||||||
tempURI = getCachedPath().toUri(); | ||||||||||
} catch (InvalidPathException e) { | ||||||||||
throw new IllegalArgumentException(e.getMessage(), e); | ||||||||||
} | ||||||||||
} | ||||||||||
uri = tempURI; | ||||||||||
if (!uri.isAbsolute()) { | ||||||||||
// assert the invariant that every URI we create has a scheme, even if the raw input string does not | ||||||||||
throw new HtsjdkException("URI has no scheme"); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have either more specific exceptions (never throw a raw one) or in this case it should be an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed this one to If |
||||||||||
} | ||||||||||
} catch (URISyntaxException e) { | ||||||||||
final String errorMessage = String.format("%s must be a valid URI. '%s'/'%s'", rawInputString, e.getMessage(), e.getReason()); | ||||||||||
throw new IllegalArgumentException(errorMessage); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public boolean isNIO() { | ||||||||||
// try to find a provider; assume that our URI always has a scheme | ||||||||||
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) { | ||||||||||
if (provider.getScheme().equalsIgnoreCase(uri.getScheme())) { | ||||||||||
return true; | ||||||||||
} | ||||||||||
} | ||||||||||
return false; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public boolean isPath() { | ||||||||||
try { | ||||||||||
return getCachedPath() != null || toPath() != null; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only |
||||||||||
} catch (ProviderNotFoundException | | ||||||||||
FileSystemNotFoundException | | ||||||||||
IllegalArgumentException | | ||||||||||
HtsjdkException | | ||||||||||
AssertionError e) { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not nice to have a catch clause just because of a test. The test should handle that instead...or this catch any error when trying to get the path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a test assertion, its actually thrown by at least one of the provider implementations (jimfs, and also some java code IIRC). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a test assertion, its actually thrown by at least one of the provider implementations (jimfs, and also some java code IIRC). It is unfortunate though. |
||||||||||
// jimfs throws an AssertionError that wraps a URISyntaxException when trying to create path where | ||||||||||
// the scheme-specific part is missing or incorrect | ||||||||||
pathFailureReason = e.getMessage(); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the message is empty or uninformative, the failure reason is not really useful; sometimes the useful message comes from the stacktrace. I would say to at least test if the message is empty, and if it is at least specify the class for the exception. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think that it will be much better to store the exception to let the user handle it by themselves as they wish. |
||||||||||
return false; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public URI getURI() { | ||||||||||
return uri; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public String getURIString() { | ||||||||||
return getURI().toString(); | ||||||||||
} | ||||||||||
|
||||||||||
/** | ||||||||||
* Return the raw input string as provided to the constructor. | ||||||||||
*/ | ||||||||||
@Override | ||||||||||
public String getRawInputString() { return rawInputString; } | ||||||||||
|
||||||||||
/** | ||||||||||
* Resolve the URI to a {@link Path} object. | ||||||||||
* | ||||||||||
* @return the resulting {@code Path} | ||||||||||
* @throws HtsjdkException if an I/O error occurs when creating the file system | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not really throw |
||||||||||
*/ | ||||||||||
@Override | ||||||||||
public Path toPath() { | ||||||||||
if (getCachedPath() != null) { | ||||||||||
return getCachedPath(); | ||||||||||
} else { | ||||||||||
final Path tmpPath = Paths.get(getURI()); | ||||||||||
setCachedPath(tmpPath); | ||||||||||
return tmpPath; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public String getToPathFailureReason() { | ||||||||||
if (pathFailureReason == null) { | ||||||||||
try { | ||||||||||
toPath(); | ||||||||||
return String.format("'%s' appears to be a valid Path", rawInputString); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should specify the behavior at the interface-level for this case. I think that it is much better if it returns an empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case of no-failure, this could instead return an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good idea. Changed to Optional. |
||||||||||
} catch (ProviderNotFoundException e) { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be summarize better as:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only in case the exception is not returned instead, which looks much better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I generally try to avoid catching |
||||||||||
return String.format("ProviderNotFoundException: %s", e.getMessage()); | ||||||||||
} catch (FileSystemNotFoundException e) { | ||||||||||
return String.format("FileSystemNotFoundException: %s", e.getMessage()); | ||||||||||
} catch (IllegalArgumentException e) { | ||||||||||
return String.format("IllegalArgumentException: %s", e.getMessage()); | ||||||||||
} catch (HtsjdkException e) { | ||||||||||
return String.format("UserException: %s", e.getMessage()); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||||||
} | ||||||||||
} | ||||||||||
return pathFailureReason; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public InputStream getInputStream() { | ||||||||||
if (!isPath()) { | ||||||||||
throw new HtsjdkException(getToPathFailureReason()); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No raw |
||||||||||
} | ||||||||||
|
||||||||||
final Path resourcePath = toPath(); | ||||||||||
try { | ||||||||||
return Files.newInputStream(resourcePath); | ||||||||||
} catch (IOException e) { | ||||||||||
throw new HtsjdkException( | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No raw |
||||||||||
String.format("Could not create open input stream for %s (as URI %s)", getRawInputString(), getURIString()), e); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public OutputStream getOutputStream() { | ||||||||||
if (!isPath()) { | ||||||||||
throw new HtsjdkException(getToPathFailureReason()); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No raw There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - perhaps we need a separate discussion about how we want to partition the exception namespace. #36. |
||||||||||
} | ||||||||||
|
||||||||||
final Path resourcePath = toPath(); | ||||||||||
try { | ||||||||||
return Files.newOutputStream(resourcePath); | ||||||||||
} catch (IOException e) { | ||||||||||
throw new HtsjdkException(String.format("Could not open output stream for %s (as URI %s)", getRawInputString(), getURIString()), e); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No raw |
||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// get the cached path associated with this URI if its already been created | ||||||||||
protected Path getCachedPath() { return cachedPath; } | ||||||||||
|
||||||||||
protected void setCachedPath(Path path) { | ||||||||||
this.cachedPath = path; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public String toString() { | ||||||||||
return rawInputString; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public boolean equals(Object o) { | ||||||||||
if (this == o) return true; | ||||||||||
if (!(o instanceof PathSpecifier)) return false; | ||||||||||
|
||||||||||
PathSpecifier that = (PathSpecifier) o; | ||||||||||
|
||||||||||
if (!getURIString().equals(that.getURIString())) return false; | ||||||||||
if (!getURI().equals(that.getURI())) return false; | ||||||||||
return true; | ||||||||||
} | ||||||||||
|
||||||||||
@Override | ||||||||||
public int hashCode() { | ||||||||||
int result = getURIString().hashCode(); | ||||||||||
result = 31 * result + getURI().hashCode(); | ||||||||||
return result; | ||||||||||
} | ||||||||||
|
||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention: is this needed in this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch - its left over from previous changes and can be removed.