Bug 69033 - Please add "platform:/meta" URLConnection support (final touches)
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/Messages.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/Messages.java
index 2fa1f78..f8c841c 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/Messages.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/Messages.java
@@ -175,6 +175,7 @@
 	public static String url_createConnection;
 	public static String url_invalidURL;
 	public static String url_noaccess;
+	public static String url_noOutput;
 	public static String url_resolveFragment;
 	public static String url_resolvePlugin;
 
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLConfigConnection.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLConfigConnection.java
index fe1313e..9ecd117 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLConfigConnection.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLConfigConnection.java
@@ -12,12 +12,15 @@
 
 import java.io.*;
 import java.net.URL;
+import java.net.UnknownServiceException;
 import org.eclipse.core.internal.boot.PlatformURLConnection;
 import org.eclipse.core.internal.boot.PlatformURLHandler;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.util.NLS;
 
 public class PlatformURLConfigConnection extends PlatformURLConnection {
+	private static final String FILE_PROTOCOL = "file"; //$NON-NLS-1$
 	private static boolean isRegistered = false;
 	public static final String CONFIG = "config"; //$NON-NLS-1$
 
@@ -35,7 +38,28 @@
 		if (!spec.startsWith(CONFIG))
 			throw new IOException(NLS.bind(Messages.url_badVariant, url.toString()));
 		String path = spec.substring(CONFIG.length() + 1);
-		return new URL(Platform.getConfigurationLocation().getURL(), path);
+		// resolution takes parent configuration into account (if it exists)
+		Location localConfig = Platform.getConfigurationLocation();
+		Location parentConfig = localConfig.getParentLocation();
+		// assume we will find the file locally
+		URL localURL = new URL(localConfig.getURL(), path);
+		if (!FILE_PROTOCOL.equals(localURL.getProtocol()) || parentConfig == null)
+			// we only support cascaded file: URLs
+			return localURL;
+		File localFile = new File(localURL.getPath());
+		if (localFile.exists())
+			// file exists in local configuration
+			return localURL;
+		// try to find in the parent configuration
+		URL parentURL = new URL(parentConfig.getURL(), path);
+		if (FILE_PROTOCOL.equals(parentURL.getProtocol())) {
+			// we only support cascaded file: URLs			
+			File parentFile = new File(parentURL.getPath());
+			if (parentFile.exists())
+				// parent has the file
+				return parentURL;
+		}
+		return localURL;
 	}
 
 	public static void startup() {
@@ -50,6 +74,8 @@
 	 * @see java.net.URLConnection#getOutputStream()
 	 */
 	public OutputStream getOutputStream() throws IOException {
+		if (Platform.getConfigurationLocation().isReadOnly())
+			throw new UnknownServiceException(NLS.bind(Messages.url_noOutput, url));
 		//This is not optimal but connection is a private ivar in super.
 		URL resolved = getResolvedURL();
 		if (resolved != null) {
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLMetaConnection.java b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLMetaConnection.java
index 2845d5d..8282364 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLMetaConnection.java
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/PlatformURLMetaConnection.java
@@ -14,7 +14,8 @@
 import java.net.URL;
 import org.eclipse.core.internal.boot.PlatformURLConnection;
 import org.eclipse.core.internal.boot.PlatformURLHandler;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.Bundle;
 
@@ -41,11 +42,11 @@
 		String id = getId(ref);
 		target = InternalPlatform.getDefault().getBundle(id);
 		if (target == null)
-			throw new IOException(NLS.bind(Messages.url_resolvePlugin, url.toString())); 
+			throw new IOException(NLS.bind(Messages.url_resolvePlugin, url.toString()));
 		IPath path = Platform.getStateLocation(target);
 		if (ix != -1 || (ix + 1) <= spec.length())
 			path = path.append(spec.substring(ix + 1));
-		return new URL("file", null, path.toString()); //$NON-NLS-1$
+		return path.toFile().toURL(); //$NON-NLS-1$
 	}
 
 	public static void startup() {
diff --git a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties
index ff2b15d..328839a 100644
--- a/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties
+++ b/bundles/org.eclipse.core.runtime/src/org/eclipse/core/internal/runtime/messages.properties
@@ -167,6 +167,7 @@
 url_badVariant=Unsupported \"platform:\" protocol variation \"{0}\".
 url_createConnection=Unable to create connection on \"{0}\".
 url_invalidURL=Invalid URL \"{0}\".
+url_noOutput=Output is not supported for \"{0}\".
 url_noaccess=Unhandled URL protocol \"{0}\".
 url_resolveFragment=Unable to resolve fragment \"{0}\".
 url_resolvePlugin=Unable to resolve plug-in \"{0}\".
diff --git a/tests/org.eclipse.core.tests.runtime/META-INF/MANIFEST.MF b/tests/org.eclipse.core.tests.runtime/META-INF/MANIFEST.MF
index dee3a31..a19cf2f 100644
--- a/tests/org.eclipse.core.tests.runtime/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.core.tests.runtime/META-INF/MANIFEST.MF
@@ -17,8 +17,7 @@
  org.eclipse.core.tests.runtime.model,
  org.eclipse.core.tests.runtime.perf,
  org.eclipse.core.tests.runtime.session
-Require-Bundle: org.eclipse.osgi.services,
- org.eclipse.core.runtime.compatibility,
+Require-Bundle: org.eclipse.core.runtime.compatibility,
  org.eclipse.core.tests.harness,
  org.junit,
  org.eclipse.test.performance;resolution:=optional
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/AllTests.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/AllTests.java
index cfd5da9..4d7ef42 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/AllTests.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/AllTests.java
@@ -35,6 +35,8 @@
 		suite.addTest(CipherStreamsTest.suite());
 		suite.addTest(CipherTest.suite());
 		suite.addTest(LogSerializationTest.suite());
+		suite.addTest(PlatformURLLocalTest.suite());
+		suite.addTest(PlatformURLSessionTest.suite());
 		return suite;
 	}
 }
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLLocalTest.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLLocalTest.java
new file mode 100644
index 0000000..b6cb955
--- /dev/null
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLLocalTest.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.tests.internal.runtime;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.tests.runtime.RuntimeTest;
+import org.eclipse.core.tests.runtime.RuntimeTestsPlugin;
+
+public class PlatformURLLocalTest extends RuntimeTest {
+
+	public static void assertEquals(String tag, URL expected, URL actual, boolean external) {
+		if (external) {
+			assertEquals(tag, expected, actual);
+			return;
+		}
+		try {
+			assertEquals(tag + ".1", new URL(expected.getProtocol(), expected.getHost(), expected.getPort(), expected.getFile()), new URL(actual.getProtocol(), actual.getHost(), actual.getPort(), actual.getFile()));
+		} catch (MalformedURLException e) {
+			fail(tag + ".2", e);
+		}
+	}
+
+	public static Test suite() {
+		return new TestSuite(PlatformURLLocalTest.class);
+	}
+
+	public PlatformURLLocalTest(String name) {
+		super(name);
+	}
+
+	public void testPlatformURLConfigResolution() {
+		URL platformURL = null;
+		try {
+			// 	create a fake URL
+			platformURL = new URL("platform:/config/x");
+		} catch (MalformedURLException e) {
+			fail("1.0", e);
+		}
+		URL resolvedURL = null;
+		try {
+			resolvedURL = Platform.resolve(platformURL);
+		} catch (IOException e) {
+			fail("2.0", e);
+		}
+		assertFalse("3.0", platformURL.equals(resolvedURL));
+		URL expected = null;
+		try {
+			expected = new URL(Platform.getConfigurationLocation().getURL(), "x");
+		} catch (MalformedURLException e) {
+			fail("4.0", e);
+		}
+		assertEquals("5.0", expected, resolvedURL, false);
+	}
+
+	public void testPlatformURLMetaResolution() {
+		URL platformURL = null;
+		try {
+			// 	create a fake URL
+			platformURL = new URL("platform:/meta/" + PI_RUNTIME_TESTS + "/x");
+		} catch (MalformedURLException e) {
+			fail("1.0", e);
+		}
+		URL resolvedURL = null;
+		try {
+			resolvedURL = Platform.resolve(platformURL);
+		} catch (IOException e) {
+			fail("2.0", e);
+		}
+		assertFalse("3.0", platformURL.equals(resolvedURL));
+		URL expected = null;
+		try {
+			expected = new URL(RuntimeTestsPlugin.getPlugin().getStateLocation().toFile().toURL(), "x");
+		} catch (MalformedURLException e) {
+			fail("4.0", e);
+		}
+		assertEquals("5.0", expected, resolvedURL, false);
+	}
+}
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLSessionTest.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLSessionTest.java
new file mode 100644
index 0000000..e53a9b7
--- /dev/null
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/internal/runtime/PlatformURLSessionTest.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright (c) 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.tests.internal.runtime;
+
+import java.io.*;
+import java.net.*;
+import junit.framework.Test;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.tests.runtime.RuntimeTest;
+import org.eclipse.core.tests.session.ConfigurationSessionTestSuite;
+import org.eclipse.osgi.service.datalocation.Location;
+
+public class PlatformURLSessionTest extends RuntimeTest {
+
+	private static final String CONFIG_URL = "platform:/config/" + PI_RUNTIME_TESTS + "/";
+	private static final String DATA_CHILD = "child";
+	private static final String DATA_PARENT = "parent";
+	private static final String FILE_BOTH_PARENT_AND_CHILD = "both.txt";
+	private static final String FILE_CHILD_ONLY = "child.txt";
+	private static final String FILE_PARENT_ONLY = "parent.txt";
+
+	public static void assertEquals(String tag, URL expected, URL actual, boolean external) {
+		if (external) {
+			assertEquals(tag, expected, actual);
+			return;
+		}
+		assertEquals(tag + " different protocol", expected.getProtocol(), actual.getProtocol());
+		assertEquals(tag + " different host", expected.getHost(), actual.getHost());
+		assertEquals(tag + " different path", expected.getPath(), actual.getPath());
+		assertEquals(tag + " different port", expected.getPort(), actual.getPort());
+	}
+
+	private static String readContents(String tag, URL url) {
+		URLConnection connection = null;
+		try {
+			connection = url.openConnection();
+		} catch (IOException e) {
+			fail(tag + ".1", e);
+		}
+		InputStream input = null;
+		try {
+			input = connection.getInputStream();
+		} catch (IOException e) {
+			fail(tag + ".2", e);
+		}
+		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
+		String line = null;
+		StringBuffer result = new StringBuffer();
+		try {
+			while ((line = reader.readLine()) != null)
+				result.append(line);
+			return result.toString();
+		} catch (IOException e) {
+			fail(tag + ".99", e);
+		} finally {
+			try {
+				reader.close();
+			} catch (IOException e) {
+				// not interested
+			}
+		}
+		// never happens
+		return null;
+	}
+
+	public static Test suite() {
+		ConfigurationSessionTestSuite suite = new ConfigurationSessionTestSuite(PI_RUNTIME_TESTS, PlatformURLSessionTest.class);
+		suite.setReadOnly(true);
+		suite.setCascaded(true);
+		String[] ids = ConfigurationSessionTestSuite.MINIMAL_BUNDLE_SET;
+		for (int i = 0; i < ids.length; i++)
+			suite.addBundle(ids[i]);
+		suite.addBundle(PI_RUNTIME_TESTS);
+		return suite;
+	}
+
+	public PlatformURLSessionTest(String name) {
+		super(name);
+	}
+
+	/**
+	 * Creates test data in both child and parent configurations.
+	 */
+	public void test0CreateData() {
+		URL childConfigURL = Platform.getConfigurationLocation().getURL();
+		//tests run with file based configuration
+		assertEquals("0.1", "file", childConfigURL.getProtocol());
+		File childConfigPrivateDir = new File(childConfigURL.getPath(), PI_RUNTIME_TESTS);
+		try {
+			createFileInFileSystem(new File(childConfigPrivateDir, FILE_CHILD_ONLY), getContents(DATA_CHILD));
+		} catch (IOException e) {
+			fail("1.0");
+		}
+		try {
+			createFileInFileSystem(new File(childConfigPrivateDir, FILE_BOTH_PARENT_AND_CHILD), getContents(DATA_CHILD));
+		} catch (IOException e) {
+			fail("2.0");
+		}
+
+		Location parent = Platform.getConfigurationLocation().getParentLocation();
+		//tests run with cascaded configuration
+		assertNotNull("4.0", parent);
+		URL parentConfigURL = parent.getURL();
+		//tests run with file based configuration
+		assertEquals("4.1", "file", parentConfigURL.getProtocol());
+		File parentConfigPrivateDir = new File(parentConfigURL.getPath(), PI_RUNTIME_TESTS);
+		try {
+			createFileInFileSystem(new File(parentConfigPrivateDir, FILE_PARENT_ONLY), getContents(DATA_PARENT));
+		} catch (IOException e) {
+			fail("5.0");
+		}
+		try {
+			createFileInFileSystem(new File(parentConfigPrivateDir, FILE_BOTH_PARENT_AND_CHILD), getContents(DATA_PARENT));
+		} catch (IOException e) {
+			fail("6.0");
+		}
+	}
+
+	public void test1OutputOnReadOnly() {
+		// try to modify a file in the configuration area  -should fail
+		URL configURL = null;
+		try {
+			configURL = new URL(CONFIG_URL + "somefile.txt");
+		} catch (MalformedURLException e) {
+			fail("1.0", e);
+		}
+		URLConnection connection = null;
+		try {
+			connection = configURL.openConnection();
+		} catch (IOException e) {
+			fail("2.0", e);
+		}
+		connection.setDoOutput(true);
+		OutputStream output = null;
+		try {
+			output = connection.getOutputStream();
+			fail("3.0 - should have failed");
+		} catch (IOException e) {
+			// that is expected - configuration area is read-only
+		} finally {
+			if (output != null)
+				try {
+					output.close();
+				} catch (IOException e) {
+					// not interested
+				}
+		}
+	}
+
+	public void test2Resolution() {
+		URL parent = null;
+		URL child = null;
+		URL both = null;
+		URL none = null;
+		try {
+			parent = new URL(CONFIG_URL + FILE_PARENT_ONLY);
+			child = new URL(CONFIG_URL + FILE_CHILD_ONLY);
+			both = new URL(CONFIG_URL + FILE_BOTH_PARENT_AND_CHILD);
+			none = new URL(CONFIG_URL + "none.txt");
+		} catch (MalformedURLException e) {
+			fail("0.1", e);
+		}
+		assertEquals("1.0", DATA_PARENT, readContents("1.1", parent));
+		assertEquals("2.0", DATA_CHILD, readContents("2.1", child));
+		assertEquals("3.0", DATA_CHILD, readContents("3.1", both));
+		URL resolvedURL = null;
+		try {
+			resolvedURL = Platform.resolve(none);
+		} catch (IOException e) {
+			fail("4.0", e);
+		}
+		assertFalse("4.1", none.equals(resolvedURL));
+		assertTrue("4.2", resolvedURL.toExternalForm().startsWith(Platform.getConfigurationLocation().getURL().toExternalForm()));
+	}
+
+}
diff --git a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/RuntimeTestsPlugin.java b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/RuntimeTestsPlugin.java
index 5e3ad41..8973727 100644
--- a/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/RuntimeTestsPlugin.java
+++ b/tests/org.eclipse.core.tests.runtime/src/org/eclipse/core/tests/runtime/RuntimeTestsPlugin.java
@@ -41,4 +41,8 @@
 		return plugin != null ? plugin.context : null;
 	}
 
+	public static Plugin getPlugin() {
+		return plugin;
+	}
+
 }