summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--benchmarks/src/benchmarks/regression/EqualsHashCodeBenchmark.java15
-rw-r--r--harmony-tests/src/test/java/org/apache/harmony/tests/java/io/PipedInputStreamTest.java102
-rw-r--r--harmony-tests/src/test/java/org/apache/harmony/tests/java/net/CookieStoreTest.java4
-rw-r--r--harmony-tests/src/test/java/org/apache/harmony/tests/java/util/TreeSetTest.java8
-rw-r--r--luni/src/main/java/java/io/PipedInputStream.java5
-rw-r--r--luni/src/main/java/java/io/PipedOutputStream.java2
-rw-r--r--luni/src/main/java/java/net/URI.java46
-rw-r--r--luni/src/test/java/libcore/java/net/URITest.java20
8 files changed, 114 insertions, 88 deletions
diff --git a/benchmarks/src/benchmarks/regression/EqualsHashCodeBenchmark.java b/benchmarks/src/benchmarks/regression/EqualsHashCodeBenchmark.java
index a15a41a..72bb216 100644
--- a/benchmarks/src/benchmarks/regression/EqualsHashCodeBenchmark.java
+++ b/benchmarks/src/benchmarks/regression/EqualsHashCodeBenchmark.java
@@ -36,6 +36,8 @@ public final class EqualsHashCodeBenchmark extends SimpleBenchmark {
abstract Object newInstance(String text) throws Exception;
}
+ private static final String QUERY = "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
+
@Param Type type;
Object a1;
@@ -43,11 +45,18 @@ public final class EqualsHashCodeBenchmark extends SimpleBenchmark {
Object b1;
Object b2;
+ Object c1;
+ Object c2;
+
@Override protected void setUp() throws Exception {
a1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
a2 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
b1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
b2 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+
+ c1 = type.newInstance("http://developer.android.com/query?q=" + QUERY);
+ // Replace the very last char.
+ c2 = type.newInstance("http://developer.android.com/query?q=" + QUERY.substring(0, QUERY.length() - 3) + "%AF");
}
public void timeEquals(int reps) {
@@ -64,4 +73,10 @@ public final class EqualsHashCodeBenchmark extends SimpleBenchmark {
b1.hashCode();
}
}
+
+ public void timeEqualsWithHeavilyEscapedComponent(int reps) {
+ for (int i = 0; i < reps; ++i) {
+ c1.equals(c2);
+ }
+ }
}
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/PipedInputStreamTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/PipedInputStreamTest.java
index 9cf8054..6122dbb 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/PipedInputStreamTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/io/PipedInputStreamTest.java
@@ -19,6 +19,7 @@ package org.apache.harmony.tests.java.io;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
+import java.util.concurrent.CountDownLatch;
public class PipedInputStreamTest extends junit.framework.TestCase {
@@ -248,106 +249,83 @@ public class PipedInputStreamTest extends junit.framework.TestCase {
/**
* java.io.PipedInputStream#receive(int)
*/
- public void test_receive() throws IOException {
+ public void test_write_failsAfterReaderDead() throws Exception {
pis = new PipedInputStream();
pos = new PipedOutputStream();
// test if writer recognizes dead reader
pis.connect(pos);
- class WriteRunnable implements Runnable {
- boolean pass = false;
+ class WriteRunnable implements Runnable {
- volatile boolean readerAlive = true;
+ final CountDownLatch readerAlive = new CountDownLatch(1);
public void run() {
try {
pos.write(1);
- while (readerAlive) {
- ;
+
+ try {
+ readerAlive.await();
+ } catch (InterruptedException ie) {
+ fail();
+ return;
}
+
try {
// should throw exception since reader thread
// is now dead
pos.write(1);
- } catch (IOException e) {
- pass = true;
+ fail();
+ } catch (IOException expected) {
}
} catch (IOException e) {
}
}
}
- WriteRunnable writeRunnable = new WriteRunnable();
- Thread writeThread = new Thread(writeRunnable);
- class ReadRunnable implements Runnable {
-
- boolean pass;
+ class ReadRunnable implements Runnable {
public void run() {
try {
pis.read();
- pass = true;
} catch (IOException e) {
+ fail();
}
}
}
- ;
+
+ WriteRunnable writeRunnable = new WriteRunnable();
+ Thread writeThread = new Thread(writeRunnable);
+
ReadRunnable readRunnable = new ReadRunnable();
Thread readThread = new Thread(readRunnable);
writeThread.start();
readThread.start();
- while (readThread.isAlive()) {
- ;
- }
- writeRunnable.readerAlive = false;
- assertTrue("reader thread failed to read", readRunnable.pass);
- while (writeThread.isAlive()) {
- ;
- }
- assertTrue("writer thread failed to recognize dead reader",
- writeRunnable.pass);
+ readThread.join();
- // attempt to write to stream after writer closed
- pis = new PipedInputStream();
- pos = new PipedOutputStream();
+ writeRunnable.readerAlive.countDown();
+ writeThread.join();
+ }
- pis.connect(pos);
- class MyRunnable implements Runnable {
+ static final class PipedInputStreamWithPublicReceive extends PipedInputStream {
+ @Override
+ public void receive(int oneByte) throws IOException {
+ super.receive(oneByte);
+ }
+ }
- boolean pass;
- public void run() {
- try {
- pos.write(1);
- } catch (IOException e) {
- pass = true;
- }
- }
- }
- MyRunnable myRun = new MyRunnable();
- synchronized (pis) {
- t = new Thread(myRun);
- // thread t will be blocked inside pos.write(1)
- // when it tries to call the synchronized method pis.receive
- // because we hold the monitor for object pis
- t.start();
- try {
- // wait for thread t to get to the call to pis.receive
- Thread.sleep(100);
- } catch (InterruptedException e) {
- }
- // now we close
- pos.close();
- }
- // we have exited the synchronized block, so now thread t will make
- // a call to pis.receive AFTER the output stream was closed,
- // in which case an IOException should be thrown
- while (t.isAlive()) {
- ;
+ public void test_receive_failsIfWriterClosed() throws Exception {
+ // attempt to write to stream after writer closed
+ PipedInputStreamWithPublicReceive pis = new PipedInputStreamWithPublicReceive();
+
+ pos = new PipedOutputStream();
+ pos.connect(pis);
+ pos.close();
+ try {
+ pis.receive(1);
+ fail();
+ } catch (IOException expected) {
}
- assertTrue(
- "write failed to throw IOException on closed PipedOutputStream",
- myRun.pass);
}
static class Worker extends Thread {
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/CookieStoreTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/CookieStoreTest.java
index d437802..72be761 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/CookieStoreTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/net/CookieStoreTest.java
@@ -39,7 +39,7 @@ public class CookieStoreTest extends TestCase {
HttpCookie cookie = new HttpCookie("name1", "value1");
cookie.setDiscard(true);
- // This needn't throw. We should use the cookies domain, if set.
+ // This needn't throw. We should use the cookie's domain, if set.
// If no domain is set, this cookie will languish in the store until
// it expires.
cookieStore.add(null, cookie);
@@ -303,7 +303,7 @@ public class CookieStoreTest extends TestCase {
cookieStore.add(uri2, cookie2);
HttpCookie cookie3 = new HttpCookie("cookie_name3", "cookie_value3");
assertFalse(cookieStore.remove(null, cookie3));
- // No guarantees on behaviour if we call remove with a different
+ // No guarantees on behavior if we call remove with a different
// uri from the one originally associated with the cookie.
assertFalse(cookieStore.remove(null, cookie1));
assertTrue(cookieStore.remove(uri1, cookie1));
diff --git a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/TreeSetTest.java b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/TreeSetTest.java
index 265f5b9..43efa2d 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/TreeSetTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/TreeSetTest.java
@@ -319,14 +319,6 @@ public class TreeSetTest extends junit.framework.TestCase {
s2.add(new Object());
assertFalse("Sets should not be equal 3", s1.equals(s2));
assertFalse("Sets should not be equal 4", s2.equals(s1));
-
- // comparing TreeSets with not-comparable objects inside
- s1 = new TreeSet();
- s2 = new TreeSet();
- s1.add(new Object());
- s2.add(new Object());
- assertFalse("Sets should not be equal 5", s1.equals(s2));
- assertFalse("Sets should not be equal 6", s2.equals(s1));
}
/**
diff --git a/luni/src/main/java/java/io/PipedInputStream.java b/luni/src/main/java/java/io/PipedInputStream.java
index 2c27695..3e81cb8 100644
--- a/luni/src/main/java/java/io/PipedInputStream.java
+++ b/luni/src/main/java/java/io/PipedInputStream.java
@@ -390,6 +390,7 @@ public class PipedInputStream extends InputStream {
if (lastReader != null && !lastReader.isAlive()) {
throw new IOException("Pipe broken");
}
+
notifyAll();
wait(1000);
}
@@ -402,6 +403,10 @@ public class PipedInputStream extends InputStream {
if (in == -1) {
in = 0;
}
+ if (lastReader != null && !lastReader.isAlive()) {
+ throw new IOException("Pipe broken");
+ }
+
buffer[in++] = (byte) oneByte;
if (in == buffer.length) {
in = 0;
diff --git a/luni/src/main/java/java/io/PipedOutputStream.java b/luni/src/main/java/java/io/PipedOutputStream.java
index 1b139e9..4c431c1 100644
--- a/luni/src/main/java/java/io/PipedOutputStream.java
+++ b/luni/src/main/java/java/io/PipedOutputStream.java
@@ -140,7 +140,7 @@ public class PipedOutputStream extends OutputStream {
* @throws IOException
* if this stream is not connected, if the target stream is
* closed or if the thread reading from the target stream is no
- * longer alive. This case is currently not handled correctly.
+ * longer alive.
*/
@Override
public void write(byte[] buffer, int offset, int count) throws IOException {
diff --git a/luni/src/main/java/java/net/URI.java b/luni/src/main/java/java/net/URI.java
index 6b7b1da..09268b8 100644
--- a/luni/src/main/java/java/net/URI.java
+++ b/luni/src/main/java/java/net/URI.java
@@ -766,33 +766,51 @@ public final class URI implements Comparable<URI>, Serializable {
}
/**
- * Returns true if {@code first} and {@code second} are equal after
- * unescaping hex sequences like %F1 and %2b.
+ * Returns true if the given URI escaped strings {@code first} and {@code second} are
+ * equal.
+ *
+ * TODO: This method assumes that both strings are escaped using the same escape rules
+ * yet it still performs case insensitive comparison of the escaped sequences.
+ * Why is this necessary ? We can just replace it with first.equals(second)
+ * otherwise.
*/
private boolean escapedEquals(String first, String second) {
- if (first.indexOf('%') != second.indexOf('%')) {
- return first.equals(second);
+ // This length test isn't a micro-optimization. We need it because we sometimes
+ // calculate the number of characters to match based on the length of the second
+ // string. If the second string is shorter than the first, we might attempt to match
+ // 0 chars, and regionMatches is specified to return true in that case.
+ if (first.length() != second.length()) {
+ return false;
}
- int index, prevIndex = 0;
- while ((index = first.indexOf('%', prevIndex)) != -1
- && second.indexOf('%', prevIndex) == index) {
- boolean match = first.substring(prevIndex, index).equals(
- second.substring(prevIndex, index));
- if (!match) {
+ int prevIndex = 0;
+ while (true) {
+ int index = first.indexOf('%', prevIndex);
+ int index1 = second.indexOf('%', prevIndex);
+ if (index != index1) {
+ return false;
+ }
+
+ // index == index1 from this point on.
+
+ if (index == -1) {
+ // No more escapes, match the remainder of the string
+ // normally.
+ return first.regionMatches(prevIndex, second, prevIndex,
+ second.length() - prevIndex);
+ }
+
+ if (!first.regionMatches(prevIndex, second, prevIndex, (index - prevIndex))) {
return false;
}
- match = first.substring(index + 1, index + 3).equalsIgnoreCase(
- second.substring(index + 1, index + 3));
- if (!match) {
+ if (!first.regionMatches(true /* ignore case */, index + 1, second, index + 1, 2)) {
return false;
}
index += 3;
prevIndex = index;
}
- return first.substring(prevIndex).equals(second.substring(prevIndex));
}
@Override public boolean equals(Object o) {
diff --git a/luni/src/test/java/libcore/java/net/URITest.java b/luni/src/test/java/libcore/java/net/URITest.java
index 04a7d2e..0267699 100644
--- a/luni/src/test/java/libcore/java/net/URITest.java
+++ b/luni/src/test/java/libcore/java/net/URITest.java
@@ -16,9 +16,9 @@
package libcore.java.net;
+import junit.framework.TestCase;
import java.net.URI;
import java.net.URISyntaxException;
-import junit.framework.TestCase;
import libcore.util.SerializationTester;
public final class URITest extends TestCase {
@@ -57,6 +57,24 @@ public final class URITest extends TestCase {
.equals(new URI("http://localhost/foo?bar=baz#QUUX")));
}
+ public void testEqualsEscaping() throws Exception {
+ // Case insensitive when comparing escaped values, but not when
+ // comparing unescaped values.
+ assertEquals(new URI("http://localhost/foo?bar=fooobar%E0%AE%A8%E0bar"),
+ new URI("http://localhost/foo?bar=fooobar%E0%AE%a8%e0bar"));
+ assertFalse(new URI("http://localhost/foo?bar=fooobar%E0%AE%A8%E0bar").equals(
+ new URI("http://localhost/foo?bar=FoooBar%E0%AE%a8%e0bar")));
+ assertFalse(new URI("http://localhost/foo?bar=fooobar%E0%AE%A8%E0bar").equals(
+ new URI("http://localhost/foo?bar=fooobar%E0%AE%a8%e0BaR")));
+
+ // Last byte replaced by an unescaped value.
+ assertFalse(new URI("http://localhost/foo?bar=%E0%AE%A8%E0").equals(
+ new URI("http://localhost/foo?bar=%E0%AE%a8xxx")));
+ // Missing byte.
+ assertFalse(new URI("http://localhost/foo?bar=%E0%AE%A8%E0").equals(
+ new URI("http://localhost/foo?bar=%E0%AE%a8")));
+ }
+
public void testFileEqualsWithEmptyHost() throws Exception {
assertEquals(new URI("file", "", "/a/", null), new URI("file:/a/"));
assertEquals(new URI("file", null, "/a/", null), new URI("file:/a/"));