This commit was manufactured by cvs2svn to create tag 'R1_0_2'.
diff --git a/docs/org.eclipse.wst.validation.infopop/.project b/docs/org.eclipse.wst.validation.infopop/.project
new file mode 100644
index 0000000..28f5b4f
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/.project
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.validation.infopop</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+	</natures>
+</projectDescription>
diff --git a/docs/org.eclipse.wst.validation.infopop/META-INF/MANIFEST.MF b/docs/org.eclipse.wst.validation.infopop/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..f48ef97
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %Plugin.name
+Bundle-SymbolicName: org.eclipse.wst.validation.infopop; singleton:=true
+Bundle-Version: 1.0.2.qualifier
+Bundle-Localization: plugin
+Bundle-Vendor: %Plugin.providerName
+Require-Bundle: org.eclipse.help
diff --git a/docs/org.eclipse.wst.validation.infopop/ValidationPrefs_HelpContexts.xml b/docs/org.eclipse.wst.validation.infopop/ValidationPrefs_HelpContexts.xml
new file mode 100644
index 0000000..e18a4e5
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/ValidationPrefs_HelpContexts.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?NLS type="org.eclipse.help.contexts"?>
+
+<contexts>
+	<!-- Eclipse Validation preferences -->
+<context id="jvgp0000">
+<description> The validation preferences page allows you to view or change the default validation settings for all workbench projects. A validator is a tool that checks that resources conform to a specification, DTD, or some other set of rules.
+
+Select the <b>Allow projects to override these preference settings</b> check box if you want to allow individual projects to set their own validation preferences.
+To configure new validation settings for an individual project, select the project in the Navigator view, right-click and select <b>Properties > Validation</b>.
+
+Select the <b>Run validation when you manually build a project</b> check box if you want the selected validators to run whenever you build your projects.
+To enable the <b>Run validation when you manually build a project</b> check box, select at least one validator in the list.
+
+Select  the <b>Run validation automatically when you save changes to a resource</b> check box if you want the selected validators to automatically run whenever you save changes to any project resources.
+To enable the <b>Run validation automatically when you save changes to a resource</b> check box, select at least one validator in the list. 
+
+Use the <b>Maximum number of validation messages</b> field to define the maximum allowable validation messages for the project.
+If the number of validation messages reported in the task list exceeds the number set in this field, validation will terminate.
+</description>
+<topic label="Validating code in enterprise applications" href="../org.eclipse.jst.j2ee.doc.user/topics/tjval.html"/>
+<topic label="Common validation errors and solutions" href="../org.eclipse.jst.j2ee.doc.user/topics/rvalerr.html"/>
+</context>
+
+</contexts>
\ No newline at end of file
diff --git a/docs/org.eclipse.wst.validation.infopop/ValidationProjPrefs_HelpContexts.xml b/docs/org.eclipse.wst.validation.infopop/ValidationProjPrefs_HelpContexts.xml
new file mode 100644
index 0000000..1e9fba0
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/ValidationProjPrefs_HelpContexts.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?NLS type="org.eclipse.help.contexts"?>
+
+<contexts>
+	<!-- Validation settings for project -->
+
+<context id="jvpp0000">
+<description>The project validation page allows you to view or change the validation settings for a project. A validator is a tool that checks that resources conform to a specification, DTD, or some other set of rules.
+
+Select the <b>Override validation preferences</b> check box. Select this check box if you want to override the default validation preferences set in the workbench Preferences page.
+If the <b>Override validation preferences</b> check box is not enabled, go to <b>Window > Preferences > Validation</b> and select the <b>Allow projects to override these preference settings</b> check box.
+
+Select the <b>Run validation when you manually build</b> check box if you want the selected validators to run whenever you build your project.
+To enable the <b>Run validation when you manually build</b> check box, select at least one validator in the list.
+
+Select  the <b>Run validation automatically when you save changes to resources</b> check box if you want the selected validators to automatically run whenever you save changes to your project resources.
+To enable the <b>Run validation automatically when you save changes to resources</b> check box, select at least one validator in the list. 
+
+Use the <b>Maximum number of validation messages</b> field to define the maximum allowable validation messages for the project.
+If the number of validation messages reported in the task list exceeds the number set in this field, validation will terminate.
+</description>
+<topic label="Validating code in enterprise applications" href="../org.eclipse.jst.j2ee.doc.user/topics/tjval.html"/>
+<topic label="Common validation errors and solutions" href="..org.eclipse.jst.j2ee.doc.user/topics/rvalerr.html"/>
+</context>
+
+</contexts>
\ No newline at end of file
diff --git a/docs/org.eclipse.wst.validation.infopop/build.properties b/docs/org.eclipse.wst.validation.infopop/build.properties
new file mode 100644
index 0000000..1df77e7
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/build.properties
@@ -0,0 +1,6 @@
+bin.includes = META-INF/,\
+               plugin.xml,\
+               ValidationPrefs_HelpContexts.xml,\
+               ValidationProjPrefs_HelpContexts.xml,\
+               plugin.properties
+src.includes = build.properties
diff --git a/docs/org.eclipse.wst.validation.infopop/plugin.properties b/docs/org.eclipse.wst.validation.infopop/plugin.properties
new file mode 100644
index 0000000..b0c0709
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/plugin.properties
@@ -0,0 +1,6 @@
+# NLS_MESSAGEFORMAT_VAR
+# ==============================================================================
+# Translation Instruction: section to be translated
+# ==============================================================================
+Plugin.name = WST validation infopop plug-in
+Plugin.providerName = Eclipse.org
\ No newline at end of file
diff --git a/docs/org.eclipse.wst.validation.infopop/plugin.xml b/docs/org.eclipse.wst.validation.infopop/plugin.xml
new file mode 100644
index 0000000..1a861c3
--- /dev/null
+++ b/docs/org.eclipse.wst.validation.infopop/plugin.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<plugin>
+   <extension
+         point="org.eclipse.help.toc">
+    <contexts file="ValidationProjPrefs_HelpContexts.xml" plugin="org.eclipse.wst.validation.ui" />
+	<contexts file="ValidationPrefs_HelpContexts.xml" plugin="org.eclipse.wst.validation.ui" />
+   </extension>
+
+</plugin>
diff --git a/plugins/org.eclipse.wst.common.infopop/.cvsignore b/plugins/org.eclipse.wst.common.infopop/.cvsignore
new file mode 100644
index 0000000..c14487c
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/.cvsignore
@@ -0,0 +1 @@
+build.xml
diff --git a/plugins/org.eclipse.wst.common.infopop/.project b/plugins/org.eclipse.wst.common.infopop/.project
new file mode 100644
index 0000000..d8e0250
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/.project
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.common.infopop</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.wst.common.infopop/META-INF/MANIFEST.MF b/plugins/org.eclipse.wst.common.infopop/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..1be75da
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Common WST infopops
+Bundle-SymbolicName: org.eclipse.wst.common.infopop; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: Eclipse.org
+Bundle-Localization: plugin
+Eclipse-AutoStart: true
diff --git a/plugins/org.eclipse.wst.common.infopop/SnippetsContexts.xml b/plugins/org.eclipse.wst.common.infopop/SnippetsContexts.xml
new file mode 100644
index 0000000..b3b0c43
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/SnippetsContexts.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?NLS type="org.eclipse.help.contexts"?>
+<!-- /*******************************************************************************
+ * Copyright (c) 2000, 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
+ *******************************************************************************/ -->
+
+<contexts>
+<context id="libv1000">
+<description>Use this view to catalog and organize reusable programming objects, such as HTML tagging, JavaScript, and JSP markup, along with custom tags, that can be embedded in existing files.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+</context>
+
+<context id="libv1100">
+<description>Supply values in this dialog to define the Snippets view object to be inserted in the current file at the cursor position. Assign a value to the variables being inserted.
+</description>
+<topic label="Inserting and editing items in the active editor" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+</context>
+
+<context id="libv1200">
+<description>In the <b>Name</b> field, type the name of the object as you want it be displayed in the Snippets view.  Optionally, add a description of the object in the <b>Description</b> field.
+
+Select the <b>Hide</b> check box to make this snippet object not visible in the view.
+
+To add a new variable, click the <b>New</b> push button, and type values in the cells of the <b>Variables</b> table. The <b>Name</b> field represents name of the variable that will be inserted when you add it to a document. <b>Default value</b> is the initial value of the named variable. Optionally, add a description of the variable.
+
+Insert the variables that you define into the <b>Template Pattern</b> field using the <b>Insert Variable Placeholder</b> push button.
+</description>
+<topic label="Deleting or hiding snippet items or drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt016.html"/>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+</context>
+
+<context id="libv1300">
+<description>Type the name of the new category in the <b>Name</b> field and optionally add a description of the category in the <b>Description</b> field.
+
+Select <b>Open drawer at start-up</b> to have the category drawer opened in the Snippets view when the workbench starts up.
+
+You can specify how you want the category drawer displayed in the Snippets view by selecting a radio button for the <b>Show/Hide Drawer</b> field.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Deleting or hiding snippet items or drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt016.html"/>
+</context>
+
+<context id="snip0010">
+<description>Select this option to open the <b>Import</b> dialog, which lets you import snippet categories from the file system.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Adding items to snippets drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+</context>
+
+<context id="snip0020">
+<description>Select this option to open the <b>Export</b> dialog, which lets you export snippet categories to the file system.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Adding items to snippets drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+</context>
+
+<context id="snip0030">
+<description>Select the content types that should be visible in this drawer for in the Snippets view.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Deleting or hiding snippet items or drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt016.html"/>
+</context>
+
+<context id="snip0040">
+<description>This menu item adds the selected text to the Snippets view as a new item.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Adding items to snippets drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+</context>
+
+<context id="snip0050">
+<description>This menu item cuts the selected Snippet item to the clipboard.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Editing snippet items" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt022.html"/>
+</context>
+
+<context id="snip0060">
+<description>This menu item copies the selected Snippet item to the clipboard.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Editing snippet items" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt022.html"/>
+</context>
+
+<context id="snip0070">
+<description>This menu item creates a new Snippets view item containing the current contents of the clipboard.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Adding items to snippets drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+</context>
+<context id="snip0080">
+<description>Type a name for the category. The category appears in the Customize Palette dialog box. Once you have created a category, you can add items to the category.
+</description>
+<topic label="Snippets view" href="../org.eclipse.wst.sse.doc.user/topics/csrcedt001.html"/>
+<topic label="Adding items to snippets drawers" href="../org.eclipse.wst.sse.doc.user/topics/tsrcedt015.html"/>
+</context>
+
+</contexts>
diff --git a/plugins/org.eclipse.wst.common.infopop/about.html b/plugins/org.eclipse.wst.common.infopop/about.html
new file mode 100644
index 0000000..4c99086
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/about.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<head>
+<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>February 24, 2005</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.infopop/build.properties b/plugins/org.eclipse.wst.common.infopop/build.properties
new file mode 100644
index 0000000..ed51e16
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/build.properties
@@ -0,0 +1,6 @@
+bin.includes = plugin.xml,\
+               about.html,\
+               SnippetsContexts.xml,\
+               EditorCssContexts.xml,\
+               META-INF/
+src.includes = build.properties
diff --git a/plugins/org.eclipse.wst.common.infopop/plugin.xml b/plugins/org.eclipse.wst.common.infopop/plugin.xml
new file mode 100644
index 0000000..f1e178c
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.infopop/plugin.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<!-- ================================================= -->
+<!-- This is the plugin for declaring the help         -->
+<!-- contributions for using the tool.                 -->
+<!-- ================================================= -->
+
+<plugin>
+
+<extension point="org.eclipse.help.contexts">
+       <contexts file="SnippetsContexts.xml" plugin ="org.eclipse.wst.common.snippets"/>
+</extension>
+
+
+</plugin>
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.uriresolver/.classpath b/plugins/org.eclipse.wst.common.uriresolver/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.wst.common.uriresolver/.cvsignore b/plugins/org.eclipse.wst.common.uriresolver/.cvsignore
new file mode 100644
index 0000000..80bdb6d
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/.cvsignore
@@ -0,0 +1,7 @@
+bin
+uriresolver.jar
+build.xml
+temp.folder
+org.eclipse.wst.common.uriresolver_1.0.0.jar
+@dot
+src.zip
diff --git a/plugins/org.eclipse.wst.common.uriresolver/.project b/plugins/org.eclipse.wst.common.uriresolver/.project
new file mode 100644
index 0000000..e19b693
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.common.uriresolver</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.pde.PluginNature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.wst.common.uriresolver/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.wst.common.uriresolver/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b8a0d61
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,57 @@
+#Mon Jan 30 19:48:07 EST 2006
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/plugins/org.eclipse.wst.common.uriresolver/META-INF/MANIFEST.MF b/plugins/org.eclipse.wst.common.uriresolver/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..2caa0e9
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/META-INF/MANIFEST.MF
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.wst.common.uriresolver; singleton:=true
+Bundle-Version: 1.0.1.qualifier
+Bundle-Activator: org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Export-Package: org.eclipse.wst.common.uriresolver.internal,
+ org.eclipse.wst.common.uriresolver.internal.provisional,
+  org.eclipse.wst.common.uriresolver.internal.util
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.core.resources
+Eclipse-AutoStart: true
+Bundle-ClassPath: .
diff --git a/plugins/org.eclipse.wst.common.uriresolver/README.txt b/plugins/org.eclipse.wst.common.uriresolver/README.txt
new file mode 100644
index 0000000..acaf514
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/README.txt
@@ -0,0 +1 @@
+API for an URI resolver.
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.uriresolver/about.html b/plugins/org.eclipse.wst.common.uriresolver/about.html
new file mode 100644
index 0000000..6f6b96c
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/about.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>February 24, 2005</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.uriresolver/build.properties b/plugins/org.eclipse.wst.common.uriresolver/build.properties
new file mode 100644
index 0000000..afe5fa3
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/build.properties
@@ -0,0 +1,11 @@
+source.. = src/
+output.. = bin/
+bin.includes = plugin.xml,\
+               META-INF/,\
+               about.html,\
+               .,\
+               plugin.properties
+bin.excludes = bin/**,\
+               @dot/**,\
+               temp.folder/**
+               
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.uriresolver/plugin.properties b/plugins/org.eclipse.wst.common.uriresolver/plugin.properties
new file mode 100644
index 0000000..307664b
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/plugin.properties
@@ -0,0 +1,15 @@
+###############################################################################
+# 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
+###############################################################################
+providerName=Eclipse.org
+pluginName=Common URI Resolver Framework
+###############################################################################
+
+resolverExtensions=URI Resolver Extensions Point
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.common.uriresolver/plugin.xml b/plugins/org.eclipse.wst.common.uriresolver/plugin.xml
new file mode 100644
index 0000000..9aa99c4
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/plugin.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<plugin>
+   <extension-point id="resolverExtensions" name="%resolverExtensions" schema="schema/resolverExtensions.exsd"/>
+
+
+</plugin>
diff --git a/plugins/org.eclipse.wst.common.uriresolver/schema/resolverExtensions.exsd b/plugins/org.eclipse.wst.common.uriresolver/schema/resolverExtensions.exsd
new file mode 100644
index 0000000..702bb0c
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/schema/resolverExtensions.exsd
@@ -0,0 +1,167 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.wst.common.uriresolver">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="org.eclipse.wst.common.uriresolver" id="resolverExtensions" name="URI Resolver Extensions Point"/>
+      </appInfo>
+      <documentation>
+         The URI Resolver Extensions point allows clients to register custom URI resolvers that will be used in the resolution of resources by tools such as editors, generators, validators, and wizards.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <complexType>
+         <sequence>
+            <element ref="resolverExtension" minOccurs="1" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a fully qualified identifier of the target extension point
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  an optional identifier of the extension instance
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  an optional name of the extension instance
+               </documentation>
+               <appInfo>
+                  <meta.attribute translatable="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="resolverExtension">
+      <complexType>
+         <sequence>
+            <element ref="projectNature" minOccurs="0" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="stage" type="string">
+            <annotation>
+               <documentation>
+                  The stage in which to run this extension resolver: prenormalization, postnormalization, or physical. Defaults to postnormalization.&lt;br&gt;
+prenormalization:  run before normalization of the input&lt;br&gt;
+postnormalization: run after normalization of the input&lt;br&gt;
+physical:          run after all pre and postnormalization resolvers
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  The class that implements &lt;code&gt;org.eclipse.wst.common.uriresolver.internal.provisional.URIResolver&lt;/code&gt;.
+               </documentation>
+               <appInfo>
+                  <meta.attribute kind="java"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+         <attribute name="priority" type="string">
+            <annotation>
+               <documentation>
+                  The priority of this resolver: high, medium, or low. Defaults to medium. The priority allows you to specify when this resolver should run with respect to other resolvers defined for the same stage.
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="projectNature">
+      <annotation>
+         <documentation>
+            If a project nature is specified the URI resolver will only be used for projects that contain one of the specified project natures. If no project natures are specified the URI resolver will be used for all projects.
+         </documentation>
+      </annotation>
+      <complexType>
+         <attribute name="value" type="string" use="required">
+            <annotation>
+               <documentation>
+                  A project nature ID for which the URI resolver should be used.
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         &lt;b&gt;This extension point is part of an interim API that is still under development and expected to change significantly before reaching stability. It is being made available at this early stage to solicit feedback from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken (repeatedly) as the API evolves.&lt;/b&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         The following is an example of an URI resolver contribution:
+&lt;pre&gt;
+   &lt;extension
+         point=&quot;org.eclipse.wst.common.uriresolver.resolverExtensions&quot;&gt;
+      &lt;resolverExtension
+            stage=&quot;physical&quot;
+            priority=&quot;low&quot;
+            class=&quot;org.eclipse.wst.common.uriresolver.SampleResolver&quot;&gt;
+      &lt;/resolverExtension&gt;
+   &lt;/extension&gt;
+&lt;/pre&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiInfo"/>
+      </appInfo>
+      <documentation>
+         The supplied class must implement &lt;code&gt;org.eclipse.wst.common.uriresolver.internal.provisional.URIResolver&lt;/code&gt;.
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="implementation"/>
+      </appInfo>
+      <documentation>
+         &lt;code&gt;
+org.eclipse.wst.common.componentcore.internal.util.ComponentResolver
+&lt;/code&gt;
+provides an URI resolver that resolves references in a project with multiple components
+&lt;code&gt;
+org.eclipse.wst.internet.cache.internal.CacheURIResolverExtension
+&lt;/code&gt;
+provides an URI resolver that resolves references in a cache
+&lt;code&gt;
+org.eclipse.wst.xml.core.internal.catalog.XMLCatalogURIResolverExtension
+&lt;/code&gt;
+provides an URI resolver that resolves references from an XML catalog
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         Copyright (c) 2000, 2005 IBM Corporation and others.&lt;br&gt;
+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 &lt;a
+href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java
new file mode 100644
index 0000000..aa3c82b
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/ExtensibleURIResolver.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal;
+
+import java.util.Iterator;
+import java.util.List;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolver;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin;
+import org.osgi.framework.Bundle;
+
+
+/**
+ * @author csalter
+ * 
+ */
+public class ExtensibleURIResolver implements URIResolver
+{
+	private static final boolean logExceptions = false;
+
+	//protected IProject project;
+
+	//TODO... consider ctor that takes a project arg
+	//public ExtensibleURIResolver(IProject project)
+	//{
+	//	this.project = project;
+	//}
+
+	public ExtensibleURIResolver()
+	{
+	}
+
+	public String resolve(String baseLocation, String publicId, String systemId)
+	{
+		String result = systemId;
+
+		// compute the project that holds the resource
+		//
+    IFile file = computeFile(baseLocation);
+		IProject project =  file != null ? file.getProject() : null;
+
+		URIResolverExtensionRegistry resolverRegistry = URIResolverExtensionRegistry.getIntance();
+		List list = resolverRegistry.getExtensionDescriptors(project);
+
+		// get the list of applicable pre-normalized resolvers from the
+		// extension registry
+		//
+		for (Iterator i = resolverRegistry.getMatchingURIResolvers(list, URIResolverExtensionRegistry.STAGE_PRENORMALIZATION).iterator(); i.hasNext();)
+		{
+			URIResolverExtension resolver = (URIResolverExtension) i.next();
+			String tempresult = resolver.resolve(file, baseLocation, publicId, result);
+			if(tempresult != null)
+			{
+			  result = tempresult;
+			}
+		}
+
+		// normalize the uri
+		//
+		result = normalize(baseLocation, result);
+
+		// get the list of applicable post-normalized resolvers from the
+		// extension registry
+		//		
+		for (Iterator i = resolverRegistry.getMatchingURIResolvers(list, URIResolverExtensionRegistry.STAGE_POSTNORMALIZATION).iterator(); i.hasNext();)
+		{ 
+			URIResolverExtension resolver = (URIResolverExtension) i.next();
+			String tempresult = resolver.resolve(file, baseLocation, publicId, result);
+			if(tempresult != null)
+			{
+			  result = tempresult;
+			}
+		}
+
+		return result;
+	}
+    
+    public String resolvePhysicalLocation(String baseLocation, String publicId, String logicalLocation)
+    {
+      String result = logicalLocation;
+      URIResolverExtensionRegistry resolverRegistry = URIResolverExtensionRegistry.getIntance();
+      IFile file = computeFile(baseLocation);
+      
+      // compute the project that holds the resource
+      //      
+      IProject project =  file != null ? file.getProject() : null;            
+      List list = resolverRegistry.getExtensionDescriptors(project);      
+      for (Iterator i = resolverRegistry.getMatchingURIResolvers(list, URIResolverExtensionRegistry.STAGE_PHYSICAL).iterator(); i.hasNext(); )
+      {        
+        // get the list of applicable physical resolvers from the extension registry
+        //
+        while (i.hasNext())
+        {
+          URIResolverExtension resolver = (URIResolverExtension) i.next();
+          String tempresult = resolver.resolve(file, baseLocation, publicId, result);
+          if(tempresult != null)
+          {
+            result = tempresult;
+          }
+        }
+      }        
+      return result;
+    }
+    
+
+	protected String normalize(String baseLocation, String systemId)
+	{
+	  // If no systemId has been specified there is nothing to do
+	  // so return null;
+	  if(systemId == null)
+	    return null;
+		String result = systemId;
+		// normalize the URI
+		URI systemURI = URI.createURI(systemId);
+		if (systemURI.isRelative())
+		{
+			baseLocation = baseLocation.replace('\\','/');
+			URI baseURI = URI.createURI(baseLocation);
+			try
+			{
+			  result = systemURI.resolve(baseURI).toString();
+			}
+			catch (IllegalArgumentException e) {
+				Bundle bundle = URIResolverPlugin.getInstance().getBundle();
+				IStatus statusObj = null;
+				java.net.URI baseURI2 = null;
+				try {
+					baseURI2 = java.net.URI.create(baseLocation);
+				}
+				catch (IllegalArgumentException e2) {
+					if(logExceptions) {
+					    statusObj = new Status(IStatus.ERROR, bundle.getSymbolicName(), IStatus.ERROR, "Problem in creating java.net.URI in ExtensibleURIResolver:" + e2.getMessage(), e2); //$NON-NLS-1$
+					    Platform.getLog(bundle).log(statusObj);
+					}
+				}
+				try {
+					if(baseURI2 != null) {
+						java.net.URI resultURI = baseURI2.resolve(systemId);
+						result = resultURI.toString();
+					}
+				}
+				catch (IllegalArgumentException e2) {
+					if(logExceptions) {
+					    statusObj = new Status(IStatus.ERROR, bundle.getSymbolicName(), IStatus.ERROR, "Problem in resolving with java.net.URI in ExtensibleURIResolver:" + e2.getMessage(), null); //$NON-NLS-1$
+					    Platform.getLog(bundle).log(statusObj);
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+  protected IFile computeFile(String baseLocation)
+  {
+    IFile file = null;
+    if (baseLocation != null)
+    {
+      String pattern = "file:///"; //$NON-NLS-1$
+      if (baseLocation.startsWith(pattern))
+      {
+        baseLocation = baseLocation.substring(pattern.length());
+      }
+      IPath path = new Path(baseLocation);
+      file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path);
+    }
+    return file;    
+  }
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java
new file mode 100644
index 0000000..aa411a9
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URI.java
@@ -0,0 +1,2875 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A representation of a Uniform Resource Identifier (URI), as specified by
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, with certain
+ * enhancements.  A <code>URI</code> instance can be created by specifying
+ * values for its components, or by providing a single URI string, which is
+ * parsed into its components.  Static factory methods whose names begin
+ * with "create" are used for both forms of object creation.  No public or
+ * protected constructors are provided; this class can not be subclassed.
+ *
+ * <p>Like <code>String</code>, <code>URI</code> is an immutable class;
+ * a <code>URI</code> instance offers several by-value methods that return a
+ * new <code>URI</code> object based on its current state.  Most useful,
+ * a relative <code>URI</code> can be {@link #resolve(URI) resolve}d against
+ * a base absolute <code>URI</code> -- the latter typically identifies the
+ * document in which the former appears.  The inverse to this is {@link
+ * #deresolve(URI) deresolve}, which answers the question, "what relative
+ * URI will resolve, against the given base, to this absolute URI?"
+ *
+ * <p>In the <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC</a>, much
+ * attention is focused on a hierarchical naming system used widely to
+ * locate resources via common protocols such as HTTP, FTP, and Gopher, and
+ * to identify files on a local file system.  Acordingly, most of this
+ * class's functionality is for handling such URIs, which can be identified
+ * via {@link #isHierarchical isHierarchical}.
+ *
+ * <p><a name="device_explanation">
+ * The primary enhancement beyond the RFC description is an optional
+ * device component.  Instead of treating the device as just another segment
+ * in the path, it can be stored as a separate component (almost a
+ * sub-authority), with the root below it.  For example, resolving
+ * <code>/bar</code> against <code>file:///c:/foo</code> would result in
+ * <code>file:///c:/bar</code> being returned.  Also, you cannot take
+ * the parent of a device, so resolving <code>..</code> against
+ * <code>file:///c:/</code> would not yield <code>file:///</code>, as you
+ * might expect.  This feature is useful when working with file-scheme
+ * URIs, as devices do not typically occur in protocol-based ones.  A
+ * device-enabled <code>URI</code> is created by parsing a string with
+ * {@link #createURI(String) createURI}; if the first segment of the path
+ * ends with the <code>:</code> character, it is stored (including the colon)
+ * as the device, instead.  Alternately, either the {@link
+ * #createHierarchicalURI(String, String, String, String, String) no-path}
+ * or the {@link #createHierarchicalURI(String, String, String, String[],
+ * String, String) absolute-path} form of <code>createHierarchicalURI()</code>
+ * can be used, in which a non-null <code>device</code> parameter can be
+ * specified.
+ *
+ * <p><a name="archive_explanation"> 
+ * The other enhancement provides support for the almost-hierarchical
+ * form used for files within archives, such as the JAR scheme, defined
+ * for the Java Platform in the documentation for {@link
+ * java.net.JarURLConnection}. By default, this support is enabled for
+ * absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and
+ * is implemented by a hierarchical URI, whose authority includes the
+ * entire URI of the archive, up to and including the <code>!</code>
+ * character.  The URI of the archive must have no fragment.  The whole
+ * archive URI must have no device and an absolute path.  Special handling
+ * is supported for {@link #createURI creating}, {@link
+ * #validArchiveAuthority validating}, {@link #devicePath getting the path}
+ * from, and {@link #toString displaying} archive URIs. In all other
+ * operations, including {@link #resolve(URI) resolving} and {@link
+ * #deresolve(URI) deresolving}, they are handled like any ordinary URI.
+ *
+ * <p>This implementation does not impose the all of the restrictions on
+ * character validity that are specified in the RFC.  Static methods whose
+ * names begin with "valid" are used to test whether a given string is valid
+ * value for the various URI components.  Presently, these tests place no
+ * restrictions beyond what would have been required in order for {@link
+ * createURI(String) createURI} to have parsed them correctly from a single
+ * URI string.  If necessary in the future, these tests may be made more
+ * strict, to better coform to the RFC.
+ * 
+ * <p>Another group of static methods, whose names begin with "encode", use
+ * percent escaping to encode any characters that are not permitted in the
+ * various URI components. Another static method is provided to {@link
+ * #decode decode} encoded strings.  An escaped character is represented as
+ * a percent sybol (<code>%</code>), followed by two hex digits that specify
+ * the character code.  These encoding methods are more strict than the
+ * validation methods described above.  They ensure validity according to the
+ * RFC, with one exception: non-ASCII characters.
+ *
+ * <p>The RFC allows only characters that can be mapped to 7-bit US-ASCII
+ * representations.  Non-ASCII, single-byte characters can be used only via
+ * percent escaping, as described above.  This implementation uses Java's
+ * Unicode <code>char</code> and <code>String</code> representations, and
+ * makes no attempt to encode characters 0xA0 and above.  Characters in the
+ * range 0x80-0x9F are still escaped.  In this respect, this notion of a URI
+ * is actually more like an IRI (Internationalized Resource Identifier), for
+ * which an RFC is now in <href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">draft
+ * form</a>.
+ *
+ * <p>Finally, note the difference between a <code>null</code> parameter to
+ * the static factory methods and an empty string.  The former signifies the
+ * absense of a given URI component, while the latter simply makes the
+ * component blank.  This can have a significant effect when resolving.  For
+ * example, consider the following two URIs: <code>/bar</code> (with no
+ * authority) and <code>///bar</code> (with a blank authority).  Imagine
+ * resolving them against a base with an authority, such as
+ * <code>http://www.eclipse.org/</code>.  The former case will yield
+ * <code>http://www.eclipse.org/bar</code>, as the base authority will be
+ * preserved.  In the latter case, the empty authority will override the
+ * base authority, resulting in <code>http:///bar</code>!
+ */
+public final class URI
+{
+  // Common to all URI types.
+  private final int hashCode;
+  private final boolean hierarchical;
+  private final String scheme;  // null -> relative URI reference
+  private final String authority;
+  private final String fragment;
+  private URI cachedTrimFragment;
+  private String cachedToString;
+  //private final boolean iri;
+  //private URI cachedASCIIURI;
+
+  // Applicable only to a hierarchical URI.
+  private final String device;
+  private final boolean absolutePath;
+  private final String[] segments; // empty last segment -> trailing separator
+  private final String query;
+
+  // A cache of URIs, keyed by the strings from which they were created.
+  // The fragment of any URI is removed before caching it here, to minimize
+  // the size of the cache in the usual case where most URIs only differ by
+  // the fragment.
+  private static final Map uriCache = Collections.synchronizedMap(new HashMap());
+
+  // The lower-cased schemes that will be used to identify archive URIs.
+  private static final Set archiveSchemes;
+
+  // Identifies a file-type absolute URI.
+  private static final String SCHEME_FILE = "file";
+  private static final String SCHEME_JAR = "jar";
+  private static final String SCHEME_ZIP = "zip";
+  private static final String SCHEME_ARCHIVE = "archive";
+
+  // Special segment values interpreted at resolve and resolve time.
+  private static final String SEGMENT_EMPTY = "";
+  private static final String SEGMENT_SELF = ".";
+  private static final String SEGMENT_PARENT = "..";
+  private static final String[] NO_SEGMENTS = new String[0];
+
+  // Separators for parsing a URI string.
+  private static final char SCHEME_SEPARATOR = ':';
+  private static final String AUTHORITY_SEPARATOR = "//";
+  private static final char DEVICE_IDENTIFIER = ':';
+  private static final char SEGMENT_SEPARATOR = '/';
+  private static final char QUERY_SEPARATOR = '?';
+  private static final char FRAGMENT_SEPARATOR = '#';
+  private static final char USER_INFO_SEPARATOR = '@';
+  private static final char PORT_SEPARATOR = ':';
+  private static final char FILE_EXTENSION_SEPARATOR = '.';
+  private static final char ARCHIVE_IDENTIFIER = '!';
+  private static final String ARCHIVE_SEPARATOR = "!/";
+
+  // Characters to use in escaping.
+  private static final char ESCAPE = '%';
+  private static final char[] HEX_DIGITS = {
+    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+  // Some character classes, as defined in RFC 2396's BNF for URI.
+  // These are 128-bit bitmasks, stored as two longs, where the Nth bit is set
+  // iff the ASCII character with value N is included in the set.  These are
+  // created with the highBitmask() and lowBitmask() methods defined below,
+  // and a character is tested against them using matches().
+  //
+  private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z');
+  private static final long ALPHA_LO = lowBitmask('a', 'z')  | lowBitmask('A', 'Z');
+  private static final long DIGIT_HI = highBitmask('0', '9');
+  private static final long DIGIT_LO = lowBitmask('0', '9');
+  private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI;
+  private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO;
+  private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f');
+  private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F')  | lowBitmask('a', 'f');
+  private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()"); 
+  private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()");
+  private static final long RESERVED_HI = highBitmask(";/?:@&=+$,");
+  private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,");
+  private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI;  // | ucschar | escaped
+  private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO;
+
+  // Additional useful character classes, including characters valid in certain
+  // URI components and separators used in parsing them out of a string. 
+  //
+  private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,");  // | ucschar | escaped
+  private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,");
+  private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/');  // | ucschar | escaped
+  private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/');
+//  private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-.");
+//  private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-.");
+  private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#");
+  private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#");
+  private static final long SEGMENT_END_HI = highBitmask("/?#");
+  private static final long SEGMENT_END_LO = lowBitmask("/?#");
+
+  // Static initializer for archiveSchemes.
+  static
+  {
+    Set set = new HashSet();
+    set.add(SCHEME_JAR);
+    set.add(SCHEME_ZIP);
+    set.add(SCHEME_ARCHIVE);
+    
+    
+    archiveSchemes = Collections.unmodifiableSet(set);
+  }
+
+  // Returns the lower half bitmask for the given ASCII character.
+  private static long lowBitmask(char c)
+  {
+    return c < 64 ? 1L << c : 0L;
+  }
+
+  // Returns the upper half bitmask for the given ACSII character.
+  private static long highBitmask(char c)
+  {
+    return c >= 64 && c < 128 ? 1L << (c - 64) : 0L;
+  }
+
+  // Returns the lower half bitmask for all ASCII characters between the two
+  // given characters, inclusive.
+  private static long lowBitmask(char from, char to)
+  {
+    long result = 0L;
+    if (from < 64 && from <= to)
+    {
+      to = to < 64 ? to : 63;
+      for (char c = from; c <= to; c++)
+      {
+        result |= (1L << c);
+      }
+    }
+    return result;
+  }
+
+  // Returns the upper half bitmask for all AsCII characters between the two
+  // given characters, inclusive.
+  private static long highBitmask(char from, char to)
+  {
+    return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64));
+  }
+
+  // Returns the lower half bitmask for all the ASCII characters in the given
+  // string.
+  private static long lowBitmask(String chars)
+  {
+    long result = 0L;
+    for (int i = 0, len = chars.length(); i < len; i++)
+    {
+      char c = chars.charAt(i);
+      if (c < 64) result |= (1L << c);
+    }
+    return result;
+  }
+
+  // Returns the upper half bitmask for all the ASCII characters in the given
+  // string.
+  private static long highBitmask(String chars)
+  {
+    long result = 0L;
+    for (int i = 0, len = chars.length(); i < len; i++)
+    {
+      char c = chars.charAt(i);
+      if (c >= 64 && c < 128) result |= (1L << (c - 64));
+    }
+    return result;
+  }
+
+  // Returns whether the given character is in the set specified by the given
+  // bitmask.
+  private static boolean matches(char c, long highBitmask, long lowBitmask)
+  {
+    if (c >= 128) return false;
+    return c < 64 ?
+      ((1L << c) & lowBitmask) != 0 :
+      ((1L << (c - 64)) & highBitmask) != 0;
+  }
+
+  // Debugging method: converts the given long to a string of binary digits.
+/*
+  private static String toBits(long l)
+  {
+    StringBuffer result = new StringBuffer();
+    for (int i = 0; i < 64; i++)
+    {
+      boolean b = (l & 1L) != 0;
+      result.insert(0, b ? '1' : '0');
+      l >>= 1;
+    }
+    return result.toString();
+  }
+*/
+
+  /**
+   * Static factory method for a generic, non-hierarchical URI.  There is no
+   * concept of a relative non-hierarchical URI; such an object cannot be
+   * created.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
+   * null, if <code>scheme</code> is an <a href="#archive_explanation">archive
+   * URI</a> scheme, or if <code>scheme</code>, <code>opaquePart</code>, or
+   * <code>fragment</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link
+   * #validFragment validFragment}, respectively.
+   */
+  public static URI createGenericURI(String scheme, String opaquePart,
+                                     String fragment)
+  {
+    if (scheme == null)
+    {
+      throw new IllegalArgumentException("relative non-hierarchical URI");
+    }
+
+    if (isArchiveScheme(scheme))
+    {
+      throw new IllegalArgumentException("non-hierarchical archive URI");
+    }
+
+    validateURI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
+    return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment);
+  }
+
+  /**
+   * Static factory method for a hierarchical URI with no path.  The
+   * URI will be relative if <code>scheme</code> is non-null, and absolute
+   * otherwise.  An absolute URI with no path requires a non-null
+   * <code>authority</code> and/or <code>device</code>.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
+   * non-null while <code>authority</code> and <code>device</code> are null,
+   * if <code>scheme</code> is an <a href="#archive_explanation">archive
+   * URI</a> scheme, or if <code>scheme</code>, <code>authority</code>,
+   * <code>device</code>, <code>query</code>, or <code>fragment</code> is not
+   * valid according to {@link #validScheme validSheme}, {@link
+   * #validAuthority validAuthority}, {@link #validDevice validDevice},
+   * {@link #validQuery validQuery}, or {@link #validFragment validFragment},
+   * respectively.
+   */
+  public static URI createHierarchicalURI(String scheme, String authority,
+                                          String device, String query,
+                                          String fragment)
+  {
+    if (scheme != null && authority == null && device == null)
+    {
+      throw new IllegalArgumentException(
+        "absolute hierarchical URI without authority, device, path");
+    }
+
+    if (isArchiveScheme(scheme))
+    {
+      throw new IllegalArgumentException("archive URI with no path");
+    }
+
+    validateURI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
+    return new URI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment);
+  }
+
+  /**
+   * Static factory method for a hierarchical URI with absolute path.
+   * The URI will be relative if <code>scheme</code> is non-null, and
+   * absolute otherwise. 
+   *
+   * @param segments an array of non-null strings, each representing one
+   * segment of the path.  As an absolute path, it is automatically
+   * preceeded by a <code>/</code> separator.  If desired, a trailing
+   * separator should be represented by an empty-string segment as the last
+   * element of the array. 
+   *
+   * @exception java.lang.IllegalArgumentException if <code>scheme</code> is
+   * an <a href="#archive_explanation">archive URI</a> scheme and 
+   * <code>device</code> is non-null, or if <code>scheme</code>,
+   * <code>authority</code>, <code>device</code>, <code>segments</code>,
+   * <code>query</code>, or <code>fragment</code> is not valid according to
+   * {@link #validScheme validScheme}, {@link #validAuthority validAuthority}
+   * or {@link #validArchiveAuthority validArchiveAuthority}, {@link
+   * #validDevice validDevice}, {@link #validSegments validSegments}, {@link
+   * #validQuery validQuery}, or {@link #validFragment validFragment}, as
+   * appropriate.
+   */
+  public static URI createHierarchicalURI(String scheme, String authority,
+                                          String device, String[] segments,
+                                          String query, String fragment)
+  {
+    if (isArchiveScheme(scheme) && device != null)
+    {
+      throw new IllegalArgumentException("archive URI with device");
+    }
+
+    segments = fix(segments);
+    validateURI(true, scheme, authority, device, true, segments, query, fragment);
+    return new URI(true, scheme, authority, device, true, segments, query, fragment);
+  }
+
+  /**
+   * Static factory method for a relative hierarchical URI with relative
+   * path.
+   *
+   * @param segments an array of non-null strings, each representing one
+   * segment of the path.  A trailing separator is represented by an
+   * empty-string segment at the end of the array.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>segments</code>,
+   * <code>query</code>, or <code>fragment</code> is not valid according to 
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}, respectively.
+   */
+  public static URI createHierarchicalURI(String[] segments, String query,
+                                          String fragment)
+  {
+    segments = fix(segments);
+    validateURI(true, null, null, null, false, segments, query, fragment);
+    return new URI(true, null, null, null, false, segments, query, fragment);
+  }
+
+  // Converts null to length-zero array, and clones array to ensure
+  // immutability.
+  private static String[] fix(String[] segments)
+  {
+    return segments == null ? NO_SEGMENTS : (String[])segments.clone();
+  }
+  
+  /**
+   * Static factory method based on parsing a URI string, with 
+   * <a href="#device_explanation">explicit device support</a> and handling
+   * for <a href="#archive_explanation">archive URIs</a> enabled. The
+   * specified string is parsed as described in <a
+   * href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>, and an
+   * appropriate <code>URI</code> is created and returned.  Note that
+   * validity testing is not as strict as in the RFC; essentially, only
+   * separator characters are considered.  So, for example, non-Latin
+   * alphabet characters appearing in the scheme would not be considered an
+   * error.
+   *
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
+   */
+  public static URI createURI(String uri)
+  {
+    return createURIWithCache(uri); 
+  }
+
+  /**
+   * Static factory method that encodes and parses the given URI string.
+   * Appropriate encoding is performed for each component of the URI.
+   * If more than one <code>#</code> is in the string, the last one is
+   * assumed to be the fragment's separator, and any others are encoded.
+   *  
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   *
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
+   */
+  public static URI createURI(String uri, boolean ignoreEscaped)
+  {
+    return createURIWithCache(encodeURI(uri, ignoreEscaped));
+  }
+
+  /**
+   * Static factory method based on parsing a URI string, with 
+   * <a href="#device_explanation">explicit device support</a> enabled.  
+   * Note that validity testing is not a strict as in the RFC; essentially,
+   * only separator characters are considered.  So, for example, non-Latin
+   * alphabet characters appearing in the scheme would not be considered an
+   * error.
+   *
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from <code>uri</code> is not valid according to {@link #validScheme
+   * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link
+   * #validAuthority validAuthority}, {@link #validArchiveAuthority
+   * validArchiveAuthority}, {@link #validDevice validDevice}, {@link
+   * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link
+   * #validFragment validFragment}, as appropriate.
+   *
+   * @deprecated Use {@link #createURI createURI}, which now has explicit
+   * device support enabled. The two methods now operate identically.
+   */
+  public static URI createDeviceURI(String uri)
+  {
+    return createURIWithCache(uri);
+  }
+
+  // Uses a cache to speed up creation of a URI from a string.  The cache
+  // is consulted to see if the URI, less any fragment, has already been
+  // created.  If needed, the fragment is re-appended to the cached URI,
+  // which is considerably more efficient than creating the whole URI from
+  // scratch.  If the URI wasn't found in the cache, it is created using
+  // parseIntoURI() and then cached.  This method should always be used
+  // by string-parsing factory methods, instead of parseIntoURI() directly.
+  /**
+   * This method was included in the public API by mistake.
+   * 
+   * @deprecated Please use {@link #createURI createURI} instead.
+   */
+  public static URI createURIWithCache(String uri)
+  {
+    int i = uri.indexOf(FRAGMENT_SEPARATOR);
+    String base = i == -1 ? uri : uri.substring(0, i);
+    String fragment = i == -1 ? null : uri.substring(i + 1);
+
+    URI result = (URI)uriCache.get(base);
+
+    if (result == null)
+    {
+      result = parseIntoURI(base);
+      uriCache.put(base, result);
+    }
+
+    if (fragment != null)
+    {
+      result = result.appendFragment(fragment);
+    }
+    return result;
+  }
+
+  // String-parsing implementation.
+  private static URI parseIntoURI(String uri)
+  {
+    boolean hierarchical = true;
+    String scheme = null;
+    String authority = null;
+    String device = null;
+    boolean absolutePath = false;
+    String[] segments = NO_SEGMENTS;
+    String query = null;
+    String fragment = null;
+
+    int i = 0;
+    int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);
+
+    if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR)
+    {
+      scheme = uri.substring(i, j);
+      i = j + 1;
+    }
+
+    boolean archiveScheme = isArchiveScheme(scheme);
+    if (archiveScheme)
+    {
+      j = uri.lastIndexOf(ARCHIVE_SEPARATOR);
+      if (j == -1)
+      {
+        throw new IllegalArgumentException("no archive separator");
+      }
+      hierarchical = true;
+      authority = uri.substring(i, ++j);
+      i = j;
+    }
+    else if (uri.startsWith(AUTHORITY_SEPARATOR, i))
+    {
+      i += AUTHORITY_SEPARATOR.length();
+      j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
+      authority = uri.substring(i, j);
+      i = j;
+    }
+    else if (scheme != null &&
+             (i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR))
+    {
+      hierarchical = false;
+      j = uri.indexOf(FRAGMENT_SEPARATOR, i);
+      if (j == -1) j = uri.length();
+      authority = uri.substring(i, j);
+      i = j;
+    }
+
+    if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
+    {
+      j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO);
+      String s = uri.substring(i + 1, j);
+      
+      if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER)
+      {
+        device = s;
+        i = j;
+      }
+    }
+
+    if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
+    {
+      i++;
+      absolutePath = true;
+    }
+
+    if (segmentsRemain(uri, i))
+    {
+      List segmentList = new ArrayList();
+
+      while (segmentsRemain(uri, i))
+      {
+        j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO);
+        segmentList.add(uri.substring(i, j));
+        i = j;
+
+        if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR)
+        {
+          if (!segmentsRemain(uri, ++i)) segmentList.add(SEGMENT_EMPTY);
+        }
+      }
+      segments = new String[segmentList.size()];
+      segmentList.toArray(segments);
+    }
+
+    if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR)
+    {
+      j = uri.indexOf(FRAGMENT_SEPARATOR, ++i);
+      if (j == -1) j = uri.length();
+      query = uri.substring(i, j);
+      i = j;
+    }
+
+    if (i < uri.length()) // && uri.charAt(i) == FRAGMENT_SEPARATOR (implied)
+    {
+      fragment = uri.substring(++i);
+    }
+
+    validateURI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
+    return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment);
+  }
+
+  // Checks whether the string contains any more segments after the one that
+  // starts at position i.
+  private static boolean segmentsRemain(String uri, int i)
+  {
+    return i < uri.length() && uri.charAt(i) != QUERY_SEPARATOR &&
+      uri.charAt(i) != FRAGMENT_SEPARATOR;
+  }
+
+  // Finds the next occurance of one of the characters in the set represented
+  // by the given bitmask in the given string, beginning at index i. The index
+  // of the first found character, or s.length() if there is none, is
+  // returned.  Before searching, i is limited to the range [0, s.length()].
+  //
+  private static int find(String s, int i, long highBitmask, long lowBitmask)
+  {
+    int len = s.length();
+    if (i >= len) return len;
+
+    for (i = i > 0 ? i : 0; i < len; i++)
+    {
+      if (matches(s.charAt(i), highBitmask, lowBitmask)) break;
+    }
+    return i;
+  }
+
+  /**
+   * Static factory method based on parsing a {@link java.io.File} path
+   * string.  The <code>pathName</code> is converted into an appropriate
+   * form, as follows: platform specific path separators are converted to
+   * <code>/<code>; the path is encoded; and a "file" scheme and, if missing,
+   * a leading <code>/</code>, are added to an absolute path.  The result
+   * is then parsed using {@link #createURI(String) createURI}.
+   *
+   * <p>The encoding step escapes all spaces, <code>#</code> characters, and
+   * other characters disallowed in URIs, as well as <code>?</code>, which
+   * would delimit a path from a query.  Decoding is automatically performed
+   * by {@link #toFileString toFileString}, and can be applied to the values
+   * returned by other accessors by via the static {@link #decode(String)
+   * decode} method.
+   *
+   * <p>A relative path with a specified device (something like
+   * <code>C:myfile.txt</code>) cannot be expressed as a valid URI.
+   * 
+   * @exception java.lang.IllegalArgumentException if <code>pathName</code>
+   * specifies a device and a relative path, or if any component of the path
+   * is not valid according to {@link #validAuthority validAuthority}, {@link
+   * #validDevice validDevice}, or {@link #validSegments validSegments},
+   * {@link #validQuery validQuery}, or {@link #validFragment validFragment}.
+   */
+  public static URI createFileURI(String pathName)
+  {
+    File file = new File(pathName);
+    String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName;
+    uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false);
+    if (file.isAbsolute())
+    {
+      URI result = createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri);
+      return result;
+    }
+    else
+    {
+      URI result = createURI(uri);
+      if (result.scheme() != null)
+      {
+        throw new IllegalArgumentException("invalid relative pathName: " + pathName);
+      }
+      return result;
+    }
+  }
+
+  /**
+   * Static factory method based on parsing a platform-relative path string.
+   *
+   * <p>The <code>pathName</code> must be of the form:
+   * <pre>
+   *   /project-name/path</pre>
+   *
+   * <p>Platform-specific path separators will be converterted to slashes.
+   * If not included, the leading path separator will be added.  The
+   * result will be of this form, which is parsed using {@link #createURI
+   * createURI}:
+   * <pre>
+   *   platform:/resource/project-name/path</pre>
+   *
+   * 
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from the path is not valid according to {@link #validDevice validDevice},
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}.
+   *
+   * @see org.eclipse.core.runtime.Platform#resolve
+   * @see #createPlatformResourceURI(String, boolean)
+   */
+  public static URI createPlatformResourceURI(String pathName)
+  {
+    return createPlatformResourceURI(pathName, false);
+  }
+
+  /**
+   * Static factory method based on parsing a platform-relative path string,
+   * with an option to encode the created URI.
+   *
+   * <p>The <code>pathName</code> must be of the form:
+   * <pre>
+   *   /project-name/path</pre>
+   *
+   * <p>Platform-specific path separators will be converterted to slashes.
+   * If not included, the leading path separator will be added.  The
+   * result will be of this form, which is parsed using {@link #createURI
+   * createURI}:
+   * <pre>
+   *   platform:/resource/project-name/path</pre>
+   *
+   * <p>This scheme supports relocatable projects in Eclipse and in
+   * stand-alone .
+   *
+   * <p>Depending on the <code>encode</code> argument, the path may be
+   * automatically encoded to escape all spaces, <code>#</code> characters,
+   * and other characters disallowed in URIs, as well as <code>?</code>,
+   * which would delimit a path from a query.  Decoding can be performed with
+   * the static {@link #decode(String) decode} method.
+   * 
+   * @exception java.lang.IllegalArgumentException if any component parsed
+   * from the path is not valid according to {@link #validDevice validDevice},
+   * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or
+   * {@link #validFragment validFragment}.
+   *
+   * @see org.eclipse.core.runtime.Platform#resolve
+   */
+  public static URI createPlatformResourceURI(String pathName, boolean encode)
+  {
+    if (File.separatorChar != SEGMENT_SEPARATOR)
+    {
+      pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR);
+    }
+
+    if (encode)
+    {
+      pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false);
+    }
+    URI result = createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? "platform:/resource" : "platform:/resource/") + pathName);
+    return result;
+  }
+  
+  // Private constructor for use of static factory methods.
+  private URI(boolean hierarchical, String scheme, String authority,
+              String device, boolean absolutePath, String[] segments,
+              String query, String fragment)
+  {
+    int hashCode = 0;
+    //boolean iri = false;
+
+    if (hierarchical)
+    {
+      ++hashCode;
+    }
+    if (absolutePath)
+    {
+      hashCode += 2;
+    }
+    if (scheme != null)
+    {
+      hashCode ^= scheme.toLowerCase().hashCode();
+    }
+    if (authority != null)
+    {
+      hashCode ^= authority.hashCode();
+      //iri = iri || containsNonASCII(authority);
+    }
+    if (device != null)
+    {
+      hashCode ^= device.hashCode();
+      //iri = iri || containsNonASCII(device);
+    }
+    if (query != null)
+    {
+      hashCode ^= query.hashCode();
+      //iri = iri || containsNonASCII(query);
+    }
+    if (fragment != null)
+    {
+      hashCode ^= fragment.hashCode();
+      //iri = iri || containsNonASCII(fragment);
+    }
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      hashCode ^= segments[i].hashCode();
+      //iri = iri || containsNonASCII(segments[i]);
+    }
+
+    this.hashCode = hashCode;
+    //this.iri = iri;
+    this.hierarchical = hierarchical;
+    this.scheme = scheme == null ? null : scheme.intern();
+    this.authority = authority;
+    this.device = device;
+    this.absolutePath = absolutePath;
+    this.segments = segments;
+    this.query = query;
+    this.fragment = fragment;
+  }
+  
+  // Validates all of the URI components.  Factory methods should call this
+  // before using the constructor, though they must ensure that the
+  // inter-component requirements described in their own Javadocs are all
+  // satisfied, themselves.  If a new URI is being constructed out of
+  // an existing URI, this need not be called.  Instead, just the new
+  // components may be validated individually.
+  private static void validateURI(boolean hierarchical, String scheme,
+                                    String authority, String device,
+                                    boolean absolutePath, String[] segments,
+                                    String query, String fragment)
+  {
+    if (!validScheme(scheme))
+    {
+      throw new IllegalArgumentException("invalid scheme: " + scheme);
+    }
+    if (!hierarchical && !validOpaquePart(authority))
+    {
+      throw new IllegalArgumentException("invalid opaquePart: " + authority);
+    }
+    if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority))
+    {
+      throw new IllegalArgumentException("invalid authority: " + authority);
+    }
+    if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority))
+    {
+      throw new IllegalArgumentException("invalid authority: " + authority);
+    }
+    if (!validDevice(device))
+    {
+      throw new IllegalArgumentException("invalid device: " + device);
+    }
+    if (!validSegments(segments))
+    {
+      String s = segments == null ? "invalid segments: " + segments :
+        "invalid segment: " + firstInvalidSegment(segments);
+      throw new IllegalArgumentException(s);
+    }
+    if (!validQuery(query))
+    {
+      throw new IllegalArgumentException("invalid query: " + query);
+    }
+    if (!validFragment(fragment))
+    {
+      throw new IllegalArgumentException("invalid fragment: " + fragment);
+    }
+  }
+
+  // Alternate, stricter implementations of the following validation methods
+  // are provided, commented out, for possible future use...
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the scheme component of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid scheme may be null or contain any characters except for the
+   * following: <code>: / ? #</code>
+   */
+  public static boolean validScheme(String value)
+  {
+    return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO);  
+
+  // <p>A valid scheme may be null, or consist of a single letter followed
+  // by any number of letters, numbers, and the following characters:
+  // <code>+ - .</code>
+
+    //if (value == null) return true;
+    //return value.length() != 0 &&
+    //  matches(value.charAt(0), ALPHA_HI, ALPHA_LO) &&
+    //  validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the opaque part component of a URI; <code>false</code>
+   * otherwise.
+   *
+   * <p>A valid opaque part must be non-null, non-empty, and not contain the
+   * <code>#</code> character.  In addition, its first character must not be
+   * <code>/</code>
+   */
+  public static boolean validOpaquePart(String value)
+  {
+    return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 &&
+    value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR;
+
+  // <p>A valid opaque part must be non-null and non-empty. It may contain
+  // any allowed URI characters, but its first character may not be
+  // <code>/</code> 
+
+    //return value != null && value.length() != 0 &&
+    //  value.charAt(0) != SEGMENT_SEPARATOR &&
+    //  validate(value, URIC_HI, URIC_LO, true, true);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the authority component of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid authority may be null or contain any characters except for
+   * the following: <code>/ ? #</code>
+   */
+  public static boolean validAuthority(String value)
+  {
+    return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // A valid authority may be null or contain any allowed URI characters except
+  // for the following: <code>/ ?</code>
+
+    //return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the authority component of an <a
+   * href="#archive_explanation">archive URI</a>; <code>false</code>
+   * otherwise.
+   *
+   * <p>To be valid, the authority, itself, must be a URI with no fragment,
+   * followed by the character <code>!</code>.
+   */
+  public static boolean validArchiveAuthority(String value)
+  {
+    if (value != null && value.length() > 0 &&
+        value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER)
+    {
+      try
+      {
+        URI archiveURI = createURI(value.substring(0, value.length() - 1));
+        return !archiveURI.hasFragment();
+      }
+      catch (IllegalArgumentException e)
+      {
+      }
+    }
+    return false;
+  }
+
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the device component of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid device may be null or non-empty, containing any characters
+   * except for the following: <code>/ ? #</code>  In addition, its last
+   * character must be <code>:</code>
+   */
+  public static boolean validDevice(String value)
+  {    
+    if (value == null) return true;
+    int len = value.length();
+    return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER &&
+      !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // <p>A valid device may be null or non-empty, containing any allowed URI
+  // characters except for the following: <code>/ ?</code>  In addition, its
+  // last character must be <code>:</code>
+
+    //if (value == null) return true;
+    //int len = value.length();
+    //return len > 0 && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true) &&
+    //  value.charAt(len - 1) == DEVICE_IDENTIFIER;
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * a valid path segment of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid path segment must be non-null and not contain any of the
+   * following characters: <code>/ ? #</code>
+   */
+  public static boolean validSegment(String value)
+  {
+    return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO);
+
+  // <p>A valid path segment must be non-null and may contain any allowed URI
+  // characters except for the following: <code>/ ?</code> 
+
+    //return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * a valid path segment array of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid path segment array must be non-null and contain only path
+   * segements that are valid according to {@link #validSegment validSegment}.
+   */
+  public static boolean validSegments(String[] value)
+  {
+    if (value == null) return false;
+    for (int i = 0, len = value.length; i < len; i++)
+    {
+      if (!validSegment(value[i])) return false;
+    }
+    return true;
+  }
+
+  // Returns null if the specicied value is null or would be a valid path
+  // segment array of a URI; otherwise, the value of the first invalid
+  // segment. 
+  private static String firstInvalidSegment(String[] value)
+  {
+    if (value == null) return null;
+    for (int i = 0, len = value.length; i < len; i++)
+    {
+      if (!validSegment(value[i])) return value[i];
+    }
+    return null;
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the query component of a URI; <code>false</code> otherwise.
+   *
+   * <p>A valid query may be null or contain any characters except for
+   * <code>#</code>
+   */
+  public static boolean validQuery(String value)
+  {
+    return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1;
+
+  // <p>A valid query may be null or contain any allowed URI characters.
+
+    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
+}
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the fragment component of a URI; <code>false</code> otherwise.
+   *
+   * <p>A fragment is taken to be unconditionally valid.
+   */
+  public static boolean validFragment(String value)
+  {
+    return true;
+
+  // <p>A valid fragment may be null or contain any allowed URI characters.
+
+    //return value == null || validate(value, URIC_HI, URIC_LO, true, true);
+  }
+
+  // Searches the specified string for any characters in the set represnted
+  // by the 128-bit bitmask.  Returns true if any occur, or false otherwise.
+  private static boolean contains(String s, long highBitmask, long lowBitmask)
+  {
+    for (int i = 0, len = s.length(); i < len; i++)
+    {
+      if (matches(s.charAt(i), highBitmask, lowBitmask)) return true;
+    }
+    return false;
+  }
+
+  // Tests the non-null string value to see if it contains only ASCII
+  // characters in the set represented by the specified 128-bit bitmask,
+  // as well as, optionally, non-ASCII characters 0xA0 and above, and,
+  // also optionally, escape sequences of % followed by two hex digits.
+  // This method is used for the new, strict URI validation that is not
+  // not currently in place.
+/*
+  private static boolean validate(String value, long highBitmask, long lowBitmask,
+                                     boolean allowNonASCII, boolean allowEscaped)
+  {
+    for (int i = 0, len = value.length(); i < len; i++)
+    { 
+      char c = value.charAt(i);
+
+      if (matches(c, highBitmask, lowBitmask)) continue;
+      if (allowNonASCII && c >= 160) continue;
+      if (allowEscaped && isEscaped(value, i))
+      {
+        i += 2;
+        continue;
+      }
+      return false;
+    }
+    return true;
+  }
+*/
+
+  /**
+   * Returns <code>true</code> if this is a relative URI, or
+   * <code>false</code> if it is an absolute URI.
+   */
+  public boolean isRelative()
+  {
+    return scheme == null;
+  }
+
+  /**
+   * Returns <code>true</code> if this a a hierarchical URI, or
+   * <code>false</code> if it is of the generic form.
+   */
+  public boolean isHierarchical()
+  {
+    return hierarchical;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarcical URI with an authority
+   * component; <code>false</code> otherwise. 
+   */
+  public boolean hasAuthority()
+  {
+    return hierarchical && authority != null;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a non-hierarchical URI with an
+   * opaque part component; <code>false</code> otherwise.
+   */
+  public boolean hasOpaquePart()
+  {
+    // note: hierarchical -> authority != null
+    return !hierarchical;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with a device
+   * component; <code>false</code> otherwise.
+   */
+  public boolean hasDevice()
+  {
+    // note: device != null -> hierarchical
+    return device != null;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with an
+   * absolute or relative path; <code>false</code> otherwise.
+   */
+  public boolean hasPath()
+  {
+    // note: (absolutePath || authority == null) -> hierarchical
+    // (authority == null && device == null && !absolutePath) -> scheme == null
+    return absolutePath || (authority == null && device == null);
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with an
+   * absolute path, or <code>false</code> if it is non-hierarchical, has no
+   * path, or has a relative path.
+   */
+  public boolean hasAbsolutePath()
+  {
+    // note: absolutePath -> hierarchical
+    return absolutePath;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with a relative
+   * path, or <code>false</code> if it is non-hierarchical, has no path, or
+   * has an absolute path.
+   */
+  public boolean hasRelativePath()
+  {
+    // note: authority == null -> hierarchical
+    // (authority == null && device == null && !absolutePath) -> scheme == null
+    return authority == null && device == null && !absolutePath;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with an empty
+   * relative path; <code>false</code> otherwise.  
+   *
+   * <p>Note that <code>!hasEmpty()</code> does <em>not</em> imply that this
+   * URI has any path segments; however, <code>hasRelativePath &&
+   * !hasEmptyPath()</code> does.
+   */
+  public boolean hasEmptyPath()
+  {
+    // note: authority == null -> hierarchical
+    // (authority == null && device == null && !absolutePath) -> scheme == null
+    return authority == null && device == null && !absolutePath &&
+      segments.length == 0;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI with a query
+   * component; <code>false</code> otherwise.
+   */
+  public boolean hasQuery()
+  {
+    // note: query != null -> hierarchical
+    return query != null;
+  }
+
+  /**
+   * Returns <code>true</code> if this URI has a fragment component;
+   * <code>false</code> otherwise.
+   */
+  public boolean hasFragment()
+  {
+    return fragment != null;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a current document reference; that
+   * is, if it is a relative hierarchical URI with no authority, device or
+   * query components, and no path segments; <code>false</code> is returned
+   * otherwise.
+   */
+  public boolean isCurrentDocumentReference()
+  {
+    // note: authority == null -> hierarchical
+    // (authority == null && device == null && !absolutePath) -> scheme == null
+    return authority == null && device == null && !absolutePath &&
+      segments.length == 0 && query == null;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a {@link
+   * #isCurrentDocumentReference() current document reference} with no
+   * fragment component; <code>false</code> otherwise.
+   *
+   * @see #isCurrentDocumentReference()
+   */
+  public boolean isEmpty()
+  {
+    // note: authority == null -> hierarchical
+    // (authority == null && device == null && !absolutePath) -> scheme == null
+    return authority == null && device == null && !absolutePath &&
+      segments.length == 0 && query == null && fragment == null;
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI that may refer
+   * directly to a locally accessible file.  This is considered to be the
+   * case for a file-scheme absolute URI, or for a relative URI with no query;
+   * <code>false</code> is returned otherwise.
+   */
+  public boolean isFile()
+  {
+    return isHierarchical() &&
+      ((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme));
+  }
+
+  // Returns true if this is an archive URI.  If so, we should expect that
+  // it is also hierarchical, with an authority (consisting of an absolute
+  // URI followed by "!"), no device, and an absolute path.
+  private boolean isArchive()
+  {
+    return isArchiveScheme(scheme);
+  }
+
+  /**
+   * Returns <code>true</code> if the specified <code>value</code> would be
+   * valid as the scheme of an <a
+   * href="#archive_explanation">archive URI</a>; <code>false</code>
+   * otherwise.
+   */
+  public static boolean isArchiveScheme(String value)
+  {
+    return value != null && archiveSchemes.contains(value.toLowerCase());
+  }
+  
+  /**
+   * Returns the hash code.
+   */
+  public int hashCode()
+  {
+    return hashCode;
+  }
+
+  /**
+   * Returns <code>true</code> if <code>obj</code> is an instance of
+   * <code>URI</code> equal to this one; <code>false</code> otherwise.
+   *
+   * <p>Equality is determined strictly by comparing components, not by
+   * attempting to interpret what resource is being identified.  The
+   * comparison of schemes is case-insensitive.
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj) return true;
+    if (!(obj instanceof URI)) return false;
+    URI uri = (URI) obj;
+
+    return hashCode == uri.hashCode() &&
+      hierarchical == uri.isHierarchical() &&
+      absolutePath == uri.hasAbsolutePath() &&
+      equals(scheme, uri.scheme(), true) &&
+      equals(authority, hierarchical ? uri.authority() : uri.opaquePart()) &&
+      equals(device, uri.device()) &&
+      equals(query, uri.query()) && 
+      equals(fragment, uri.fragment()) &&
+      segmentsEqual(uri);
+  }
+
+  // Tests whether this URI's path segment array is equal to that of the
+  // given uri.
+  private boolean segmentsEqual(URI uri)
+  {
+    if (segments.length != uri.segmentCount()) return false;
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      if (!segments[i].equals(uri.segment(i))) return false;
+    }
+    return true;
+  }
+
+  // Tests two objects for equality, tolerating nulls; null is considered
+  // to be a valid value that is only equal to itself.
+  private static boolean equals(Object o1, Object o2)
+  {
+    return o1 == null ? o2 == null : o1.equals(o2);
+  }
+
+  // Tests two strings for equality, tolerating nulls and optionally
+  // ignoring case.
+  private static boolean equals(String s1, String s2, boolean ignoreCase)
+  {
+    return s1 == null ? s2 == null :
+      ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2);
+  }
+
+  /**
+   * If this is an absolute URI, returns the scheme component;
+   * <code>null</code> otherwise.
+   */
+  public String scheme()
+  {
+    return scheme;
+  }
+
+  /**
+   * If this is a non-hierarchical URI, returns the opaque part component;
+   * <code>null</code> otherwise.
+   */
+  public String opaquePart()
+  {
+    return isHierarchical() ? null : authority;
+  }
+
+  /**
+   * If this is a hierarchical URI with an authority component, returns it;
+   * <code>null</code> otherwise.
+   */
+  public String authority()
+  {
+    return isHierarchical() ? authority : null;
+  }
+
+  /**
+   * If this is a hierarchical URI with an authority component that has a
+   * user info portion, returns it; <code>null</code> otherwise.
+   */
+  public String userInfo()
+  { 
+    if (!hasAuthority()) return null;
+   
+    int i = authority.indexOf(USER_INFO_SEPARATOR);
+    return i < 0 ? null : authority.substring(0, i);
+  }
+
+  /**
+   * If this is a hierarchical URI with an authority component that has a
+   * host portion, returns it; <code>null</code> otherwise.
+   */
+  public String host()
+  {
+    if (!hasAuthority()) return null;
+    
+    int i = authority.indexOf(USER_INFO_SEPARATOR);
+    int j = authority.indexOf(PORT_SEPARATOR);
+    return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j);
+  }
+
+  /**
+   * If this is a hierarchical URI with an authority component that has a
+   * port portion, returns it; <code>null</code> otherwise.
+   */
+  public String port()
+  {
+    if (!hasAuthority()) return null;
+
+    int i = authority.indexOf(PORT_SEPARATOR);
+    return i < 0 ? null : authority.substring(i + 1);
+  }
+
+  /**
+   * If this is a hierarchical URI with a device component, returns it;
+   * <code>null</code> otherwise.
+   */
+  public String device()
+  {
+    return device;
+  }
+
+  /**
+   * If this is a hierarchical URI with a path, returns an array containing
+   * the segments of the path; an empty array otherwise.  The leading
+   * separator in an absolute path is not represented in this array, but a
+   * trailing separator is represented by an empty-string segment as the
+   * final element.
+   */
+  public String[] segments()
+  {
+    return (String[])segments.clone();
+  }
+
+  /**
+   * Returns an unmodifiable list containing the same segments as the array
+   * returned by {@link #segments segments}.
+   */
+  public List segmentsList()
+  {
+    return Collections.unmodifiableList(Arrays.asList(segments));
+  }
+
+  /**
+   * Returns the number of elements in the segment array that would be
+   * returned by {@link #segments segments}.
+   */
+  public int segmentCount()
+  {
+    return segments.length;
+  }
+
+  /**
+   * Provides fast, indexed access to individual segments in the path
+   * segment array.
+   *
+   * @exception java.lang.IndexOutOfBoundsException if <code>i < 0</code> or
+   * <code>i >= segmentCount()</code>.
+   */
+  public String segment(int i)
+  {
+    return segments[i];
+  }
+
+  /**
+   * Returns the last segment in the segment array, or <code>null</code>.
+   */
+  public String lastSegment()
+  {
+    int len = segments.length;
+    if (len == 0) return null;
+    return segments[len - 1];
+  }
+
+  /**
+   * If this is a hierarchical URI with a path, returns a string
+   * representation of the path; <code>null</code> otherwise.  The path
+   * consists of a leading segment separator character (a slash), if the
+   * path is absolute, followed by the slash-separated path segments.  If
+   * this URI has a separate <a href="#device_explanation">device
+   * component</a>, it is <em>not</em> included in the path.
+   */
+  public String path()
+  {
+    if (!hasPath()) return null;
+
+    StringBuffer result = new StringBuffer();
+    if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      if (i != 0) result.append(SEGMENT_SEPARATOR);
+      result.append(segments[i]);
+    }
+    return result.toString();
+  }
+
+  /**
+   * If this is a hierarchical URI with a path, returns a string
+   * representation of the path, including the authority and the 
+   * <a href="#device_explanation">device component</a>; 
+   * <code>null</code> otherwise.  
+   *
+   * <p>If there is no authority, the format of this string is:
+   * <pre>
+   *   device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>If there is an authority, it is:
+   * <pre>
+   *   //authority/device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
+   * <pre>
+   *   authority/pathSegment1/pathSegment2...</pre>
+   */
+  public String devicePath()
+  {
+    if (!hasPath()) return null;
+
+    StringBuffer result = new StringBuffer();
+
+    if (hasAuthority())
+    {
+      if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
+      result.append(authority);
+
+      if (hasDevice()) result.append(SEGMENT_SEPARATOR);
+    }
+
+    if (hasDevice()) result.append(device);
+    if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      if (i != 0) result.append(SEGMENT_SEPARATOR);
+      result.append(segments[i]);
+    }
+    return result.toString();
+  }
+
+  /**
+   * If this is a hierarchical URI with a query component, returns it;
+   * <code>null</code> otherwise.
+   */
+  public String query()
+  {
+    return query;
+  }
+
+
+  /**
+   * Returns the URI formed from this URI and the given query.
+   *
+   * @exception java.lang.IllegalArgumentException if
+   * <code>query</code> is not a valid query (portion) according
+   * to {@link #validQuery validQuery}.
+   */
+  public URI appendQuery(String query)
+  {
+    if (!validQuery(query))
+    {
+      throw new IllegalArgumentException(
+        "invalid query portion: " + query);
+    }
+    return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment); 
+  }
+
+  /**
+   * If this URI has a non-null {@link #query query}, returns the URI
+   * formed by removing it; this URI unchanged, otherwise.
+   */
+  public URI trimQuery()
+  {
+    if (query == null)
+    {
+      return this;
+    }
+    else
+    {
+      return new URI(hierarchical, scheme, authority, device, absolutePath, segments, null, fragment); 
+    }
+  }
+
+  /**
+   * If this URI has a fragment component, returns it; <code>null</code>
+   * otherwise.
+   */
+  public String fragment()
+  {
+    return fragment;
+  }
+
+  /**
+   * Returns the URI formed from this URI and the given fragment.
+   *
+   * @exception java.lang.IllegalArgumentException if
+   * <code>fragment</code> is not a valid fragment (portion) according
+   * to {@link #validFragment validFragment}.
+   */
+  public URI appendFragment(String fragment)
+  {
+    if (!validFragment(fragment))
+    {
+      throw new IllegalArgumentException(
+        "invalid fragment portion: " + fragment);
+    }
+    URI result = new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment); 
+
+    if (!hasFragment())
+    {
+      result.cachedTrimFragment = this;
+    }
+    return result;
+  }
+
+  /**
+   * If this URI has a non-null {@link #fragment fragment}, returns the URI
+   * formed by removing it; this URI unchanged, otherwise.
+   */
+  public URI trimFragment()
+  {
+    if (fragment == null)
+    {
+      return this;
+    }
+    else if (cachedTrimFragment == null)
+    {
+      cachedTrimFragment = new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, null); 
+    }
+
+    return cachedTrimFragment;
+  }
+
+  /**
+   * Resolves this URI reference against a <code>base</code> absolute
+   * hierarchical URI, returning the resulting absolute URI.  If already
+   * absolute, the URI itself is returned.  URI resolution is described in
+   * detail in section 5.2 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
+   * 2396</a>, "Resolving Relative References to Absolute Form."
+   *
+   * <p>During resolution, empty segments, self references ("."), and parent
+   * references ("..") are interpreted, so that they can be removed from the
+   * path.  Step 6(g) gives a choice of how to handle the case where parent
+   * references point to a path above the root: the offending segments can
+   * be preserved or discarded.  This method preserves them.  To have them
+   * discarded, please use the two-parameter form of {@link
+   * #resolve(URI, boolean) resolve}.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>base</code> is
+   * non-hierarchical or is relative.
+   */
+  public URI resolve(URI base)
+  {
+    return resolve(base, true);
+  }
+
+  /**
+   * Resolves this URI reference against a <code>base</code> absolute
+   * hierarchical URI, returning the resulting absolute URI.  If already
+   * absolute, the URI itself is returned.  URI resolution is described in
+   * detail in section 5.2 of <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC
+   * 2396</a>, "Resolving Relative References to Absolute Form."
+   *
+   * <p>During resultion, empty segments, self references ("."), and parent
+   * references ("..") are interpreted, so that they can be removed from the
+   * path.  Step 6(g) gives a choice of how to handle the case where parent
+   * references point to a path above the root: the offending segments can
+   * be preserved or discarded.  This method can do either.
+   *
+   * @param preserveRootParents <code>true</code> if segments refering to the
+   * parent of the root path are to be preserved; <code>false</code> if they
+   * are to be discarded.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>base</code> is
+   * non-hierarchical or is relative.
+   */
+  public URI resolve(URI base, boolean preserveRootParents)
+  {
+    if (!base.isHierarchical() || base.isRelative())
+    {
+      throw new IllegalArgumentException(
+        "resolve against non-hierarchical or relative base");
+    }
+
+    // an absolute URI needs no resolving
+    if (!isRelative()) return this;
+
+    // note: isRelative() -> hierarchical
+
+    String newAuthority = authority;
+    String newDevice = device;
+    boolean newAbsolutePath = absolutePath;
+    String[] newSegments = segments;
+    String newQuery = query;
+    // note: it's okay for two URIs to share a segments array, since
+    // neither will ever modify it
+    
+    if (authority == null)
+    {
+      // no authority: use base's
+      newAuthority = base.authority();
+
+      if (device == null)
+      {
+        // no device: use base's
+        newDevice = base.device();
+
+        if (hasEmptyPath() && query == null)
+        {
+          // current document reference: use base path and query
+          newAbsolutePath = base.hasAbsolutePath();
+          newSegments = base.segments();
+          newQuery = base.query();
+        }
+        else if (hasRelativePath())
+        {
+          // relative path: merge with base and keep query (note: if the
+          // base has no path and this a non-empty relative path, there is
+          // an implied root in the resulting path) 
+          newAbsolutePath = base.hasAbsolutePath() || !hasEmptyPath();
+          newSegments = newAbsolutePath ? mergePath(base, preserveRootParents)
+            : NO_SEGMENTS;
+        }
+        // else absolute path: keep it and query
+      }
+      // else keep device, path, and query
+    }
+    // else keep authority, device, path, and query
+    
+    // always keep fragment, even if null, and use scheme from base;
+    // no validation needed since all components are from existing URIs
+    return new URI(true, base.scheme(), newAuthority, newDevice,
+                   newAbsolutePath, newSegments, newQuery, fragment);
+  }
+
+  // Merges this URI's relative path with the base non-relative path.  If
+  // base has no path, treat it as the root absolute path, unless this has
+  // no path either.
+  private String[] mergePath(URI base, boolean preserveRootParents)
+  {
+    if (base.hasRelativePath())
+    {
+      throw new IllegalArgumentException("merge against relative path");
+    }
+    if (!hasRelativePath())
+    {
+      throw new IllegalStateException("merge non-relative path");
+    }
+
+    int baseSegmentCount = base.segmentCount();
+    int segmentCount = segments.length;
+    String[] stack = new String[baseSegmentCount + segmentCount];
+    int sp = 0;
+
+    // use a stack to accumulate segments of base, except for the last
+    // (i.e. skip trailing separator and anything following it), and of
+    // relative path
+    for (int i = 0; i < baseSegmentCount - 1; i++)
+    {
+      sp = accumulate(stack, sp, base.segment(i), preserveRootParents);
+    }
+
+    for (int i = 0; i < segmentCount; i++)
+    {
+      sp = accumulate(stack, sp, segments[i], preserveRootParents);
+    }
+
+    // if the relative path is empty or ends in an empty segment, a parent 
+    // reference, or a self referenfce, add a trailing separator to a
+    // non-empty path
+    if (sp > 0 &&  (segmentCount == 0 ||
+                    SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
+                    SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
+                    SEGMENT_SELF.equals(segments[segmentCount - 1])))
+    {
+      stack[sp++] = SEGMENT_EMPTY;
+    }
+
+    // return a correctly sized result
+    String[] result = new String[sp];
+    System.arraycopy(stack, 0, result, 0, sp);
+    return result;
+  }
+
+  // Adds a segment to a stack, skipping empty segments and self references,
+  // and interpreting parent references.
+  private static int accumulate(String[] stack, int sp, String segment,
+                                boolean preserveRootParents)
+  {
+    if (SEGMENT_PARENT.equals(segment))
+    {
+      if (sp == 0)
+      {
+        // special care must be taken for a root's parent reference: it is
+        // either ignored or the symbolic reference itself is pushed
+        if (preserveRootParents) stack[sp++] = segment;
+      }
+      else
+      {
+        // unless we're already accumulating root parent references,
+        // parent references simply pop the last segment descended
+        if (SEGMENT_PARENT.equals(stack[sp - 1])) stack[sp++] = segment;
+        else sp--;
+      }
+    }
+    else if (!SEGMENT_EMPTY.equals(segment) && !SEGMENT_SELF.equals(segment))
+    {
+      // skip empty segments and self references; push everything else
+      stack[sp++] = segment;
+    }
+    return sp;
+  }
+
+  /**
+   * Finds the shortest relative or, if necessary, the absolute URI that,
+   * when resolved against the given <code>base</code> absolute hierarchical
+   * URI using {@link #resolve(URI) resolve}, will yield this absolute URI.  
+   *
+   * @exception java.lang.IllegalArgumentException if <code>base</code> is
+   * non-hierarchical or is relative.
+   * @exception java.lang.IllegalStateException if <code>this</code> is
+   * relative.
+   */
+  public URI deresolve(URI base)
+  {
+    return deresolve(base, true, false, true);
+  }
+
+  /**
+   * Finds an absolute URI that, when resolved against the given
+   * <code>base</code> absolute hierarchical URI using {@link
+   * #resolve(URI, boolean) resolve}, will yield this absolute URI.
+   *
+   * @param preserveRootParents the boolean argument to <code>resolve(URI,
+   * boolean)</code> for which the returned URI should resolve to this URI.
+   * @param anyRelPath if <code>true</code>, the returned URI's path (if
+   * any) will be relative, if possible.  If <code>false</code>, the form of
+   * the result's path will depend upon the next parameter.
+   * @param shorterRelPath if <code>anyRelPath</code> is <code>false</code>
+   * and this parameter is <code>true</code>, the returned URI's path (if
+   * any) will be relative, if one can be found that is no longer (by number
+   * of segments) than the absolute path.  If both <code>anyRelPath</code>
+   * and this parameter are <code>false</code>, it will be absolute.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>base</code> is
+   * non-hierarchical or is relative.
+   * @exception java.lang.IllegalStateException if <code>this</code> is
+   * relative.
+   */
+  public URI deresolve(URI base, boolean preserveRootParents,
+                       boolean anyRelPath, boolean shorterRelPath)
+  {
+    if (!base.isHierarchical() || base.isRelative())
+    {
+      throw new IllegalArgumentException(
+        "deresolve against non-hierarchical or relative base");
+    }
+    if (isRelative())
+    {
+      throw new IllegalStateException("deresolve relative URI");
+    }
+
+    // note: these assertions imply that neither this nor the base URI has a
+    // relative path; thus, both have either an absolute path or no path
+    
+    // different scheme: need complete, absolute URI
+    if (!scheme.equalsIgnoreCase(base.scheme())) return this;
+
+    // since base must be hierarchical, and since a non-hierarchical URI
+    // must have both scheme and opaque part, the complete absolute URI is
+    // needed to resolve to a non-hierarchical URI
+    if (!isHierarchical()) return this;
+
+    String newAuthority = authority;
+    String newDevice = device;
+    boolean newAbsolutePath = absolutePath;
+    String[] newSegments = segments;
+    String newQuery = query;
+
+    if (equals(authority, base.authority()) &&
+        (hasDevice() || hasPath() || (!base.hasDevice() && !base.hasPath())))
+    {
+      // matching authorities and no device or path removal
+      newAuthority = null;
+
+      if (equals(device, base.device()) && (hasPath() || !base.hasPath()))
+      {
+        // matching devices and no path removal
+        newDevice = null;
+
+        // exception if (!hasPath() && base.hasPath())
+
+        if (!anyRelPath && !shorterRelPath)
+        {
+          // user rejects a relative path: keep absolute or no path
+        }
+        else if (hasPath() == base.hasPath() && segmentsEqual(base) &&
+                 equals(query, base.query()))
+        {
+          // current document reference: keep no path or query
+          newAbsolutePath = false;
+          newSegments = NO_SEGMENTS;
+          newQuery = null;
+        }
+        else if (!hasPath() && !base.hasPath())
+        {
+          // no paths: keep query only
+          newAbsolutePath = false;
+          newSegments = NO_SEGMENTS;
+        }
+        // exception if (!hasAbsolutePath())
+        else if (hasCollapsableSegments(preserveRootParents))
+        {
+          // path form demands an absolute path: keep it and query
+        }
+        else
+        {
+          // keep query and select relative or absolute path based on length
+          String[] rel = findRelativePath(base, preserveRootParents);
+          if (anyRelPath || segments.length > rel.length)
+          {
+            // user demands a relative path or the absolute path is longer
+            newAbsolutePath = false;
+            newSegments = rel;
+          }
+          // else keep shorter absolute path
+        }
+      }
+      // else keep device, path, and query
+    }
+    // else keep authority, device, path, and query
+
+    // always include fragment, even if null;
+    // no validation needed since all components are from existing URIs
+    return new URI(true, null, newAuthority, newDevice, newAbsolutePath,
+                   newSegments, newQuery, fragment);
+  }
+
+  // Returns true if the non-relative path includes segments that would be
+  // collapsed when resolving; false otherwise.  If preserveRootParents is
+  // true, collapsable segments include any empty segments, except for the
+  // last segment, as well as and parent and self references.  If
+  // preserveRootsParents is false, parent references are not collapsable if
+  // they are the first segment or preceeded only by other parent
+  // references.
+  private boolean hasCollapsableSegments(boolean preserveRootParents)
+  {
+    if (hasRelativePath())
+    {
+      throw new IllegalStateException("test collapsability of relative path");
+    }
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      String segment = segments[i];
+      if ((i < len - 1 && SEGMENT_EMPTY.equals(segment)) ||
+          SEGMENT_SELF.equals(segment) ||
+          SEGMENT_PARENT.equals(segment) && (
+            !preserveRootParents || (
+              i != 0 && !SEGMENT_PARENT.equals(segments[i - 1]))))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Returns the shortest relative path between the the non-relative path of
+  // the given base and this absolute path.  If the base has no path, it is
+  // treated as the root absolute path.
+  private String[] findRelativePath(URI base, boolean preserveRootParents)
+  {
+    if (base.hasRelativePath())
+    {
+      throw new IllegalArgumentException(
+        "find relative path against base with relative path");
+    }
+    if (!hasAbsolutePath())
+    {
+      throw new IllegalArgumentException(
+        "find relative path of non-absolute path");
+    }
+
+    // treat an empty base path as the root absolute path
+    String[] startPath = base.collapseSegments(preserveRootParents);
+    String[] endPath = segments;
+
+    // drop last segment from base, as in resolving
+    int startCount = startPath.length > 0 ? startPath.length - 1 : 0;
+    int endCount = endPath.length;
+
+    // index of first segment that is different between endPath and startPath
+    int diff = 0;
+
+    // if endPath is shorter than startPath, the last segment of endPath may
+    // not be compared: because startPath has been collapsed and had its
+    // last segment removed, all preceeding segments can be considered non-
+    // empty and followed by a separator, while the last segment of endPath
+    // will either be non-empty and not followed by a separator, or just empty
+    for (int count = startCount < endCount ? startCount : endCount - 1;
+         diff < count && startPath[diff].equals(endPath[diff]); diff++);
+
+    int upCount = startCount - diff;
+    int downCount = endCount - diff;
+
+    // a single separator, possibly preceeded by some parent reference
+    // segments, is redundant
+    if (downCount == 1 && SEGMENT_EMPTY.equals(endPath[endCount - 1]))
+    {
+      downCount = 0;
+    }
+
+    // an empty path needs to be replaced by a single "." if there is no
+    // query, to distinguish it from a current document reference
+    if (upCount + downCount == 0)
+    {
+      if (query == null) return new String[] { SEGMENT_SELF };
+      return NO_SEGMENTS;
+    }
+
+    // return a correctly sized result
+    String[] result = new String[upCount + downCount];
+    Arrays.fill(result, 0, upCount, SEGMENT_PARENT);
+    System.arraycopy(endPath, diff, result, upCount, downCount);
+    return result;
+  }
+
+  // Collapses non-ending empty segments, parent references, and self
+  // references in a non-relative path, returning the same path that would
+  // be produced from the base hierarchical URI as part of a resolve.
+  String[] collapseSegments(boolean preserveRootParents)
+  {
+    if (hasRelativePath())
+    {
+      throw new IllegalStateException("collapse relative path");
+    }
+
+    if (!hasCollapsableSegments(preserveRootParents)) return segments();
+
+    // use a stack to accumulate segments
+    int segmentCount = segments.length;
+    String[] stack = new String[segmentCount];
+    int sp = 0;
+
+    for (int i = 0; i < segmentCount; i++)
+    {
+      sp = accumulate(stack, sp, segments[i], preserveRootParents);
+    }
+
+    // if the path is non-empty and originally ended in an empty segment, a
+    // parent reference, or a self reference, add a trailing separator
+    if (sp > 0 && (SEGMENT_EMPTY.equals(segments[segmentCount - 1]) ||
+                   SEGMENT_PARENT.equals(segments[segmentCount - 1]) ||
+                   SEGMENT_SELF.equals(segments[segmentCount - 1])))
+    {                   
+      stack[sp++] = SEGMENT_EMPTY;
+    }
+
+    // return a correctly sized result
+    String[] result = new String[sp];
+    System.arraycopy(stack, 0, result, 0, sp);
+    return result;
+  }
+
+  /**
+   * Returns the string representation of this URI.  For a generic,
+   * non-hierarchical URI, this looks like:
+   * <pre>
+   *   scheme:opaquePart#fragment</pre>
+   * 
+   * <p>For a hierarchical URI, it looks like:
+   * <pre>
+   *   scheme://authority/device/pathSegment1/pathSegment2...?query#fragment</pre>
+   *
+   * <p>For an <a href="#archive_explanation">archive URI</a>, it's just:
+   * <pre>
+   *   scheme:authority/pathSegment1/pathSegment2...?query#fragment</pre>
+   * <p>Of course, absent components and their separators will be omitted.
+   */
+  public String toString()
+  {
+    if (cachedToString == null)
+    {
+      StringBuffer result = new StringBuffer();
+      if (!isRelative())
+      {
+        result.append(scheme);
+        result.append(SCHEME_SEPARATOR);
+      }
+
+      if (isHierarchical())
+      {
+        if (hasAuthority())
+        {
+          if (!isArchive()) result.append(AUTHORITY_SEPARATOR);
+          result.append(authority);
+        }
+
+        if (hasDevice())
+        {
+          result.append(SEGMENT_SEPARATOR);
+          result.append(device);
+        }
+
+        if (hasAbsolutePath()) result.append(SEGMENT_SEPARATOR);
+
+        for (int i = 0, len = segments.length; i < len; i++)
+        {
+          if (i != 0) result.append(SEGMENT_SEPARATOR);
+          result.append(segments[i]);
+        }
+
+        if (hasQuery())
+        {
+          result.append(QUERY_SEPARATOR);
+          result.append(query);
+        }
+      }
+      else
+      {
+        result.append(authority);
+      }
+
+      if (hasFragment())
+      {
+        result.append(FRAGMENT_SEPARATOR);
+        result.append(fragment);
+      }
+      cachedToString = result.toString();
+    }
+    return cachedToString;
+  }
+
+  // Returns a string representation of this URI for debugging, explicitly
+  // showing each of the components.
+  String toString(boolean includeSimpleForm)
+  {
+    StringBuffer result = new StringBuffer();
+    if (includeSimpleForm) result.append(toString());
+    result.append("\n hierarchical: ");
+    result.append(hierarchical);
+    result.append("\n       scheme: ");
+    result.append(scheme);
+    result.append("\n    authority: ");
+    result.append(authority);
+    result.append("\n       device: ");
+    result.append(device);
+    result.append("\n absolutePath: ");
+    result.append(absolutePath);
+    result.append("\n     segments: ");
+    if (segments.length == 0) result.append("<empty>");
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      if (i > 0) result.append("\n               ");
+      result.append(segments[i]);
+    }
+    result.append("\n        query: ");
+    result.append(query);
+    result.append("\n     fragment: ");
+    result.append(fragment);
+    return result.toString();
+  }
+
+  /**
+   * If this URI may refer directly to a locally accessible file, as
+   * determined by {@link #isFile isFile}, {@link decode decodes} and formats  
+   * the URI as a pathname to that file; returns null otherwise.
+   *
+   * <p>If there is no authority, the format of this string is:
+   * <pre>
+   *   device/pathSegment1/pathSegment2...</pre>
+   *
+   * <p>If there is an authority, it is:
+   * <pre>
+   *   //authority/device/pathSegment1/pathSegment2...</pre>
+   * 
+   * <p>However, the character used as a separator is system-dependant and
+   * obtained from {@link java.io.File#separatorChar}.
+   */
+  public String toFileString()
+  {
+    if (!isFile()) return null;
+
+    StringBuffer result = new StringBuffer();
+    char separator = File.separatorChar;
+
+    if (hasAuthority())
+    {
+      result.append(separator);
+      result.append(separator);
+      result.append(authority);
+
+      if (hasDevice()) result.append(separator);
+    }
+
+    if (hasDevice()) result.append(device);
+    if (hasAbsolutePath()) result.append(separator);
+
+    for (int i = 0, len = segments.length; i < len; i++)
+    {
+      if (i != 0) result.append(separator);
+      result.append(segments[i]);
+    }
+
+    return decode(result.toString());
+  }
+
+  /**
+   * Returns the URI formed by appending the specified segment on to the end
+   * of the path of this URI, if hierarchical; this URI unchanged,
+   * otherwise.  If this URI has an authority and/or device, but no path,
+   * the segment becomes the first under the root in an absolute path.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>segment</code>
+   * is not a valid segment according to {@link #validSegment}.
+   */
+  public URI appendSegment(String segment)
+  {
+    if (!validSegment(segment))
+    {
+      throw new IllegalArgumentException("invalid segment: " + segment);
+    }
+
+    if (!isHierarchical()) return this;
+
+    // absolute path or no path -> absolute path
+    boolean newAbsolutePath = !hasRelativePath();
+
+    int len = segments.length;
+    String[] newSegments = new String[len + 1];
+    System.arraycopy(segments, 0, newSegments, 0, len);
+    newSegments[len] = segment;
+
+    return new URI(true, scheme, authority, device, newAbsolutePath,
+                   newSegments, query, fragment);
+  }
+
+  /**
+   * Returns the URI formed by appending the specified segments on to the
+   * end of the path of this URI, if hierarchical; this URI unchanged,
+   * otherwise.  If this URI has an authority and/or device, but no path,
+   * the segments are made to form an absolute path.
+   *
+   * @param segments an array of non-null strings, each representing one
+   * segment of the path.  If desired, a trailing separator should be
+   * represented by an empty-string segment as the last element of the
+   * array.
+   *
+   * @exception java.lang.IllegalArgumentException if <code>segments</code>
+   * is not a valid segment array according to {@link #validSegments}.
+   */
+  public URI appendSegments(String[] segments)
+  {
+    if (!validSegments(segments))
+    {
+      String s = segments == null ? "invalid segments: " + segments :
+        "invalid segment: " + firstInvalidSegment(segments);
+      throw new IllegalArgumentException(s);
+    }
+
+    if (!isHierarchical()) return this;
+
+    // absolute path or no path -> absolute path
+    boolean newAbsolutePath = !hasRelativePath(); 
+
+    int len = this.segments.length;
+    int segmentsCount = segments.length;
+    String[] newSegments = new String[len + segmentsCount];
+    System.arraycopy(this.segments, 0, newSegments, 0, len);
+    System.arraycopy(segments, 0, newSegments, len, segmentsCount);
+    
+    return new URI(true, scheme, authority, device, newAbsolutePath,
+                   newSegments, query, fragment);
+  }
+
+  /**
+   * Returns the URI formed by trimming the specified number of segments
+   * (including empty segments, such as one representing a trailing
+   * separator) from the end of the path of this URI, if hierarchical;
+   * otherwise, this URI is returned unchanged.
+   *
+   * <p>Note that if all segments are trimmed from an absolute path, the
+   * root absolute path remains.
+   * 
+   * @param i the number of segments to be trimmed in the returned URI.  If
+   * less than 1, this URI is returned unchanged; if equal to or greater
+   * than the number of segments in this URI's path, all segments are
+   * trimmed.  
+   */
+  public URI trimSegments(int i)
+  {
+    if (!isHierarchical() || i < 1) return this;
+
+    String[] newSegments = NO_SEGMENTS;
+    int len = segments.length - i;
+    if (len > 0)
+    {
+      newSegments = new String[len];
+      System.arraycopy(segments, 0, newSegments, 0, len);
+    }
+    return new URI(true, scheme, authority, device, absolutePath,
+                   newSegments, query, fragment);
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI that has a path
+   * that ends with a trailing separator; <code>false</code> otherwise.
+   *
+   * <p>A trailing separator is represented as an empty segment as the
+   * last segment in the path; note that this definition does <em>not</em>
+   * include the lone separator in the root absolute path.
+   */
+  public boolean hasTrailingPathSeparator()
+  {
+    return segments.length > 0 && 
+      SEGMENT_EMPTY.equals(segments[segments.length - 1]);
+  }
+
+  /**
+   * If this is a hierarchical URI whose path includes a file extension,
+   * that file extension is returned; null otherwise.  We define a file
+   * extension as any string following the last period (".") in the final
+   * path segment.  If there is no path, the path ends in a trailing
+   * separator, or the final segment contains no period, then we consider
+   * there to be no file extension.  If the final segment ends in a period,
+   * then the file extension is an empty string.
+   */
+  public String fileExtension()
+  {
+    int len = segments.length;
+    if (len == 0) return null;
+
+    String lastSegment = segments[len - 1];
+    int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
+    return i < 0 ? null : lastSegment.substring(i + 1);
+  }
+
+  /**
+   * Returns the URI formed by appending a period (".") followed by the
+   * specified file extension to the last path segment of this URI, if it is
+   * hierarchical with a non-empty path ending in a non-empty segment;
+   * otherwise, this URI is returned unchanged.
+
+   * <p>The extension is appended regardless of whether the segment already
+   * contains an extension.
+   *
+   * @exception java.lang.IllegalArgumentException if
+   * <code>fileExtension</code> is not a valid segment (portion) according
+   * to {@link #validSegment}.
+   */
+  public URI appendFileExtension(String fileExtension)
+  {
+    if (!validSegment(fileExtension))
+    {
+      throw new IllegalArgumentException(
+        "invalid segment portion: " + fileExtension);
+    }
+
+    int len = segments.length;
+    if (len == 0) return this;
+
+    String lastSegment = segments[len - 1];
+    if (SEGMENT_EMPTY.equals(lastSegment)) return this;
+    StringBuffer newLastSegment = new StringBuffer(lastSegment);
+    newLastSegment.append(FILE_EXTENSION_SEPARATOR);
+    newLastSegment.append(fileExtension);
+
+    String[] newSegments = new String[len];
+    System.arraycopy(segments, 0, newSegments, 0, len - 1);
+    newSegments[len - 1] = newLastSegment.toString();
+    
+    // note: segments.length > 0 -> hierarchical
+    return new URI(true, scheme, authority, device, absolutePath,
+                   newSegments, query, fragment); 
+  }
+
+  /**
+   * If this URI has a non-null {@link #fileExtension fileExtension},
+   * returns the URI formed by removing it; this URI unchanged, otherwise.
+   */
+  public URI trimFileExtension()
+  {
+    int len = segments.length;
+    if (len == 0) return this;
+
+    String lastSegment = segments[len - 1];
+    int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR);
+    if (i < 0) return this;
+
+    String newLastSegment = lastSegment.substring(0, i);
+    String[] newSegments = new String[len];
+    System.arraycopy(segments, 0, newSegments, 0, len - 1);
+    newSegments[len - 1] = newLastSegment;
+
+    // note: segments.length > 0 -> hierarchical
+    return new URI(true, scheme, authority, device, absolutePath,
+                   newSegments, query, fragment); 
+  }
+
+  /**
+   * Returns <code>true</code> if this is a hierarchical URI that ends in a
+   * slash; that is, it has a trailing path separator or is the root
+   * absolute path, and has no query and no fragment; <code>false</code>
+   * is returned otherwise.
+   */
+  public boolean isPrefix()
+  {
+    return hierarchical && query == null && fragment == null &&
+      (hasTrailingPathSeparator() || (absolutePath && segments.length == 0));
+  }
+
+  /**
+   * If this is a hierarchical URI reference and <code>oldPrefix</code> is a
+   * prefix of it, this returns the URI formed by replacing it by
+   * <code>newPrefix</code>; <code>null</code> otherwise.
+   *
+   * <p>In order to be a prefix, the <code>oldPrefix</code>'s
+   * {@link #isPrefix isPrefix} must return <code>true</code>, and it must
+   * match this URI's scheme, authority, and device.  Also, the paths must
+   * match, up to prefix's end.
+   *
+   * @exception java.lang.IllegalArgumentException if either
+   * <code>oldPrefix</code> or <code>newPrefix</code> is not a prefix URI
+   * according to {@link #isPrefix}.
+   */
+  public URI replacePrefix(URI oldPrefix, URI newPrefix)
+  {
+    if (!oldPrefix.isPrefix() || !newPrefix.isPrefix())
+    {
+      String which = oldPrefix.isPrefix() ? "new" : "old";
+      throw new IllegalArgumentException("non-prefix " + which + " value");
+    }
+
+    // Get what's left of the segments after trimming the prefix.
+    String[] tailSegments = getTailSegments(oldPrefix);
+    if (tailSegments == null) return null;
+
+    // If the new prefix has segments, it is not the root absolute path,
+    // and we need to drop the trailing empty segment and append the tail
+    // segments.
+    String[] mergedSegments = tailSegments;
+    if (newPrefix.segmentCount() != 0)
+    {
+      int segmentsToKeep = newPrefix.segmentCount() - 1;
+      mergedSegments = new String[segmentsToKeep + tailSegments.length];
+      System.arraycopy(newPrefix.segments(), 0, mergedSegments, 0,
+                       segmentsToKeep);
+
+      if (tailSegments.length != 0)
+      {
+        System.arraycopy(tailSegments, 0, mergedSegments, segmentsToKeep,
+                         tailSegments.length);
+      }
+    }
+
+    // no validation needed since all components are from existing URIs
+    return new URI(true, newPrefix.scheme(), newPrefix.authority(),
+                   newPrefix.device(), newPrefix.hasAbsolutePath(),
+                   mergedSegments, query, fragment);
+  }
+
+  // If this is a hierarchical URI reference and prefix is a prefix of it,
+  // returns the portion of the path remaining after that prefix has been
+  // trimmed; null otherwise.
+  private String[] getTailSegments(URI prefix)
+  {
+    if (!prefix.isPrefix())
+    {
+      throw new IllegalArgumentException("non-prefix trim");
+    }
+
+    // Don't even consider it unless this is hierarchical and has scheme,
+    // authority, device and path absoluteness equal to those of the prefix.
+    if (!hierarchical ||
+        !equals(scheme, prefix.scheme(), true) ||
+        !equals(authority, prefix.authority()) ||
+        !equals(device, prefix.device()) ||
+        absolutePath != prefix.hasAbsolutePath())
+    {
+      return null;
+    }
+
+    // If the prefix has no segments, then it is the root absolute path, and
+    // we know this is an absolute path, too.
+    if (prefix.segmentCount() == 0) return segments;
+
+    // This must have no fewer segments than the prefix.  Since the prefix
+    // is not the root absolute path, its last segment is empty; all others
+    // must match.
+    int i = 0;
+    int segmentsToCompare = prefix.segmentCount() - 1;
+    if (segments.length <= segmentsToCompare) return null;
+
+    for (; i < segmentsToCompare; i++)
+    {
+      if (!segments[i].equals(prefix.segment(i))) return null;
+    }
+
+    // The prefix really is a prefix of this.  If this has just one more,
+    // empty segment, the paths are the same.
+    if (i == segments.length - 1 && SEGMENT_EMPTY.equals(segments[i]))
+    {
+      return NO_SEGMENTS;
+    }
+    
+    // Otherwise, the path needs only the remaining segments.
+    String[] newSegments = new String[segments.length - i];
+    System.arraycopy(segments, i, newSegments, 0, newSegments.length);
+    return newSegments;
+  }
+
+  /**
+   * Encodes a string so as to produce a valid opaque part value, as defined
+   * by the RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as is <code>/</code> if it is the first character.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeOpaquePart(String value, boolean ignoreEscaped)
+  {
+    String result = encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+    return result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ?
+      "%2F" + result.substring(1) :
+      result;
+  }
+
+  /**
+   * Encodes a string so as to produce a valid authority, as defined by the
+   * RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as are <code>/</code> and <code>?</code>
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeAuthority(String value, boolean ignoreEscaped)
+  {
+    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid segment, as defined by the
+   * RFC.  All excluded characters, such as space and <code>#</code>,
+   * are escaped, as are <code>/</code> and <code>?</code>
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeSegment(String value, boolean ignoreEscaped)
+  {
+    return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid query, as defined by the RFC.
+   * Only excluded characters, such as space and <code>#</code>, are escaped.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeQuery(String value, boolean ignoreEscaped)
+  {
+    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+  }
+
+  /**
+   * Encodes a string so as to produce a valid fragment, as defined by the
+   * RFC.  Only excluded characters, such as space and <code>#</code>, are
+   * escaped.
+   * 
+   * @param ignoreEscaped <code>true</code> to leave <code>%</code> characters
+   * unescaped if they already begin a valid three-character escape sequence;
+   * <code>false</code> to encode all <code>%</code> characters.  Note that
+   * if a <code>%</code> is not followed by 2 hex digits, it will always be
+   * escaped. 
+   */
+  public static String encodeFragment(String value, boolean ignoreEscaped)
+  {
+    return encode(value, URIC_HI, URIC_LO, ignoreEscaped);
+  }
+
+  // Encodes a complete URI, optionally leaving % characters unescaped when
+  // beginning a valid three-character escape sequence.  We assume that the
+  // last # begins the fragment.
+  private static String encodeURI(String uri, boolean ignoreEscaped)
+  {
+    if (uri == null) return null;
+
+    StringBuffer result = new StringBuffer();
+
+    int i = uri.indexOf(SCHEME_SEPARATOR);
+    if (i != -1)
+    {
+      String scheme = uri.substring(0, i);
+      result.append(scheme);
+      result.append(SCHEME_SEPARATOR);
+    }
+    
+    int j = uri.lastIndexOf(FRAGMENT_SEPARATOR);
+    if (j != -1)
+    {
+      String sspart = uri.substring(++i, j);
+      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
+      result.append(FRAGMENT_SEPARATOR);
+
+      String fragment = uri.substring(++j);
+      result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped));
+    }
+    else
+    {
+      String sspart = uri.substring(++i);
+      result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped));
+    }
+    
+    return result.toString();
+  }
+
+  // Encodes the given string, replacing each ASCII character that is not in
+  // the set specified by the 128-bit bitmask and each non-ASCII character
+  // below 0xA0 by an escape sequence of % followed by two hex digits.  If
+  // % is not in the set but ignoreEscaped is true, then % will not be encoded
+  // iff it already begins a valid escape sequence.
+  private static String encode(String value, long highBitmask, long lowBitmask, boolean ignoreEscaped)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      char c = value.charAt(i);
+
+      if (!matches(c, highBitmask, lowBitmask) && c < 160 &&
+          (!ignoreEscaped || !isEscaped(value, i)))
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+        appendEscaped(result, (byte)c);
+      }
+      else if (result != null)
+      {
+        result.append(c);
+      }
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Tests whether an escape occurs in the given string, starting at index i.
+  // An escape sequence is a % followed by two hex digits.
+  private static boolean isEscaped(String s, int i)
+  {
+    return s.charAt(i) == ESCAPE && s.length() > i + 2 &&
+      matches(s.charAt(i + 1), HEX_HI, HEX_LO) &&
+      matches(s.charAt(i + 2), HEX_HI, HEX_LO);
+  }
+
+  // Computes a three-character escape sequence for the byte, appending
+  // it to the StringBuffer.  Only characters up to 0xFF should be escaped;
+  // all but the least significant byte will be ignored.
+  private static void appendEscaped(StringBuffer result, byte b)
+  {
+    result.append(ESCAPE);
+
+    // The byte is automatically widened into an int, with sign extension,
+    // for shifting.  This can introduce 1's to the left of the byte, which
+    // must be cleared by masking before looking up the hex digit.
+    //
+    result.append(HEX_DIGITS[(b >> 4) & 0x0F]);
+    result.append(HEX_DIGITS[b & 0x0F]);
+  }
+
+  /**
+   * Decodes the given string, replacing each three-digit escape sequence by
+   * the character that it represents.  Incomplete escape sequences are
+   * ignored.
+   */
+  public static String decode(String value)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      if (isEscaped(value, i)) 
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+        result.append(unescape(value.charAt(i + 1), value.charAt(i + 2)));
+        i += 2;
+      }
+      else if (result != null)
+      {
+        result.append(value.charAt(i));
+      }
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Returns the character encoded by % followed by the two given hex digits,
+  // which is always 0xFF or less, so can safely be casted to a byte.  If
+  // either character is not a hex digit, a bogus result will be returned.
+  private static char unescape(char highHexDigit, char lowHexDigit)
+  {
+    return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit));
+  }
+
+  // Returns the int value of the given hex digit.
+  private static int valueOf(char hexDigit)
+  {
+    if (hexDigit >= 'A' && hexDigit <= 'F')
+    {
+      return hexDigit - 'A' + 10;
+    }
+    if (hexDigit >= 'a' && hexDigit <= 'f')
+    {
+      return hexDigit - 'a' + 10;
+    }
+    if (hexDigit >= '0' && hexDigit <= '9')
+    {
+      return hexDigit - '0';
+    }
+    return 0;
+  }
+
+  /*
+   * Returns <code>true</code> if this URI contains non-ASCII characters;
+   * <code>false</code> otherwise.
+   *
+   * This unused code is included for possible future use... 
+   */
+/*
+  public boolean isIRI()
+  {
+    return iri; 
+  }
+
+  // Returns true if the given string contains any non-ASCII characters;
+  // false otherwise.
+  private static boolean containsNonASCII(String value)
+  {
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      if (value.charAt(i) > 127) return true;
+    }
+    return false;
+  }
+*/
+
+  /*
+   * If this is an {@link #isIRI IRI}, converts it to a strict ASCII URI,
+   * using the procedure described in Section 3.1 of the
+   * <a href="http://www.w3.org/International/iri-edit/draft-duerst-iri-09.txt">IRI
+   * Draft RFC</a>.  Otherwise, this URI, itself, is returned.
+   *
+   * This unused code is included for possible future use...
+   */
+/*
+  public URI toASCIIURI()
+  {
+    if (!iri) return this;
+
+    if (cachedASCIIURI == null)
+    {
+      String eAuthority = encodeAsASCII(authority);
+      String eDevice = encodeAsASCII(device);
+      String eQuery = encodeAsASCII(query);
+      String eFragment = encodeAsASCII(fragment);
+      String[] eSegments = new String[segments.length];
+      for (int i = 0; i < segments.length; i++)
+      {
+        eSegments[i] = encodeAsASCII(segments[i]);
+      }
+      cachedASCIIURI = new URI(hierarchical, scheme, eAuthority, eDevice, absolutePath, eSegments, eQuery, eFragment); 
+
+    }
+    return cachedASCIIURI;
+  }
+
+  // Returns a strict ASCII encoding of the given value.  Each non-ASCII
+  // character is converted to bytes using UTF-8 encoding, which are then
+  // represnted using % escaping.
+  private String encodeAsASCII(String value)
+  {
+    if (value == null) return null;
+
+    StringBuffer result = null;
+
+    for (int i = 0, len = value.length(); i < len; i++)
+    {
+      char c = value.charAt(i);
+
+      if (c >= 128)
+      {
+        if (result == null)
+        {
+          result = new StringBuffer(value.substring(0, i));
+        }
+
+        try
+        {
+          byte[] encoded = (new String(new char[] { c })).getBytes("UTF-8");
+          for (int j = 0, encLen = encoded.length; j < encLen; j++)
+          {
+            appendEscaped(result, encoded[j]);
+          }
+        }
+        catch (UnsupportedEncodingException e)
+        {
+          throw new WrappedException(e);
+        }
+      }
+      else if (result != null)
+      {
+        result.append(c);
+      }
+
+    }
+    return result == null ? value : result.toString();
+  }
+
+  // Returns the number of valid, consecutive, three-character escape
+  // sequences in the given string, starting at index i.
+  private static int countEscaped(String s, int i)
+  {
+    int result = 0;
+
+    for (int len = s.length(); i < len; i += 3)
+    {
+      if (isEscaped(s, i)) result++;
+    }
+    return result;
+  }
+*/
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionDescriptor.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionDescriptor.java
new file mode 100644
index 0000000..647fa36
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionDescriptor.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal;
+
+import java.util.List;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension;
+import org.osgi.framework.Bundle;
+
+/**
+ * A URI resolver extension descriptor contains all the information about
+ * an extension URI resolver. The information contained allows for the
+ * extension resolver to be instantiated and called at the correct times.
+ */
+public class URIResolverExtensionDescriptor
+{
+	protected URIResolverExtension resolver;
+
+	protected String fileType;
+
+	protected String className;
+
+	public List projectNatureIds;
+
+	protected String resourceType;
+
+	protected int stage = URIResolverExtensionRegistry.STAGE_POSTNORMALIZATION;
+
+	protected String priority = URIResolverExtensionRegistry.PRIORITY_MEDIUM;
+
+	protected String pluginId;
+
+	protected boolean error;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param className The extension URI resolver class name.
+	 * @param pluginId The ID of the plugin that contains the extension URI resolver class.
+	 * @param projectNatureIds The project nature IDs for which the resolver should run.
+	 * @param resourceType The type of resource for which the resolver should run.
+	 * @param stage The stage of the resolver. Either prenormalization or postnormalization.
+	 * @param priority The resolver's priority. high, medium, or low.
+	 */
+	public URIResolverExtensionDescriptor(String className, String pluginId,
+			List projectNatureIds, String resourceType, int stage, String priority)
+	{
+		this.className = className;
+		this.pluginId = pluginId;
+		this.projectNatureIds = projectNatureIds;
+		this.resourceType = resourceType;
+		this.stage = stage;
+		this.priority = priority;
+	}
+
+	/**
+	 * Get the extension URI resolver.
+	 * 
+	 * @return The extension URI resolver.
+	 */
+	public URIResolverExtension getResolver()
+	{
+
+		if (resolver == null && className != null && !error)
+		{
+			try
+			{
+				// Class theClass = classLoader != null ?
+				// classLoader.loadClass(className) : Class.forName(className);
+				Bundle bundle = Platform.getBundle(pluginId);
+				Class theClass = bundle.loadClass(className);
+				resolver = (URIResolverExtension) theClass.newInstance();
+			} catch (Exception e)
+			{
+				error = true;
+				e.printStackTrace();
+			}
+		}
+		return resolver;
+	}
+
+	/**
+	 * Determines if the resolver should run in the current scenario given
+	 * the project nature ID, resource type, and stage.
+	 * 
+	 * @param projectNatureId The project nature ID to check against.
+	 * @param resourceType The resource type to check against.
+	 * @param stage The stage to check against.
+	 * @return True if the resolver should run, false otherwise.
+	 */
+	public boolean matches(String projectNatureId, String resourceType, int stage)
+	{
+		if (projectNatureIds.contains(projectNatureId))
+		{
+			return matches(this.resourceType, resourceType) && this.stage == stage;
+		}
+		return false;
+	}
+
+	/**
+	 * Determines if string a matches string b.
+	 * TODO: Why is this required instead of just using String.equals?
+	 * 
+	 * @param a String for comparison.
+	 * @param b String for comparison.
+	 * @return True if the strings match, false otherwise.
+	 */
+	private boolean matches(String a, String b)
+	{
+		return (a != null) ? a.equals(b) : a == b;
+	}
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java
new file mode 100644
index 0000000..d081bf1
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistry.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * The URI resolver extension registry contains information about
+ * all of the extension URI resolvers.
+ */
+public class URIResolverExtensionRegistry
+{
+	protected HashMap map = new HashMap();
+
+	public static final int STAGE_PRENORMALIZATION = 1;
+
+	public static final int STAGE_POSTNORMALIZATION = 2;
+
+	public static final int STAGE_PHYSICAL = 3;
+
+	public static final String PRIORITY_LOW = "low";
+
+	public static final String PRIORITY_MEDIUM = "medium";
+
+	public static final String PRIORITY_HIGH = "high";
+
+	protected final static String NULL_PROJECT_NATURE_ID = "";
+
+	protected static URIResolverExtensionRegistry instance;
+
+	private URIResolverExtensionRegistry()
+	{
+	}
+
+	/**
+	 * Get the one and only instance of the registry.
+	 * 
+	 * @return The one and only instance of the registry.
+	 */
+	public synchronized static URIResolverExtensionRegistry getIntance()
+	{
+		if (instance == null)
+		{
+			instance = new URIResolverExtensionRegistry();
+			new URIResolverExtensionRegistryReader(instance).readRegistry();
+		}
+		return instance;
+	}
+
+	/**
+	 * Add an extension resolver to the registry.
+	 * 
+	 * @param className The name of the extension URI resolver class.
+	 * @param pluginId The ID of the plugin that contains the extension URI resolver class.
+	 * @param projectNatureIds A list of project natures IDs for which the resolver should run.
+	 * @param resourceType The type of resoure for which an extension resource should run.
+	 * @param stage The stage to run. Either prenormalization or postnormalization.
+	 * @param priority The priority of the resolver. Valid values are high, medium, and low.
+	 */
+	public void put(String className, String pluginId, List projectNatureIds,
+			String resourceType, int stage, String priority)
+	{
+		if (projectNatureIds == null)
+			projectNatureIds = new ArrayList();
+		if (projectNatureIds.isEmpty())
+		{
+			projectNatureIds.add(NULL_PROJECT_NATURE_ID);
+		}
+		URIResolverExtensionDescriptor info = new URIResolverExtensionDescriptor(
+				className, pluginId, projectNatureIds, resourceType, stage, priority);
+
+		Iterator idsIter = projectNatureIds.iterator();
+		while (idsIter.hasNext())
+		{
+			String key = (String) idsIter.next();
+
+			HashMap priorityMap = (HashMap) map.get(key);
+			if (priorityMap == null)
+			{
+				priorityMap = new HashMap();
+				map.put(key, priorityMap);
+				priorityMap.put(PRIORITY_HIGH, new ArrayList());
+				priorityMap.put(PRIORITY_MEDIUM, new ArrayList());
+				priorityMap.put(PRIORITY_LOW, new ArrayList());
+			}
+			List list = (List) priorityMap.get(priority);
+			list.add(info);
+		}
+	}
+
+	/**
+	 * Return a list of URIResolverExtensionDescriptor objects that apply to this
+	 * project. The list is in the priority order high, medium, low.
+	 * 
+	 * @param project The project for which you are requesting resolvers.
+	 * @return A list of URIResolverExtensionDescriptor objects.
+	 */
+	public List getExtensionDescriptors(IProject project)
+	{
+		List result = new ArrayList();
+		for (Iterator i = map.keySet().iterator(); i.hasNext();)
+		{
+			String key = (String) i.next();
+			try
+			{
+				if (key == NULL_PROJECT_NATURE_ID || project == null
+						|| project.hasNature(key))
+				{
+					result.addAll((List) ((HashMap) map.get(key)).get(PRIORITY_HIGH));
+					result.addAll((List) ((HashMap) map.get(key)).get(PRIORITY_MEDIUM));
+					result.addAll((List) ((HashMap) map.get(key)).get(PRIORITY_LOW));
+				}
+			} catch (CoreException e)
+			{
+			}
+		}
+		return result;
+	}
+
+	/**
+	 * Return a list of URIResolver objects that match the stage.
+	 * TODO: This seems like an odd method to house here. It may need to be moved
+	 *       or removed if the stage attribute dissapears.
+	 * 
+	 * @param resolverInfoList A list of resolvers to prune.
+	 * @param stage The stage requested.
+	 * @return A list of URIResolver objects that match the stage.
+	 */
+	public List getMatchingURIResolvers(List resolverInfoList, int stage)
+	{
+		List result = new ArrayList();
+		for (Iterator i = resolverInfoList.iterator(); i.hasNext();)
+		{
+			URIResolverExtensionDescriptor info = (URIResolverExtensionDescriptor) i
+					.next();
+			if (info.stage == stage)
+			{
+				Object resolver = info.getResolver();
+				if (resolver != null)
+				{
+					result.add(resolver);
+				}
+			}
+		}
+		return result;
+	}
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java
new file mode 100644
index 0000000..35eb702
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/URIResolverExtensionRegistryReader.java
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin;
+
+/**
+ * This class reads the URI resolver extension point and registers extension
+ * resolvers with the URI resolver registry.
+ */
+public class URIResolverExtensionRegistryReader
+{
+
+	protected static final String EXTENSION_POINT_ID = "resolverExtensions";
+
+	protected static final String TAG_NAME = "resolverExtension";
+
+	protected static final String ATT_ID = "id";
+
+	protected static final String ELEM_PROJECT_NATURE_ID = "projectNature";
+
+	protected static final String ATT_RESOURCE_TYPE = "resourceType";
+
+	protected static final String ATT_CLASS = "class";
+
+	protected static final String ATT_STAGE = "stage";
+
+	protected static final String VAL_STAGE_PRE = "prenormalization";
+
+	protected static final String VAL_STAGE_POST = "postnormalization";
+
+	protected static final String VAL_STAGE_PHYSICAL = "physical";
+
+	protected static final String ATT_VALUE = "value";
+
+	protected static final String ATT_PRIORITY = "priority";
+
+	protected URIResolverExtensionRegistry registry;
+
+	public URIResolverExtensionRegistryReader(URIResolverExtensionRegistry registry)
+	{
+		this.registry = registry;
+	}
+
+	/**
+	 * read from plugin registry and parse it.
+	 */
+	public void readRegistry()
+	{
+		IExtensionRegistry pluginRegistry = Platform.getExtensionRegistry();
+		IExtensionPoint point = pluginRegistry.getExtensionPoint(URIResolverPlugin
+				.getInstance().getBundle().getSymbolicName(), EXTENSION_POINT_ID);
+		if (point != null)
+		{
+			IConfigurationElement[] elements = point.getConfigurationElements();
+			for (int i = 0; i < elements.length; i++)
+			{
+				readElement(elements[i]);
+			}
+		}
+	}
+
+	/**
+	 * readElement() - parse and deal with an extension like:
+	 * 
+	 * <extension point="org.eclipse.wst.contentmodel.util_implementation">
+	 * <util_implementation class =
+	 * org.eclipse.wst.baseutil.CMUtilImplementationImpl /> </extension>
+	 */
+	protected void readElement(IConfigurationElement element)
+	{
+		if (element.getName().equals(TAG_NAME))
+		{
+			// String id = element.getAttribute(ATT_ID);
+			String className = element.getAttribute(ATT_CLASS);
+			// String projectNatureId = element.getAttribute(ATT_PROJECT_NATURE_ID);
+			String resourceType = element.getAttribute(ATT_RESOURCE_TYPE);
+			String stage = element.getAttribute(ATT_STAGE);
+			String priority = element.getAttribute(ATT_PRIORITY);
+			if (priority == null || priority.equals(""))
+			{
+				priority = URIResolverExtensionRegistry.PRIORITY_MEDIUM;
+			}
+			List projectNatureIds = new ArrayList();
+			IConfigurationElement[] ids = element.getChildren(ELEM_PROJECT_NATURE_ID);
+			int numids = ids.length;
+			for (int i = 0; i < numids; i++)
+			{
+				String tempid = ids[i].getAttribute(ATT_VALUE);
+
+				if (tempid != null)
+				{
+					projectNatureIds.add(tempid);
+				}
+			}
+			if (className != null)
+			{
+				try
+				{
+					String pluginId = element.getDeclaringExtension().getNamespace();
+
+					int stageint = URIResolverExtensionRegistry.STAGE_POSTNORMALIZATION;
+					if (stage.equalsIgnoreCase(VAL_STAGE_PRE))
+					{
+						stageint = URIResolverExtensionRegistry.STAGE_PRENORMALIZATION;
+					} else if (stage.equalsIgnoreCase(VAL_STAGE_PHYSICAL))
+					{
+						stageint = URIResolverExtensionRegistry.STAGE_PHYSICAL;
+					}
+					registry.put(className, pluginId, projectNatureIds, resourceType,
+							stageint, priority);
+				} catch (Exception e)
+				{
+					// TODO: Log exception as this will cause an extension resolver
+					//       from loading.
+				}
+			}
+		}
+	}
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java
new file mode 100644
index 0000000..ef0a4ca
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolver.java
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal.provisional;
+
+/**
+ * A URIResolver is used to resolve URI references to resources.
+ */
+public interface URIResolver {
+	
+	/**
+	 * @param baseLocation - the location of the resource that contains the uri 
+	 * @param publicId - an optional public identifier (i.e. namespace name), or null if none
+	 * @param systemId - an absolute or relative URI, or null if none 
+	 * @return an absolute URI represention the 'logical' location of the resource
+	 */
+	public String resolve(String baseLocation, String publicId, String systemId);
+    
+    /**
+     * @param baseLocation - the location of the resource that contains the uri 
+     * @param publicId - an optional public identifier (i.e. namespace name), or null if none
+     * @param systemId - an absolute or relative URI, or null if none 
+     * @return an absolute URI represention the 'physical' location of the resource
+     */
+    public String resolvePhysicalLocation(String baseLocation, String publicId, String logicalLocation);    
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverExtension.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverExtension.java
new file mode 100644
index 0000000..16d61e7
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverExtension.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal.provisional;
+
+import org.eclipse.core.resources.IFile;
+
+/**
+ * An extension to augment the behaviour of a URIResolver.  Extensions are project aware
+ * so that they can apply specialized project specific resolving rules. 
+ */
+public interface URIResolverExtension {
+	/**
+	 * @param file the in-workspace base resource, if one exists
+	 * @param baseLocation - the location of the resource that contains the uri
+	 * @param publicId - an optional public identifier (i.e. namespace name), or null if none
+	 * @param systemId - an absolute or relative URI, or null if none 
+	 * 
+	 * @return an absolute URI or null if this extension can not resolve this reference
+	 */
+	public String resolve(IFile file, String baseLocation, String publicId, String systemId);
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverPlugin.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverPlugin.java
new file mode 100644
index 0000000..2b63687
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/provisional/URIResolverPlugin.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal.provisional;
+
+import java.util.Map;
+
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.wst.common.uriresolver.internal.ExtensibleURIResolver;
+import org.eclipse.wst.common.uriresolver.internal.URIResolverExtensionRegistry;
+
+
+public class URIResolverPlugin extends Plugin {
+	protected static URIResolverPlugin instance;	
+	protected URIResolverExtensionRegistry xmlResolverExtensionRegistry;
+
+	public static URIResolverPlugin getInstance()
+	{
+		return instance;
+	}
+	
+	public URIResolverPlugin() {
+		super();
+		instance = this;
+	}	
+	
+					
+	public static URIResolver createResolver()
+	{
+		return createResolver(null);
+	}
+	
+	public static URIResolver createResolver(Map properties)
+	{
+		// TODO... utilize properties
+		return new ExtensibleURIResolver();
+	}	
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIEncoder.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIEncoder.java
new file mode 100644
index 0000000..6fc7c9e
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIEncoder.java
@@ -0,0 +1,204 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal.util;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+/**
+ *  This class is a modified version of java.lang.URLEncoder.
+ */
+public class URIEncoder 
+{
+  static BitSet dontNeedEncoding;
+  static final int caseDiff = ('a' - 'A');
+  static String dfltEncName = null;
+  
+
+  static 
+  {            
+	  dontNeedEncoding = new BitSet(256);
+	  int i;
+	  for (i = 'a'; i <= 'z'; i++) 
+    {
+	    dontNeedEncoding.set(i);
+	  }
+	  for (i = 'A'; i <= 'Z'; i++) 
+    {
+	    dontNeedEncoding.set(i);
+	  }
+	  for (i = '0'; i <= '9'; i++) 
+    {
+	    dontNeedEncoding.set(i);
+	  }
+
+	  //dontNeedEncoding.set(' '); // cs.. removed so that space character will be replaced by %20
+	  dontNeedEncoding.set('-');
+	  dontNeedEncoding.set('_');
+	  dontNeedEncoding.set('.');
+	  dontNeedEncoding.set('*');
+	  dontNeedEncoding.set(':');   // cs.. added 
+	  dontNeedEncoding.set('/');   // cs.. added so that slashes don't get encoded as %2F
+
+  	// dfltEncName = (String)AccessController.doPrivileged(new GetPropertyAction("file.encoding"));
+  	// As discussed with Sandy, we should encode URIs with UTF8
+   dfltEncName = "UTF8";
+    //System.out.println("dfltEncName " + dfltEncName);
+   }
+
+  /**
+   * You can't call the constructor.
+   */
+  private URIEncoder() { }
+
+  /**
+   * Translates a string into <code>x-www-form-urlencoded</code>
+   * format. This method uses the platform's default encoding
+   * as the encoding scheme to obtain the bytes for unsafe characters.
+   *
+   * @param   s   <code>String</code> to be translated.
+   * @deprecated The resulting string may vary depending on the platform's
+   *             default encoding. Instead, use the encode(String,String)
+   *             method to specify the encoding.
+   * @return  the translated <code>String</code>.
+   */
+  public static String encode(String s) 
+  {
+	  String str = null;
+	  try 
+    {
+	    str = encode(s, dfltEncName);
+	  } 
+    catch (UnsupportedEncodingException e) 
+    {
+	    // The system should always have the platform default
+	  }
+	  return str;
+  }
+
+  /**
+   * Translates a string into <code>application/x-www-form-urlencoded</code>
+   * format using a specific encoding scheme. This method uses the
+   * supplied encoding scheme to obtain the bytes for unsafe
+   * characters.
+   * <p>
+   * <em><strong>Note:</strong> The <a href=
+   * "http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars">
+   * World Wide Web Consortium Recommendation</a> states that
+   * UTF-8 should be used. Not doing so may introduce
+   * incompatibilites.</em>
+   *
+   * @param   s   <code>String</code> to be translated.
+   * @param   enc   The name of a supported 
+   *    <a href="../lang/package-summary.html#charenc">character
+   *    encoding</a>.
+   * @return  the translated <code>String</code>.
+   * @exception  UnsupportedEncodingException
+   *             If the named encoding is not supported
+   * @see java.net.URLDecoder#decode(java.lang.String, java.lang.String)
+   */
+  public static String encode(String s, String enc) throws UnsupportedEncodingException 
+  {
+	  boolean needToChange = false;
+	  boolean wroteUnencodedChar = false; 
+	  int maxBytesPerChar = 10; // rather arbitrary limit, but safe for now
+    StringBuffer out = new StringBuffer(s.length());
+	  ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+	  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(buf, enc));
+
+	  for (int i = 0; i < s.length(); i++) 
+    {
+	    int c = s.charAt(i);
+	    //System.out.println("Examining character: " + c);
+	    if (dontNeedEncoding.get(c))
+      {
+		   //if (c == ' ')
+       //{
+		   //  c = '+';
+		   //  needToChange = true;
+		   //}
+		   //System.out.println("Storing: " + c);
+		   out.append((char)c);
+		   wroteUnencodedChar = true;
+	    }
+      else
+      {
+		    // convert to external encoding before hex conversion
+		    try
+        {
+		      if (wroteUnencodedChar) 
+          { // Fix for 4407610
+		    	  writer = new BufferedWriter(new OutputStreamWriter(buf, enc));
+			      wroteUnencodedChar = false;
+		      }
+		      writer.write(c);
+		        
+		      // If this character represents the start of a Unicode
+		      // surrogate pair, then pass in two characters. It's not
+		      // clear what should be done if a bytes reserved in the 
+		      // surrogate pairs range occurs outside of a legal
+		      // surrogate pair. For now, just treat it as if it were 
+		      // any other character.
+		      // 
+		      if (c >= 0xD800 && c <= 0xDBFF) 
+          {
+			      //  System.out.println(Integer.toHexString(c) + " is high surrogate");			      
+			      if ( (i+1) < s.length()) 
+            {
+			        int d = s.charAt(i+1);
+			        // System.out.println("\tExamining " + Integer.toHexString(d));			      
+			        if (d >= 0xDC00 && d <= 0xDFFF) 
+              {
+				        // System.out.println("\t" + Integer.toHexString(d) + " is low surrogate");				
+				        writer.write(d);
+				        i++;
+			        }
+			      }
+		      }
+		      writer.flush();
+		    } 
+        catch(IOException e) 
+        {
+		      buf.reset();
+		      continue;
+		    }
+		    byte[] ba = buf.toByteArray();
+
+		    for (int j = 0; j < ba.length; j++) 
+        {
+		      out.append('%');
+		      char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
+		      // converting to use uppercase letter as part of
+		      // the hex value if ch is a letter.
+		      if (Character.isLetter(ch)) 
+          {
+			      ch -= caseDiff;
+		      }
+		      out.append(ch);
+		      ch = Character.forDigit(ba[j] & 0xF, 16);
+		      if (Character.isLetter(ch)) 
+          {
+			      ch -= caseDiff;
+		      }
+		      out.append(ch);
+		    }
+		    buf.reset();
+		    needToChange = true;
+	    }
+	  }
+	  return (needToChange? out.toString() : s);
+  }
+}
diff --git a/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIHelper.java b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIHelper.java
new file mode 100644
index 0000000..e564f41
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.uriresolver/src/org/eclipse/wst/common/uriresolver/internal/util/URIHelper.java
@@ -0,0 +1,496 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 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
+ *     Jens Lukowski/Innoopract - initial renaming/restructuring
+ *******************************************************************************/
+package org.eclipse.wst.common.uriresolver.internal.util;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URL;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+
+
+public class URIHelper
+{                       
+  protected static final String FILE_PROTOCOL = "file:";
+  protected static final String PLATFORM_RESOURCE_PROTOCOL = "platform:/resource/";
+  protected static final String PROTOCOL_PATTERN = ":"; 
+
+  public static String normalize(String uri)
+  {                           
+    if (uri != null)
+    {                      
+      String protocol = getProtocol(uri);
+      String file = uri;
+           
+      if (protocol != null)
+      {               
+        try
+        {   
+          // 
+          URL url = new URL(uri); 
+          // we use a 'Path' on the 'file' part of the url in order to normalize the '.' and '..' segments
+          IPath path = new Path(url.getFile()); 
+          URL url2 = new URL(url.getProtocol(), url.getHost(), url.getPort(), path.toString());
+          uri = url2.toString();                               
+        }                        
+        catch (Exception e)
+        {  
+        }
+      }   
+      else
+      {      
+        IPath path = new Path(file);
+        uri = path.toString();
+      }
+    }
+    return uri;
+  }
+
+
+  /**
+   * a 'null' rootLocation argument will causes uri that begins with a '/' to be treated as a workspace relative resource
+   * (i.e. the string "platform:/resource" is prepended and the uri is resolved via the Platform object)
+   */
+  public static String normalize(String uri, String resourceLocation, String rootLocation)
+  {
+    String result = null;
+
+    if (uri != null)
+    { 
+      // is the uri a url
+      if (hasProtocol(uri))
+      {                  
+        if (isPlatformResourceProtocol(uri))
+        {
+          result = resolvePlatformUrl(uri);
+        }
+        else
+        {
+          result = uri;
+        }
+      }
+   
+      // is uri absolute
+      //
+      if (result == null)
+      {
+        if (uri.indexOf(":") != -1 || uri.startsWith("/") || uri.startsWith("\\"))
+        {                   
+          result = uri;
+        }
+      }
+  
+      // if uri is relative to the resourceLocation
+      //
+      if (result == null && resourceLocation != null)
+      {          
+        if (resourceLocation.endsWith("/"))
+        {
+			    result = resourceLocation + uri;
+        }
+		    else
+        {
+			    result = resourceLocation + "/../" + uri;
+        }
+      }
+      
+      if (result == null)
+      {
+        result = uri;
+      }
+  
+      result = normalize(result);
+    }
+
+    //System.out.println("normalize(" + uri + ", " + resourceLocation + ", " + rootLocation + ") = " + result);
+    return result;
+  }
+
+
+  public static boolean isURL(String uri)
+  {
+    return uri.indexOf(":/") > 2; // test that the index is > 2 so that C:/ is not considered a protocol
+  }
+
+
+  public static String getLastSegment(String uri)
+  {
+    String result = uri;
+    int index = Math.max(uri.lastIndexOf("/"), uri.lastIndexOf("\\"));
+    if (index != -1)
+    {
+      result = uri.substring(index + 1);
+    }
+    return result;
+  }
+
+
+  public static String getFileExtension(String uri)
+  {
+    String result = null;
+    int dotIndex = getExtensionDotIndex(uri);
+               
+    if (dotIndex != -1)
+    {
+      result = uri.substring(dotIndex + 1);
+    }
+
+    return result;
+  }
+
+
+  public static String removeFileExtension(String uri)
+  {
+    String result = null;
+    int dotIndex = getExtensionDotIndex(uri);
+
+    if (dotIndex != -1)
+    {
+      result = uri.substring(0, dotIndex);
+    }
+
+    return result;
+  }   
+             
+
+  // here we use the Platform to resolve a workspace relative path to an actual url
+  //
+  protected static String resolvePlatformUrl(String urlspec)
+  {
+    String result = null;
+    try
+    {                        
+      urlspec = urlspec.replace('\\', '/'); 
+      URL url = new URL(urlspec);
+      URL resolvedURL = Platform.resolve(url);
+      result = resolvedURL.toString();
+    }
+    catch (Exception e)
+    {
+    }
+    return result;
+  }
+
+
+  protected static int getExtensionDotIndex(String uri)
+  {
+    int result = -1;
+    int dotIndex = uri.lastIndexOf(".");
+    int slashIndex = Math.max(uri.lastIndexOf("/"), uri.lastIndexOf("\\"));
+
+    if (dotIndex != -1 && dotIndex > slashIndex)
+    {
+      result = dotIndex;
+    }
+
+    return result;
+  }
+  
+
+  public static boolean isPlatformResourceProtocol(String uri)
+  {                                                     
+    return uri != null && uri.startsWith(PLATFORM_RESOURCE_PROTOCOL);
+  }                                                   
+
+  public static String removePlatformResourceProtocol(String uri)
+  {  
+    if (uri != null && uri.startsWith(PLATFORM_RESOURCE_PROTOCOL))
+    {
+      uri = uri.substring(PLATFORM_RESOURCE_PROTOCOL.length());
+    }                                                          
+    return uri;
+  }            
+
+
+  public static String prependPlatformResourceProtocol(String uri)
+  {  
+    if (uri != null && !uri.startsWith(PLATFORM_RESOURCE_PROTOCOL))
+    {
+      uri = PLATFORM_RESOURCE_PROTOCOL + uri;
+    }                                                          
+    return uri;
+  } 
+  
+
+  public static String prependFileProtocol(String uri)
+  {  
+    if (uri != null && !uri.startsWith(FILE_PROTOCOL))
+    {
+      uri = FILE_PROTOCOL + uri;
+    }                                                          
+    return uri;
+  } 
+            
+  public static boolean hasProtocol(String uri)
+  {
+    boolean result = false;     
+    if (uri != null)
+    {
+      int index = uri.indexOf(PROTOCOL_PATTERN);
+      if (index != -1 && index > 2) // assume protocol with be length 3 so that the'C' in 'C:/' is not interpreted as a protocol
+      {
+        result = true;
+      }
+    }
+    return result;
+  }     
+                      
+
+  public static boolean isAbsolute(String uri)
+  {
+    boolean result = false;     
+    if (uri != null)
+    {
+      int index = uri.indexOf(PROTOCOL_PATTERN);
+      if (index != -1 || uri.startsWith("/"))
+      {
+        result = true;
+      }
+    }
+    return result;
+  }
+
+
+  public static String addImpliedFileProtocol(String uri)
+  {  
+    if (!hasProtocol(uri))
+    {                           
+      String prefix = FILE_PROTOCOL;
+      prefix += uri.startsWith("/") ? "//" : "///";
+      uri = prefix + uri;
+    }
+    return uri;
+  }
+             
+  // todo... need to revisit this before we publicize it
+  // 
+  protected static String getProtocol(String uri)
+  {  
+    String result = null;     
+    if (uri != null)
+    {
+      int index = uri.indexOf(PROTOCOL_PATTERN);
+      if (index > 2) // assume protocol with be length 3 so that the'C' in 'C:/' is not interpreted as a protocol
+      {
+        result = uri.substring(0, index + PROTOCOL_PATTERN.length());
+      }
+    }
+    return result;
+  } 
+ 
+
+  public static String removeProtocol(String uri)
+  {
+    String result = uri;     
+    if (uri != null)
+    {
+      int index = uri.indexOf(PROTOCOL_PATTERN);
+      if (index > 2)
+      {
+        result = result.substring(index + PROTOCOL_PATTERN.length());                 
+      }
+    }
+    return result;
+  } 
+
+
+  protected static boolean isProtocolFileOrNull(String uri)
+  {                                    
+    String protocol = getProtocol(uri);   
+    return protocol == null || protocol.equals(FILE_PROTOCOL);
+  }  
+
+                                           
+  protected static boolean isMatchingProtocol(String uri1, String uri2)
+  { 
+    boolean result = false;  
+
+    String protocol1 = getProtocol(uri1);
+    String protocol2 = getProtocol(uri2);
+
+    if (isProtocolFileOrNull(protocol1) && isProtocolFileOrNull(protocol2))
+    {                                                                      
+      result = true;
+    } 
+    else
+    {
+      result = protocol1 != null && protocol2 != null && protocol1.equals(protocol2);
+    }             
+
+    return result;
+  }
+
+  /**
+   * warning... this method not fully tested yet
+   */
+  public static String getRelativeURI(String uri, String resourceLocation)
+  {                                      
+    String result = uri;  
+    if (isMatchingProtocol(uri, resourceLocation)) 
+    {
+      result = getRelativeURI(new Path(removeProtocol(uri)),
+                              new Path(removeProtocol(resourceLocation)));
+    }            
+
+    return result;
+  }
+
+  /**
+   * warning... this method not fully tested yet
+   */
+  public static String getRelativeURI(IPath uri, IPath resourceLocation)
+  {            
+    String result = null;
+    int nMatchingSegments = 0;       
+    resourceLocation = resourceLocation.removeLastSegments(1);
+    while (true)
+    {                   
+      String a = uri.segment(nMatchingSegments); 
+      String b = resourceLocation.segment(nMatchingSegments); 
+      if (a != null && b != null && a.equals(b))
+      {
+        nMatchingSegments++;
+      }
+      else
+      {
+        break;
+      }
+    }                 
+
+    if (nMatchingSegments == 0)
+    {
+      result = uri.toOSString();
+    }
+    else
+    {    
+      result = "";   
+      boolean isFirst = true;
+      String[] segments = resourceLocation.segments();
+      for (int i = nMatchingSegments; i < segments.length; i++)
+      {  
+        result += isFirst ? ".." : "/..";     
+        if (isFirst)
+        {
+          isFirst = false;
+        }        
+      }
+      // 
+      segments = uri.segments();
+      for (int i = nMatchingSegments; i < segments.length; i++)
+      {                      
+        result += isFirst ? segments[i] : ("/" + segments[i]);     
+        if (isFirst)
+        {
+          isFirst = false;
+        } 
+      }
+    }   
+    return result;
+  }
+
+
+  public static String getPlatformURI(IResource resource)
+  {                            
+    String fullPath = resource.getFullPath().toString();
+    if (fullPath.startsWith("/"))
+    {
+      fullPath = fullPath.substring(1);
+    }
+    return PLATFORM_RESOURCE_PROTOCOL + fullPath;
+  }
+  
+
+  /**
+   * This methods is used as a quick test to see if a uri can be resolved to an existing resource.   
+   */
+  public static boolean isReadableURI(String uri, boolean testRemoteURI)
+  {  
+    boolean result = true;  
+    if (uri != null)
+    {   
+      try
+      {                               
+        uri = normalize(uri, null, null);
+        if (isProtocolFileOrNull(uri))
+        {
+          uri = removeProtocol(uri);                            
+          File file = new File(uri);
+          result = file.exists() && file.isFile();
+        }
+        else if (isPlatformResourceProtocol(uri))
+        {
+          // Note - If we are here, uri has been failed to resolve
+          // relative to the Platform. See normalize() to find why.
+          result = false;
+        }
+        else if (testRemoteURI)
+        {
+          URL url = new URL(uri);
+          InputStream is = url.openConnection().getInputStream();
+          is.close();
+          // the uri is readable if we reach here.
+          result = true;
+        }
+      }
+      catch (Exception e)
+      {
+        result = false;
+      }
+    }
+    else // uri is null
+      result = false;
+
+    return result;
+  }  
+
+  /**
+   * return true if this is a valid uri
+   */
+  public static boolean isValidURI(String uri)
+  {                       
+    boolean result = false;
+    try                                                              
+    {
+      new URI(uri);
+      result = true;
+    }
+    catch (Exception e)
+    {
+    }               
+    return result;
+  }
+
+  /**
+   * returns an acceptable URI for a file path
+   */
+  public static String getURIForFilePath(String filePath)
+  {
+    String result = addImpliedFileProtocol(filePath);
+    if (!isValidURI(result))
+    {
+    	try
+    	{
+        result = URIEncoder.encode(result, "UTF8");
+    	}
+    	catch(UnsupportedEncodingException e)
+    	{
+    		// Do nothing as long as UTF8 is used. This is supported.
+    	}
+    }
+    return result;
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/.classpath b/plugins/org.eclipse.wst.internet.cache/.classpath
new file mode 100644
index 0000000..751c8f2
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/plugins/org.eclipse.wst.internet.cache/.cvsignore b/plugins/org.eclipse.wst.internet.cache/.cvsignore
new file mode 100644
index 0000000..95b91e4
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/.cvsignore
@@ -0,0 +1,6 @@
+bin
+temp.folder
+build.xml
+cache.jar
+@dot
+src.zip
diff --git a/plugins/org.eclipse.wst.internet.cache/.project b/plugins/org.eclipse.wst.internet.cache/.project
new file mode 100644
index 0000000..d7188f0
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.wst.internet.cache</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/plugins/org.eclipse.wst.internet.cache/.settings/org.eclipse.jdt.core.prefs b/plugins/org.eclipse.wst.internet.cache/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..de3a8b0
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,57 @@
+#Mon Jan 30 19:48:42 EST 2006
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=error
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.3
diff --git a/plugins/org.eclipse.wst.internet.cache/META-INF/MANIFEST.MF b/plugins/org.eclipse.wst.internet.cache/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..957cd87
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/META-INF/MANIFEST.MF
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %_PLUGIN_NAME
+Bundle-SymbolicName: org.eclipse.wst.internet.cache; singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.eclipse.wst.internet.cache.internal.CachePlugin
+Bundle-Vendor: %_PLUGIN_PROVIDER
+Bundle-Localization: plugin
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.wst.common.uriresolver,
+ org.eclipse.core.resources
+Eclipse-AutoStart: true
+Export-Package: org.eclipse.wst.internet.cache.internal;x-friends:="org.eclipse.wst.internet.cache.tests"
diff --git a/plugins/org.eclipse.wst.internet.cache/about.html b/plugins/org.eclipse.wst.internet.cache/about.html
new file mode 100644
index 0000000..6f6b96c
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/about.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<head>
+<title>About</title>
+<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1">
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+ 
+<p>February 24, 2005</p>	
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;).  Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;).  A copy of the EPL is available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content.  Check the Redistributor's license that was provided with the Content.  If no such license exists, contact the Redistributor.  Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.internet.cache/build.properties b/plugins/org.eclipse.wst.internet.cache/build.properties
new file mode 100644
index 0000000..dbd4f60
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/build.properties
@@ -0,0 +1,10 @@
+source.. = src/
+bin.includes = plugin.xml,\
+               META-INF/,\
+               .,\
+               plugin.properties,\
+               about.html
+src.includes = exsd/,\
+               .,\
+               build.properties
+output.. = bin/
diff --git a/plugins/org.eclipse.wst.internet.cache/exsd/cacheresource.exsd b/plugins/org.eclipse.wst.internet.cache/exsd/cacheresource.exsd
new file mode 100644
index 0000000..bacb272
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/exsd/cacheresource.exsd
@@ -0,0 +1,121 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.wst.internet.cache">
+<annotation>
+      <appInfo>
+         <meta.schema plugin="org.eclipse.wst.internet.cache" id="cacheresource" name="Cache Resource"/>
+      </appInfo>
+      <documentation>
+         The cache resource extension point allows clients to specify resources that may be cached that require licenses to be accepted prior to caching.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <complexType>
+         <sequence>
+            <element ref="cacheresource" minOccurs="1" maxOccurs="unbounded"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  a fully qualified identifier of the target extension point
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  an optional identifier of the extension instance
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  an optional name of the extension instance
+               </documentation>
+               <appInfo>
+                  <meta.attribute translatable="true"/>
+               </appInfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="cacheresource">
+      <complexType>
+         <attribute name="url" type="string" use="required">
+            <annotation>
+               <documentation>
+                  The URL of the resource to cache.
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="license" type="string" use="required">
+            <annotation>
+               <documentation>
+                  The license of the resource to cache. The license is specified as a URL that resolves to a file that contains the license.
+               </documentation>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="since"/>
+      </appInfo>
+      <documentation>
+         1.0
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="examples"/>
+      </appInfo>
+      <documentation>
+         The following is an example of a cache resource contribution:
+&lt;pre&gt;
+   &lt;extension
+         point=&quot;org.eclipse.wst.internet.cache.cacheresource&quot;&gt;
+      &lt;cacheresource
+            license=&quot;http://www.eclipse.org/license.html&quot;
+            url=&quot;http://www.eclipse.org&quot;/&gt;
+   &lt;/extension&gt;
+&lt;/pre&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="apiInfo"/>
+      </appInfo>
+      <documentation>
+         
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="implementation"/>
+      </appInfo>
+      <documentation>
+         
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appInfo>
+         <meta.section type="copyright"/>
+      </appInfo>
+      <documentation>
+         Copyright (c) 2000, 2005 IBM Corporation and others.&lt;br&gt;
+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 &lt;a
+href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.wst.internet.cache/plugin.properties b/plugins/org.eclipse.wst.internet.cache/plugin.properties
new file mode 100644
index 0000000..5df72b9
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/plugin.properties
@@ -0,0 +1,17 @@
+###############################################################################
+# 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
+###############################################################################
+
+_PLUGIN_PROVIDER                       = Eclipse.org
+_PLUGIN_NAME                           = Cache URI Resolver Plug-in
+
+cacheResource                          = Cache Resource
+_UI_CACHE_PREFERENCE_PAGE_TITLE        = Cache
+
diff --git a/plugins/org.eclipse.wst.internet.cache/plugin.xml b/plugins/org.eclipse.wst.internet.cache/plugin.xml
new file mode 100644
index 0000000..09c05eb
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/plugin.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<plugin>
+
+   <extension-point name="%cacheResource" id="cacheresource" schema="exsd/cacheresource.exsd"/>
+
+   <extension
+         point="org.eclipse.ui.preferencePages">
+      <page
+            name="%_UI_CACHE_PREFERENCE_PAGE_TITLE"
+            class="org.eclipse.wst.internet.cache.internal.preferences.CachePreferencePage"
+            id="org.eclipse.wst.internet.cache.internal.preferences.CachePreferencePage"
+            category = "org.eclipse.internet">
+      </page>
+   </extension>
+   <extension
+         point="org.eclipse.core.runtime.preferences">
+      <initializer
+            class="org.eclipse.wst.internet.cache.internal.preferences.PreferenceInitializer">
+      </initializer>
+   </extension>
+   
+   <extension point="org.eclipse.wst.common.uriresolver.resolverExtensions">
+      <resolverExtension
+            stage="physical"
+            priority="low"
+            class="org.eclipse.wst.internet.cache.internal.CacheURIResolverExtension">
+      </resolverExtension>
+   </extension>
+</plugin>
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/Cache.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/Cache.java
new file mode 100644
index 0000000..1951319
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/Cache.java
@@ -0,0 +1,521 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Random;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * The cache holds references to remote resources. The cache can store resources,
+ * retrieve resources, and delete resources.
+ *
+ */
+public class Cache 
+{
+  /**
+   * String instances.
+   */
+  private static final String URI = "uri";
+  private static final String LOCATION ="location";
+  private static final String ENTRY = "entry";
+  private static final String CACHE = "cache";
+  private static final String LAST_MODIFIED = "lastModified";
+  private static final String EXPIRATION_TIME = "expirationTime";
+  private static final String FILE_PROTOCOL = "file:///";
+  private static final String CACHE_FILE = "cache.xml";
+  private static final String CACHE_EXTENSION = ".cache";
+  private static final String CACHE_PREFIX = "wtpcache";
+  private static final String CACHE_SUFFIX = null;
+  /**
+   * The default timeout for a cache entry is 1 day.
+   */
+  private static final long TIMEOUT = 86400000;
+	
+  /**
+   * The one and only instance of the cache.
+   */
+  private static Cache cacheInstance = null;
+  
+  /**
+   * The cache is stored in a hashtable.
+   */
+  private Hashtable cache;
+  
+  /**
+   * A set of uncached resources. The cache was not able to cache resources
+   * in this list. This list allows quickly skipping over these resources in
+   * future requests. 
+   */
+  private Set uncached;
+  
+  /**
+   * The location of the cache
+   */
+  private File cacheLocation = null;
+  
+  /**
+   * Private constructor.
+   */
+  protected Cache(IPath cacheLocation)
+  {
+	  this.cacheLocation = cacheLocation.toFile();//Platform.getPluginStateLocation(CachePlugin.getDefault()).toFile();
+    cache = new Hashtable();
+    uncached = new HashSet();
+  }
+  
+  /**
+   * Get the one and only instance of the cache.
+   * 
+   * @return The one and only instance of the cache.
+   */
+  public static Cache getInstance()
+  {
+//	  if(cacheInstance == null)
+//	  {
+//		  cacheInstance = new Cache(cacheLocation);
+//		  cacheInstance.open(cacheLocation);
+//	  }
+	  return cacheInstance;
+  }
+  
+  /**
+   * Return the local resource for the specified uri. If there is no resource
+   * the cache will attempt to download and cache the resource before returning.
+   * If a remote resource cannot be cached this method will return null.
+   * 
+   * @param uri The URI for which a resource is requested.
+   * @return The local resource for the specified URI or null if a remote resource cannot be cached.
+   */
+  public String getResource(String uri)
+  {
+	  if(uri == null) return null;
+	  CacheEntry result = (CacheEntry)cache.get(uri);
+	  
+	  // If no result is in the cache and the URI is of an allowed type
+	  // retrieve it and store it in the cache.
+	  if(result == null)
+	  {
+      
+        if(!uncached.contains(uri))
+	    {
+          result = cacheResource(uri); 
+        }
+	  }
+	  // Retreive a fresh copy of the result if it has timed out.
+	  else if(result.hasExpired())
+	  {
+		result = refreshCacheEntry(result);
+	  }
+	  if(result == null || result.getLocalFile() == null)
+	  {
+		return null;
+	  }
+	  return FILE_PROTOCOL + cacheLocation.toString() + "/" + result.getLocalFile();
+  }
+  
+  /**
+   * Get the list of uncached resources.
+   * 
+   * @return The list of uncached resources.
+   */
+  protected String[] getUncachedURIs()
+  {
+    return (String[])uncached.toArray(new String[uncached.size()]);
+  }
+  
+  /**
+   * Clear the list of uncached resources.
+   */
+  protected void clearUncachedURIs()
+  {
+    uncached.clear();
+  }
+  
+  /**
+   * Add an uncached resource to the list.
+   */
+  protected void addUncachedURI(String uri)
+  {
+    uncached.add(uri);
+  }
+  
+  /**
+   * Cache the specified resource. This method creates a local version of the
+   * remote resource and adds the resource reference to the cache. If the resource
+   * cannot be accessed it is not added and null is returned.
+   * 
+   * @param uri The remote URI to cache.
+   * @return A new CacheEntry representing the cached resource or null if the remote
+   *         resource could not be retrieved.
+   */
+  protected CacheEntry cacheResource(String uri)
+  {
+	  CacheEntry cacheEntry = null;
+	  URLConnection conn = null;
+	  InputStream is = null;
+	  OutputStream os = null;
+	  try
+	  {
+		  URL url = new URL(uri);
+		  conn = url.openConnection();
+		  conn.connect();
+		  // Determine if this resource can be cached.
+		  if(conn.getUseCaches())
+		  {
+		    is = conn.getInputStream();//url.openStream();
+		  
+		    Random rand = new Random();
+			String fileName = rand.nextInt() + CACHE_EXTENSION;
+		    File file = new File(cacheLocation, fileName);
+		    // If the file already exists we need to change the file name.
+		    while(!file.createNewFile())
+		    {
+			  fileName = rand.nextInt() + CACHE_EXTENSION;
+			  file = new File(cacheLocation,fileName);
+		    }
+		    os = new FileOutputStream(file);
+		    byte[] bytes = new byte[1024];
+		    int bytelength;
+		    while((bytelength = is.read(bytes)) != -1)
+		    {
+			  os.write(bytes, 0, bytelength);
+		    }
+			long lastModified = conn.getLastModified();
+		    long expiration = conn.getExpiration();
+			if(expiration == 0)
+			{
+			  expiration = System.currentTimeMillis() + TIMEOUT;
+			}
+		    cacheEntry = new CacheEntry(uri, fileName, lastModified, expiration);
+		    cache.put(uri,cacheEntry);
+		  }
+
+	  }
+	  catch(Throwable t)
+	  {
+		  // Put the entry in the uncached list so the resolution work will not be performed again.
+      // TODO: Add in a timeout for the non-located uris.
+      uncached.add(uri);
+	  }
+	  finally
+	  {
+		  if(is != null)
+		  {
+			  try
+			  {
+			    is.close();
+			  }
+			  catch(IOException e)
+			  {
+			    // Do nothing if the stream cannot be closed.
+			  }
+		  }
+		  if(os != null)
+		  {
+			  try
+			  {
+			    os.close();
+			  }
+			  catch(IOException e)
+			  {
+				// Do nothing if the stream cannot be closed. 
+			  }
+		  }
+	  }
+	  return cacheEntry;
+  }
+  
+  /**
+   * Refresh the cache entry if necessary. The cache entry will be refreshed
+   * if the remote resource is accessible and the last modified time of the
+   * remote resource is greater than the last modified time of the cached
+   * resource.
+   * 
+   * @param cacheEntry The cache entry to refresh.
+   * @return The refreshed cache entry.
+   */
+  protected CacheEntry refreshCacheEntry(CacheEntry cacheEntry)
+  {
+	  URLConnection conn = null;
+	  InputStream is = null;
+	  OutputStream os = null;
+	  try
+	  {
+		  URL url = new URL(cacheEntry.getURI());
+		  conn = url.openConnection();
+		  conn.connect();
+		  
+		  long lastModified = conn.getLastModified();
+	      if(lastModified > cacheEntry.getLastModified())
+		  {
+			long expiration = conn.getExpiration();
+		    if(expiration == 0)
+			{
+			  expiration = System.currentTimeMillis() + TIMEOUT;
+			}
+			
+		    is = conn.getInputStream();
+			
+			String localFile = cacheEntry.getLocalFile();
+  
+		    File tempFile = File.createTempFile(CACHE_PREFIX, CACHE_SUFFIX);
+			tempFile.deleteOnExit();
+
+		    os = new FileOutputStream(tempFile);
+		    byte[] bytes = new byte[1024];
+		    int bytelength;
+		    while((bytelength = is.read(bytes)) != -1)
+		    {
+			  os.write(bytes, 0, bytelength);
+		    }
+			is.close();
+			os.close();
+			deleteFile(cacheEntry.getURI());
+			File f = new File(cacheLocation, localFile);
+			tempFile.renameTo(f);
+			cacheEntry.setExpiration(expiration);
+			cacheEntry.setLastModified(lastModified);
+		  }
+		  // The cache entry hasn't changed. Just update the expiration time.
+	      else
+		  {
+			long expiration = conn.getExpiration();
+			if(expiration == 0)
+			{
+			  expiration = System.currentTimeMillis() + TIMEOUT;
+			}
+			cacheEntry.setExpiration(expiration);
+		  }
+
+	  }
+	  catch(Exception e)
+	  {
+      // Do nothing.
+	  }
+	  finally
+	  {
+		  if(is != null)
+		  {
+			  try
+			  {
+			    is.close();
+			  }
+			  catch(IOException e)
+			  {
+			    // Do nothing if the stream cannot be closed.
+			  }
+		  }
+		  if(os != null)
+		  {
+			  try
+			  {
+			    os.close();
+			  }
+			  catch(IOException e)
+			  {
+				// Do nothing if the stream cannot be closed. 
+			  }
+		  }
+	  }
+	  return cacheEntry;
+  }
+  
+  /**
+   * Get an array of the cached URIs.
+   * 
+   * @return An array of the cached URIs.
+   */
+  public String[] getCachedURIs()
+  {
+	Set keyset = cache.keySet();
+	return (String[])keyset.toArray(new String[keyset.size()]);
+  }
+  
+  /**
+   * Close the cache. Closing the cache involves serializing the data to an XML file
+   * in the plugin state location.
+   */
+  protected void close()
+  {
+	  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+      try {
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document cachedoc = builder.newDocument();
+		Element rootelem = cachedoc.createElement(CACHE);
+		cachedoc.appendChild(rootelem);
+		
+	  Enumeration uris = cache.keys();
+	  while(uris.hasMoreElements())
+	  {
+		  String key = (String)uris.nextElement();
+		  CacheEntry cacheEntry = (CacheEntry)cache.get(key);
+		  if(cacheEntry != null)
+		  {
+			  Element entry = cachedoc.createElement(ENTRY);
+			  entry.setAttribute(URI, key);
+			  entry.setAttribute(LOCATION, cacheEntry.getLocalFile());
+			  entry.setAttribute(EXPIRATION_TIME, String.valueOf(cacheEntry.getExpirationTime()));
+			  entry.setAttribute(LAST_MODIFIED, String.valueOf(cacheEntry.getLastModified()));
+			  rootelem.appendChild(entry);
+		  }
+	  }
+	  
+	  // Write the cache entry.
+	  TransformerFactory tFactory  = TransformerFactory.newInstance();
+      Transformer transformer = tFactory.newTransformer();
+      Source input = new DOMSource(cachedoc);
+	  IPath stateLocation = Platform.getPluginStateLocation(CachePlugin.getDefault());
+      Result output = new StreamResult(stateLocation.toString() + "/" + CACHE_FILE);
+      transformer.transform(input, output);
+
+      }catch(Exception e)
+	  {
+		  System.out.println("Unable to store internet cache.");
+	  }
+	  cacheInstance = null;
+  }
+  
+  /**
+   * Open the cache. Opening the cache involves parsing the cache XML file in
+   * the plugin state location if it can be read.
+   */
+  protected static void open(IPath cacheLocation)
+  {
+    cacheInstance = new Cache(cacheLocation);
+	  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+      try {
+		  
+		IPath stateLocation = cacheLocation;
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document cachedoc = builder.parse(stateLocation.toString() + "/" + CACHE_FILE);
+		Element rootelem = cachedoc.getDocumentElement();
+		NodeList entries = rootelem.getChildNodes();
+		int numEntries = entries.getLength();
+		for(int i = 0; i < numEntries; i++)
+		{
+			Node entry = entries.item(i);
+			if(entry instanceof Element)
+			{
+				Element e = (Element)entry;
+				if(e.getNodeName().equals(ENTRY))
+				{
+					String uri = e.getAttribute(URI);
+					String location = e.getAttribute(LOCATION);
+					String lm = e.getAttribute(LAST_MODIFIED);
+					String et = e.getAttribute(EXPIRATION_TIME);
+					long lastModified = -1;
+					long expirationTime = -1;
+					try
+					{
+						lastModified = Long.parseLong(lm);
+					}
+					catch(NumberFormatException nfe)
+					{
+					}
+					try
+					{
+						expirationTime = Long.parseLong(et);
+					}
+					catch(NumberFormatException nfe)
+					{
+					}
+					if(uri != null && location != null)
+					{
+					  cacheInstance.cache.put(uri, new CacheEntry(uri, location, lastModified, expirationTime));
+					}
+				}
+			}
+		}
+      }
+	  catch(FileNotFoundException e)
+	  {
+		// If the file doesn't exist in the correct location there is nothing to load. Do nothing.
+	  }
+	  catch(Exception e)
+	  {
+		  System.out.println("Unable to load cache.");
+	  }
+  }
+  
+  /**
+   * Clear all of the entries from the cache. This method also deletes the cache files.
+   */
+  public void clear()
+  {
+	Enumeration keys = cache.keys();
+	while(keys.hasMoreElements())
+	{
+	  String key = (String)keys.nextElement();
+	  
+	  deleteFile(key);
+	}
+	cache.clear();
+  }
+  
+  /**
+   * Delete the cache entry specified by the given URI.
+   * @param uri The URI entry to remove from the catalog.
+   */
+  public void deleteEntry(String uri)
+  {
+	  if(uri == null) return;
+	  
+	  deleteFile(uri);
+	  cache.remove(uri);
+  }
+  
+  /**
+   * Delete the file specified by the URI.
+   * 
+   * @param uri The URI of the file to delete.
+   */
+  protected void deleteFile(String uri)
+  {
+	  CacheEntry cacheEntry = (CacheEntry)cache.get(uri);
+	  if(cacheEntry != null)
+	  {
+		String location = cacheLocation.toString() + "/" + cacheEntry.getLocalFile();
+	    File file = new File(location);
+	    if(!file.delete())
+	    {
+	      System.out.println("Unable to delete file " + location + " from cache.");
+	    } 
+	  }
+  }
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheEntry.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheEntry.java
new file mode 100644
index 0000000..817ec06
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheEntry.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+/**
+ * A cache entry contains a URI, a local file, and a timeout.
+ */
+public class CacheEntry 
+{
+  private String uri;
+  private String localFile;
+  private long lastModified;
+  private long expirationTime;
+  
+  /**
+   * Create a new cache entry.
+   * 
+   * @param uri The remote URI of the cache entry.
+   * @param localFile The local file that contains the cached entry.
+   * @param lastModifie The time this resource was last modified.
+   * @param expirationTime The time in miliseconds that this cache entry will
+   *                       expire.
+   */
+  public CacheEntry(String uri, String localFile, long lastModified, long expirationTime)
+  {
+	this.uri = uri;
+	this.localFile = localFile;
+	this.lastModified = lastModified;
+	this.expirationTime = expirationTime;
+  }
+  
+  /**
+   * The cache entry is expired if its expiration time is less then the
+   * current system time and not equal to -1.
+   * 
+   * @return True if this cached entry has expired, false otherwise.
+   */
+  public boolean hasExpired()
+  {
+	 if(expirationTime != -1 && System.currentTimeMillis() > expirationTime)
+	 {
+	   return true;
+	 }
+	 return false;
+  }
+  
+  /**
+   * Set the time in miliseconds that this cache entry will expire.
+   * 
+   * @param timeout The time in miliseconds that this cache entry will expire.
+   *                -1 indicates that this entry will not expire.
+   */
+  public void setExpiration(long expirationTime)
+  {
+	this.expirationTime = expirationTime;
+  }
+  
+  /**
+   * Get the time at which this cache entry will expire.
+   * 
+   * @return The time at which this cache entry will expire.
+   */
+  public long getExpirationTime()
+  {
+	return expirationTime;
+  }
+  
+  /**
+   * Get the remote URI for this cache entry.
+   * 
+   * @return The remote URI for this cache entry.
+   */
+  public String getURI()
+  {
+	return uri;
+  }
+  
+  /**
+   * Get the local file for this cache entry.
+   * 
+   * @return The local file for this cache entry.
+   */
+  public String getLocalFile()
+  {
+	return localFile;
+  }
+  
+  /**
+   * Get the last time this cache entry was modified.
+   * 
+   * @return The last time this cache entry was modified.
+   */
+  public long getLastModified()
+  {
+	return lastModified;
+  }
+  
+  /**
+   * Set the last time this cache entry was modified.
+   */
+  public void setLastModified(long lastModified)
+  {
+	this.lastModified = lastModified;
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheJob.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheJob.java
new file mode 100644
index 0000000..8108abc
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheJob.java
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+
+/**
+ * A cache job runs once an hour to cache any prespecified resources which
+ * should be cached and any resources for which an attempt was previously 
+ * made to cache them but they were unable to be cached.
+ */
+public class CacheJob extends Job
+{
+  private static final String _UI_CACHE_MONITOR_NAME = "_UI_CACHE_MONITOR_NAME";
+  private static final String _UI_CACHE_MONITOR_CACHING = "_UI_CACHE_MONITOR_CACHING";
+  private String[] specifiedURIsToCache = null;
+  private static final long SCHEDULE_TIME = 3600000;
+
+  /**
+   * Constructor.
+   */
+  public CacheJob()
+  {
+    super(CachePlugin.getResourceString(_UI_CACHE_MONITOR_NAME));
+    //specifiedURIsToCache = ToCacheRegistryReader.getInstance().getURIsToCache();
+  }
+
+  /**
+   * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
+   */
+  protected IStatus run(IProgressMonitor monitor)
+  {
+    Cache cache = Cache.getInstance();
+    String[] uncachedURIs = cache.getUncachedURIs();
+    int numUncachedURIs = uncachedURIs.length;
+    // Special case for the first time the job is run which will attemp to 
+    // cache specified resources.
+    if(specifiedURIsToCache != null)
+    {
+      int numSpecifiedURIs = specifiedURIsToCache.length;
+      String[] temp = new String[numUncachedURIs + numSpecifiedURIs];
+      System.arraycopy(specifiedURIsToCache, 0, temp, 0, numSpecifiedURIs);
+      System.arraycopy(uncachedURIs, 0, temp, numSpecifiedURIs, numUncachedURIs);
+      uncachedURIs = temp;
+      numUncachedURIs = uncachedURIs.length;
+      specifiedURIsToCache = null;
+    }
+
+    cache.clearUncachedURIs();
+    monitor.beginTask(CachePlugin.getResourceString(_UI_CACHE_MONITOR_NAME), numUncachedURIs);
+    try
+    {
+      for(int i = 0; i < numUncachedURIs; i++)
+      {
+        if (monitor.isCanceled())
+        {
+          for(int j = i; j < numUncachedURIs; j++)
+          {
+            cache.addUncachedURI(uncachedURIs[j]);
+          }
+          return Status.CANCEL_STATUS;
+        }
+        String uri = uncachedURIs[i];
+        monitor.subTask(CachePlugin.getResourceString(_UI_CACHE_MONITOR_CACHING, uri));
+        cache.getResource(uri);
+        monitor.worked(1);
+        monitor.subTask("");
+      }
+      monitor.done();
+      return Status.OK_STATUS;
+    } 
+    finally
+    {
+      schedule(SCHEDULE_TIME); // schedule the next time the job should run
+    }
+  }
+
+}
+
+
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePlugin.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePlugin.java
new file mode 100644
index 0000000..9001763
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePlugin.java
@@ -0,0 +1,241 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Preferences;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.eclipse.wst.internet.cache.internal.preferences.PreferenceConstants;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The main plugin class to be used in the desktop.
+ */
+public class CachePlugin extends AbstractUIPlugin 
+{
+  /**
+   * The ID of this plugin.
+   */
+  public static final String PLUGIN_ID = "org.eclipse.wst.internet.cache";
+
+  /**
+   * The shared instance.
+   */
+  private static CachePlugin plugin;
+
+  /**
+   * The cache job caches resources that were not able to be downloaded when requested.
+   */
+  private CacheJob job = null;
+
+  /**
+   * The constructor.
+   */
+  public CachePlugin() 
+  {
+	super();
+	plugin = this;
+  }
+
+  /**
+   * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+   */
+  public void start(BundleContext context) throws Exception 
+  {
+	super.start(context);
+	ToCacheRegistryReader.getInstance().readRegistry();
+	Cache.open(Platform.getPluginStateLocation(this));
+	if (getPluginPreferences().contains(PreferenceConstants.CACHE_ENABLED)) 
+	{
+	  setCacheEnabled(getPluginPreferences().getBoolean(PreferenceConstants.CACHE_ENABLED));
+	} 
+	else 
+	{
+	  // The cache is disabled by default.
+	  setCacheEnabled(false);
+	}
+	
+	// Restore license preferences
+	Preferences prefs = getPluginPreferences();
+	LicenseRegistry registry = LicenseRegistry.getInstance();
+	String[] licenses = registry.getLicenses();
+	int numLicenses = licenses.length;
+	for(int i = 0; i < numLicenses; i++)
+	{
+	  int state = prefs.getInt(licenses[i]);
+	  if(state == LicenseRegistry.LICENSE_AGREE.intValue())
+	  {
+		registry.agreeLicense(licenses[i]);
+	  }
+	  else if(state == LicenseRegistry.LICENSE_DISAGREE.intValue())
+	  {
+		registry.disagreeLicense(licenses[i]);
+	  }
+	}
+  }
+
+  /**
+   * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+   */
+  public void stop(BundleContext context) throws Exception 
+  {
+	// Save the license state information.
+	Preferences prefs = getPluginPreferences();
+	LicenseRegistry registry = LicenseRegistry.getInstance();
+	String[] licenses = registry.getLicenses();
+	int numLicenses = licenses.length;
+	for(int i = 0; i < numLicenses; i++)
+	{
+	  Integer state = registry.getLicenseState(licenses[i]);
+      // For states that have been disagreed to this session store
+	  // them as disagree.
+	  if(state == LicenseRegistry.LICENSE_DISAGREE_THIS_SESSION)
+	  {
+		state = LicenseRegistry.LICENSE_DISAGREE;
+	  }
+	  prefs.setValue(licenses[i], state.intValue());
+	}
+	
+	Cache.getInstance().close();
+	stopJob();
+	super.stop(context);
+	plugin = null;
+  }
+
+  /**
+   * Returns the shared instance.
+   * 
+   * @return The shared instance.
+   */
+  public static CachePlugin getDefault() 
+  {
+	return plugin;
+  }
+
+  /**
+   * Returns the string from the plugin's resource bundle, or 'key' if not found.
+   * 
+   * @param key The key for which the string is requested.
+   * @return The string from the plugin's resource bundle, or 'key' if not found.
+   */
+  public static String getResourceString(String key) 
+  {
+	ResourceBundle bundle = ResourceBundle
+			.getBundle("org.eclipse.wst.internet.cache.internal.CachePluginResources");
+	try 
+	{
+	  return (bundle != null) ? bundle.getString(key) : key;
+	} 
+	catch (MissingResourceException e) 
+	{
+	  return key;
+	}
+  }
+
+  /**
+   * Returns the string from the plugin's resource bundle using the specified
+   * object, or 'key' if not found.
+   * 
+   * @param key The key for which the string is requested.
+   * @param s1 The object to insert into the string.
+   * @return The formatted string.
+   */
+  public static String getResourceString(String key, Object s1) 
+  {
+	return MessageFormat.format(getResourceString(key), new Object[] { s1 });
+  }
+
+  /**
+   * Set whether or not the cache is enabled.
+   * 
+   * @param enabled If true the cache is enabled, if false it is not enabled.
+   */
+  public void setCacheEnabled(boolean enabled) 
+  {
+	getPluginPreferences().setValue(PreferenceConstants.CACHE_ENABLED, enabled);
+	if (enabled) 
+	{
+	  startJob();
+	} 
+	else 
+	{
+	  stopJob();
+	}
+  }
+
+  /**
+   * Returns true if the cache is enabled, false otherwise.
+   * 
+   * @return True if the cache is enabled, false otherwise.
+   */
+  public boolean isCacheEnabled() 
+  {
+	if (getPluginPreferences().contains(PreferenceConstants.CACHE_ENABLED))
+	  return getPluginPreferences().getBoolean(PreferenceConstants.CACHE_ENABLED);
+	return true;
+  }
+  
+  /**
+   * Set whether or not the user should be prompted for licenses to which they 
+   * have previously disagreed.
+   * 
+   * @param prompt If true the the user should be prompted, if false the user should not be prompted.
+   */
+  public void setPromptDisagreedLicenses(boolean prompt) 
+  {
+	getPluginPreferences().setValue(PreferenceConstants.PROMPT_DISAGREED_LICENSES, prompt);
+  }
+
+  /**
+   * Returns true if the the user should be prompted for licenses to which they
+   * have previously disagreed, false otherwise.
+   * 
+   * @return True if the user should be prompted, false otherwise.
+   */
+  public boolean shouldPrompt() 
+  {
+	if (getPluginPreferences().contains(PreferenceConstants.PROMPT_DISAGREED_LICENSES))
+	  return getPluginPreferences().getBoolean(PreferenceConstants.PROMPT_DISAGREED_LICENSES);
+	return true;
+  }
+
+  /**
+   * Start the cache job. The cache job caches resources that were not able to be previously
+   * downloaded.
+   */
+  private void startJob() 
+  {
+	if (job == null) 
+	{
+	  job = new CacheJob();
+	  job.setPriority(CacheJob.DECORATE);
+	  job.schedule(); // start as soon as possible
+	}
+  }
+
+  /**
+   * Stop the cache job. The cache job caches resources that were not able to be previously
+   * downloaded.
+   */
+  private void stopJob() 
+  {
+	if (job != null) 
+	{
+	  job.cancel();
+	}
+	job = null;
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePluginResources.properties b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePluginResources.properties
new file mode 100644
index 0000000..05729d2
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CachePluginResources.properties
@@ -0,0 +1,41 @@
+###############################################################################
+# 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
+###############################################################################
+
+# Cache preference page strings.
+_UI_CONFIRM_CLEAR_CACHE_DIALOG_TITLE             = Remove All?
+_UI_CONFIRM_CLEAR_CACHE_DIALOG_MESSAGE           = Remove all cache entries?
+_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_TITLE      = Remove Entries?
+_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_MESSAGE    = Remove all selected cache entries?
+_UI_BUTTON_CLEAR_CACHE                           = Remove &All
+_UI_BUTTON_DELETE_ENTRY                          = &Remove
+_UI_PREF_CACHE_ENTRIES_TITLE                     = &Cache entries:
+_UI_PREF_CACHE_CACHE_OPTION                      = &Disable caching
+_UI_PREF_CACHE_ABOUT                             = Manage the cache of remote resources, such as those downloaded from the internet.
+_UI_PREF_PROMPT_FOR_DISAGREED_LICENSES           = &Prompt me for agreement for licenses for whose terms I have already disagreed.
+
+# Cache monitor strings.
+_UI_CACHE_MONITOR_NAME                           = Caching Remote Resources
+_UI_CACHE_MONITOR_CACHING                        = Caching {0}
+
+# Cache license dialog
+_UI_CACHE_DIALOG_LICENSE_STATEMENT1              = A request has been made to cache the resource:
+_UI_CACHE_DIALOG_LICENSE_STATEMENT2              = In order to cache the resource you must agree to the license below:
+_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_INTERNAL  = In order to cache the resource you must agree to the license which has been opened in your browser.
+_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_BROWSER   = The license cannot be displayed in this dialog. In order to cache the resource you must agree to the license located at {0}.
+_UI_CACHE_DIALOG_AGREE_BUTTON                    = I Agree
+_UI_CACHE_DIALOG_DISAGREE_BUTTON                 = I Disagree
+_UI_CACHE_DIALOG_TITLE                           = License Agreement
+
+# Cache logging messages
+_LOG_INFO_WTP_NO_USER_INTERACTION                = {0} is set. Licenses dialogs will not be displayed.
+
+# WTP test no user interaction system property
+WTP_NO_USER_INTERACTION_SYSTEM_PROP              = wtp.autotest.noninteractive
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheURIResolverExtension.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheURIResolverExtension.java
new file mode 100644
index 0000000..f6518f2
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/CacheURIResolverExtension.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension;
+import org.eclipse.wst.common.uriresolver.internal.util.URIHelper;
+
+/**
+ * A cache URI resolver. This resolver will cache remote resources and return
+ * the local copy if they can be cached. If a resource cannot be cached the
+ * resource returns null.
+ */
+public class CacheURIResolverExtension implements URIResolverExtension 
+{
+	/**
+	 * @see org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension#resolve(org.eclipse.core.resources.IProject, java.lang.String, java.lang.String, java.lang.String)
+	 */
+	public String resolve(IFile file, String baseLocation, String publicId, String systemId)
+	{ 
+      if(CachePlugin.getDefault().isCacheEnabled())
+      {
+		  String resource = null;
+		  if(systemId != null)
+		  {
+		    resource = URIHelper.normalize(systemId, baseLocation, null);
+		  } 
+		  
+		  if(resource != null && (resource.startsWith("http:") || resource.startsWith("ftp:")))
+		  {
+		    // Handle resources prespecified to cache.
+		    ToCacheResource toCacheResource = ToCacheRegistryReader.getInstance().getResourceToCache(resource);
+		    if(toCacheResource == null || LicenseRegistry.getInstance().hasLicenseBeenAccepted(resource, toCacheResource.getLicense()))
+		    { 	
+		      return Cache.getInstance().getResource(resource);
+		    }
+		  }
+      }
+	  return null;
+	}
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseAcceptanceDialog.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseAcceptanceDialog.java
new file mode 100644
index 0000000..a26a254
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseAcceptanceDialog.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Hashtable;
+
+import org.eclipse.jface.dialogs.IconAndMessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.browser.Browser;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * A dialog that prompts the user to accept a license agreement.
+ */
+public class LicenseAcceptanceDialog extends IconAndMessageDialog 
+{
+  /**
+   * Externalized string keys.
+   */
+  private static final String _UI_CACHE_DIALOG_LICENSE_STATEMENT1 = "_UI_CACHE_DIALOG_LICENSE_STATEMENT1";
+  private static final String _UI_CACHE_DIALOG_LICENSE_STATEMENT2 = "_UI_CACHE_DIALOG_LICENSE_STATEMENT2";
+  private static final String _UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_INTERNAL = "_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_INTERNAL";
+  private static final String _UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_BROWSER = "_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_BROWSER";
+  private static final String _UI_CACHE_DIALOG_AGREE_BUTTON = "_UI_CACHE_DIALOG_AGREE_BUTTON";
+  private static final String _UI_CACHE_DIALOG_DISAGREE_BUTTON = "_UI_CACHE_DIALOG_DISAGREE_BUTTON";
+  private static final String _UI_CACHE_DIALOG_TITLE = "_UI_CACHE_DIALOG_TITLE";
+
+  /**
+   * Holds all the dialogs that are currently displayed keyed by the license URL.
+   */
+  private static Hashtable dialogsInUse = new Hashtable();
+  
+  /**
+   * The URL of the resource.
+   */
+  private String url;
+
+  /**
+   * The URL of the license.
+   */
+  private String licenseURL;
+  
+  /**
+   * Constructor.
+   * 
+   * @param parent The parent of this dialog.
+   * @param url The license URL.
+   */
+  protected LicenseAcceptanceDialog(Shell parent, String url, String licenseURL) 
+  {
+    super(parent);
+	this.url = url;
+	this.licenseURL = licenseURL;
+  }
+  
+  /**
+   * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+   */
+  protected void configureShell(Shell shell) 
+  {
+    super.configureShell(shell);
+    shell.setText(CachePlugin.getResourceString(_UI_CACHE_DIALOG_TITLE));
+    shell.setImage(null);
+  }
+
+  /**
+   * @see org.eclipse.jface.dialogs.Dialog#createButtonBar(org.eclipse.swt.widgets.Composite)
+   */
+  protected Control createButtonBar(Composite parent) 
+  {
+	Composite buttonBar = new Composite(parent, SWT.NONE);
+	GridLayout layout = new GridLayout();
+	layout.numColumns = 0;
+	layout.makeColumnsEqualWidth = true;
+	buttonBar.setLayout(layout);
+	GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
+	buttonBar.setLayoutData(gd);
+	
+	// Create the agree button.
+	createButton(buttonBar, LicenseAcceptanceDialog.OK, 
+			CachePlugin.getResourceString(_UI_CACHE_DIALOG_AGREE_BUTTON), false);
+
+	// Create the disagree button.
+	createButton(buttonBar, LicenseAcceptanceDialog.CANCEL, 
+			CachePlugin.getResourceString(_UI_CACHE_DIALOG_DISAGREE_BUTTON), false);
+	
+	return buttonBar;
+  }
+
+  /**
+   * @see org.eclipse.jface.window.Window#createContents(org.eclipse.swt.widgets.Composite)
+   */
+  protected Control createContents(Composite parent) 
+  {
+	Composite composite = new Composite(parent, SWT.NONE);
+	GridLayout layout = new GridLayout();
+	composite.setLayout(layout);
+	GridData gd = new GridData(SWT.FILL);
+	gd.widthHint = 500;
+	composite.setLayoutData(gd);
+
+	// Display a statement about the license.
+	Label licenseText1 = new Label(composite, SWT.NONE);
+	licenseText1.setText(CachePlugin.getResourceString(_UI_CACHE_DIALOG_LICENSE_STATEMENT1));
+	Label urlText = new Label(composite, SWT.WRAP);
+	gd = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1);
+	urlText.setLayoutData(gd);
+	urlText.setText(url);
+	new Label(composite, SWT.NONE); // Spacing label.
+	Label licenseText2 = new Label(composite, SWT.WRAP);
+	gd = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1);
+	licenseText2.setLayoutData(gd);
+	
+	// Display the license in a browser.
+	try
+	{
+	  Browser browser = new Browser(composite, SWT.BORDER);
+	  gd = new GridData(GridData.FILL_BOTH);
+	  gd.heightHint = 400;
+	  browser.setUrl(licenseURL);
+	  browser.setLayoutData(gd);
+	  licenseText2.setText(CachePlugin.getResourceString(_UI_CACHE_DIALOG_LICENSE_STATEMENT2));
+	}
+	catch(Throwable e)
+	{
+	  // The browser throws an exception on platforms that do not support it. 
+	  // In this case we need to create an external browser.
+	  try
+	  {
+	    CachePlugin.getDefault().getWorkbench().getBrowserSupport().getExternalBrowser().openURL(new URL(licenseURL));
+	    licenseText2.setText(CachePlugin.getResourceString(_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_INTERNAL));
+	  }
+	  catch(Exception ex)
+	  {
+		// In this case the license cannot be display. Inform the user of this and give them the license location.
+		licenseText2.setText(CachePlugin.getResourceString(_UI_CACHE_DIALOG_LICENSE_STATEMENT2_NO_BROWSER, licenseURL));
+	  }
+	}
+
+	createButtonBar(composite);
+		
+	return composite;
+  }
+
+  /**
+   * @see org.eclipse.jface.dialogs.IconAndMessageDialog#getImage()
+   */
+  protected Image getImage() 
+  {
+	return getInfoImage();
+  }
+
+  /**
+   * Prompt the user to accept the specified license. This method creates the
+   * dialog and returns the result.
+   * 
+   * @param parent The parent of this dialog.
+   * @param url The URL of the resource for which the license must be accepted.
+   * @param licenseURL The license URL.
+   * @return True if the license is accepted, false otherwise.
+   */
+  public static boolean promptForLicense(Shell parent, String url, String licenseURL) throws IOException
+  {
+	boolean agreedToLicense = false;
+	boolean newDialog = true;
+	LicenseAcceptanceDialog dialog = null;
+	// If the dialog is already displayed for this license use it instead of 
+	// displaying another dialog.
+	if(dialogsInUse.containsKey(licenseURL))
+	{
+	  newDialog = false;
+	  dialog = (LicenseAcceptanceDialog)dialogsInUse.get(licenseURL);
+	}
+	else
+	{
+	  //BufferedReader bufreader = null;
+	  InputStream is = null;
+//	  StringBuffer source = new StringBuffer();
+	  try
+	  {
+	    URL urlObj = new URL(licenseURL);
+	    is = urlObj.openStream();
+//        if (urlObj != null)
+//        {
+//          bufreader = new BufferedReader(new InputStreamReader(urlObj.openStream()));
+//
+//          if (bufreader != null)
+//          {
+//            while (bufreader.ready())
+//            {
+//              source.append(bufreader.readLine());
+//            }
+//          }
+//        } 
+	    dialog = new LicenseAcceptanceDialog(parent, url, licenseURL);
+	    dialogsInUse.put(licenseURL, dialog);
+	    dialog.setBlockOnOpen(true);
+	  }
+	  catch(Exception e)
+	  {
+		throw new IOException("The license cannot be opened.");
+	  }
+	  finally
+	  {
+//		if(bufreader != null)
+//		{
+//		  bufreader.close();
+//		}
+		if(is != null)
+		{
+		  try
+		  {
+			is.close();
+		  }
+		  catch(IOException e)
+		  {
+		    // Do nothing.
+		  }
+		}
+	  }
+	}
+	if(dialog != null)
+	{
+	  dialog.open();
+	  
+	  if (dialog.getReturnCode() == LicenseAcceptanceDialog.OK) 
+	  {
+		agreedToLicense = true;
+      }
+		
+	  if(newDialog)
+	  {
+       dialogsInUse.remove(licenseURL);
+	  }
+	}
+	
+	
+	 
+	return agreedToLicense;
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseRegistry.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseRegistry.java
new file mode 100644
index 0000000..6a66bc5
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/LicenseRegistry.java
@@ -0,0 +1,252 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import java.util.Hashtable;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * The license registry holds all of the registered licenses and whether or not they
+ * have been accepted.
+ */
+public class LicenseRegistry 
+{
+  protected static Integer LICENSE_UNSPECIFIED = new Integer(0); // This needs to be 0 for the plugin prefs.
+  protected static Integer LICENSE_AGREE = new Integer(1);
+  protected static Integer LICENSE_DISAGREE = new Integer(2);
+  // Signifies a license that has been disagreed to this session.
+  protected static Integer LICENSE_DISAGREE_THIS_SESSION = new Integer(3);
+  
+  protected final static String _LOG_INFO_WTP_NO_USER_INTERACTION = "_LOG_INFO_WTP_NO_USER_INTERACTION";
+  
+  protected final static String WTP_NO_USER_INTERACTION_SYSTEM_PROP = CachePlugin.getResourceString("WTP_NO_USER_INTERACTION_SYSTEM_PROP");
+  
+  /**
+   * There is only one instance of the license registry.
+   */
+  protected static LicenseRegistry instance = null;
+  
+  /**
+   * If set to true, the do not prompt flag will prevent user prompting. Used for automated testing.
+   */
+  private boolean DO_NOT_PROMPT = false;
+  
+  /**
+   * The licenses hashtable contains licenses and whether or not they've been accepted.
+   */
+  protected Hashtable licenses;
+  
+  protected LicenseRegistry()
+  {
+	licenses = new Hashtable();
+	
+	// If the wtp quiet system property is set the DO_NOT_PROMPT flag is set to true.
+	// This is used for automated testing.
+	if(System.getProperty(WTP_NO_USER_INTERACTION_SYSTEM_PROP, "false").equals("true"))
+	{
+	  CachePlugin.getDefault().getLog().log(new Status(IStatus.INFO, CachePlugin.PLUGIN_ID, IStatus.OK, CachePlugin.getResourceString(_LOG_INFO_WTP_NO_USER_INTERACTION, WTP_NO_USER_INTERACTION_SYSTEM_PROP), null));
+	  DO_NOT_PROMPT = true;
+	}
+  }
+  
+  /**
+   * Get the one and only instance of the license registry.
+   * 
+   * @return The one and only instance of the license registry.
+   */
+  public static LicenseRegistry getInstance()
+  {
+	if(instance == null)
+	{
+	  instance = new LicenseRegistry();
+	}
+	return instance;
+  }
+  
+  /**
+   * Add the specified license to the license registry. A license can only be added to the
+   * registry once.
+   * 
+   * @param url The URL of the license to add to the registry.
+   */
+  public void addLicense(String url)
+  {
+	if(url != null && !licenses.containsKey(url))
+	{
+	  licenses.put(url, LICENSE_UNSPECIFIED);
+	}
+  }
+  
+  /**
+   * Agree to the specified license. The license is agreed to only if it has already
+   * been registered with the registry.
+   * 
+   * @param url The URL of the license to accept.
+   */
+  protected void agreeLicense(String url)
+  {
+	if(licenses.containsKey(url))
+	{
+	  licenses.put(url, LICENSE_AGREE);
+	}
+  }
+  
+  /**
+   * Disagree to the specified license. The license is disagreed to only if it has already
+   * been registered with the registry.
+   * 
+   * @param url The URL of the license to accept.
+   */
+  protected void disagreeLicense(String url)
+  {
+	if(licenses.containsKey(url))
+	{
+	  licenses.put(url, LICENSE_DISAGREE);
+	}
+  }
+  
+  /**
+   * Disagree to the specified license for this session. The license is disagreed to only if it has already
+   * been registered with the registry. 
+   * 
+   * @param url The URL of the license to accept.
+   */
+  private void disagreeLicenseThisSession(String url)
+  {
+	if(licenses.containsKey(url))
+	{
+	  licenses.put(url, LICENSE_DISAGREE_THIS_SESSION);
+	}
+  }
+  
+  /**
+   * Determine if the license has been accepted. This method will return true if the license
+   * has been accepted or is not registered with the registry and false if it has not been accepted. 
+   * 
+   * @param url The URL of the resource for which we are checking the license.
+   * @param licenseURL The URL of the license that should be checked to see if it has been accepted.
+   * @return True if the license has been accepted or is not registered with the registry, false otherwise.
+   */
+  public boolean hasLicenseBeenAccepted(String url, String licenseURL)
+  {
+	if(DO_NOT_PROMPT)
+	{
+	  return true;
+	}
+	
+	if(licenses.containsKey(licenseURL))
+	{
+	  Integer agreed = (Integer)licenses.get(licenseURL);
+	  if(agreed == LICENSE_AGREE)
+	  {
+		return true;
+	  }
+	  else if(agreed == LICENSE_DISAGREE)
+	  {
+		if(!CachePlugin.getDefault().shouldPrompt())
+		  return false;
+		licenses.put(licenseURL, LICENSE_DISAGREE_THIS_SESSION);
+	  }
+	  // The license has already been disagreed to this session. Do not prompt.
+	  else if(agreed == LICENSE_DISAGREE_THIS_SESSION)
+	  {
+		return false;
+	  }
+		  
+	  
+	  // Prompt the user to accept the license.
+	  int licenseAcceptance = promptToAcceptLicense(url, licenseURL);
+	  if(licenseAcceptance == Accepted.ACCEPTED)
+	  {
+		agreeLicense(licenseURL);
+		return true;
+	  }
+	  else if(licenseAcceptance == Accepted.NOT_ACCEPTED)
+	  {
+	    disagreeLicenseThisSession(licenseURL);
+	    return false;
+	  }
+	  return false;
+	}
+	
+	// The license is not registred with the registry.
+	return true;
+  }
+  
+  /**
+   * Prompt the user to accept the license. This method creates a LicenseAcceptanceDialog.
+   * 
+   * @param url The URL of the resource for which the license needs to be accepted.
+   * @param licenseURL The URL of the license to be accepted. 
+   * @return 0 if accepted, 1 if not accepted, 2 if not able to accept.
+   */
+  protected int promptToAcceptLicense(final String url, final String licenseURL) 
+  {
+	final Accepted accepted = new Accepted();
+	  Display.getDefault().syncExec(new Runnable() {
+			public void run() {
+				try
+				{
+				  if(LicenseAcceptanceDialog.promptForLicense(null, url, licenseURL))
+				  {
+					accepted.accepted = Accepted.ACCEPTED;
+				  }
+				  else
+				  {
+					accepted.accepted = Accepted.NOT_ACCEPTED;
+				  }
+				}
+				catch(Exception e)
+				{
+				  accepted.accepted = Accepted.NOT_DETERMINED;
+				}
+			}
+		});
+	return accepted.accepted;
+  }
+  
+  /**
+   * Get an array containing all the licenses in the registry.
+   * 
+   * @return As array containing all the licenses in the registry. 
+   */
+  protected String[] getLicenses()
+  {
+	return (String[])licenses.keySet().toArray(new String[licenses.keySet().size()]);
+  }
+  
+  /**
+   * Get the state of the license.
+   * 
+   * @param url The URL of the license.
+   * @return The state of the license.
+   */
+  protected Integer getLicenseState(String url)
+  {
+	return (Integer)licenses.get(url);
+  }
+  
+  
+  /**
+   * A class to hold acceptance information for the prompt method.
+   */
+  private class Accepted
+  {
+	public static final int ACCEPTED = 0;
+	public static final int NOT_ACCEPTED = 1;
+	public static final int NOT_DETERMINED = 2;
+	public int accepted = NOT_ACCEPTED;
+  }
+
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheRegistryReader.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheRegistryReader.java
new file mode 100644
index 0000000..9673376
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheRegistryReader.java
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+import java.util.Hashtable;
+
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.IExtensionPoint;
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.core.runtime.Platform;
+
+/**
+ * The ToCacheRegistryReaders reads Eclipse extensions which specify
+ * resources to cache. An extension point looks like the following.
+ * 
+ *  <extension point="org.eclipse.wst.internet.cache.cacheresource">
+ *    <cacheresource uri="URI_TO_CACHE" license="URI_OF_LICENSE"/>
+ *  </extension> 
+ *
+ */
+public class ToCacheRegistryReader
+{
+  protected static final String PLUGIN_ID = "org.eclipse.wst.internet.cache";
+  protected static final String EXTENSION_POINT_ID = "cacheresource";
+  protected static final String ATT_URL = "url";
+  protected static final String ATT_LICENSE = "license";
+ 
+  private static ToCacheRegistryReader registryReader = null;
+  
+  private Hashtable resourcesToCache = new Hashtable();
+
+  /**
+   * Get the one and only instance of this registry reader.
+   * 
+   * @return The one and only instance of this registry reader.
+   */
+  public static ToCacheRegistryReader getInstance()
+  {
+    if(registryReader == null)
+    {
+      registryReader = new ToCacheRegistryReader();
+    }
+    return registryReader;
+  }
+  /**
+   * Read from plugin registry and handle the configuration elements that match
+   * the spedified elements.
+   */
+  public void readRegistry()
+  {
+    IExtensionRegistry pluginRegistry = Platform.getExtensionRegistry();
+    IExtensionPoint point = pluginRegistry.getExtensionPoint(PLUGIN_ID, EXTENSION_POINT_ID);
+    if (point != null)
+    {
+      IConfigurationElement[] elements = point.getConfigurationElements();
+      for (int i = 0; i < elements.length; i++)
+      {
+        ToCacheResource toCacheResource = readElement(elements[i]);
+        if(toCacheResource != null)
+        {
+          resourcesToCache.put(toCacheResource.getURL(), toCacheResource);
+          LicenseRegistry.getInstance().addLicense(toCacheResource.getLicense());
+        }
+      }
+    }
+  }
+
+  /**
+   * Parse and deal with the extension points.
+   * 
+   * @param element The extension point element.
+   */
+  protected ToCacheResource readElement(IConfigurationElement element)
+  {
+	
+    if(element.getName().equals(EXTENSION_POINT_ID))
+    {
+      String url = element.getAttribute(ATT_URL);
+      if(url != null)
+      {
+    	String license = element.getAttribute(ATT_LICENSE);
+    	return new ToCacheResource(url, license);
+      }
+    }
+    return null;
+  }
+  
+  /**
+   * Get the list of URIs that have been specified for caching.
+   * 
+   * @return The list of URIs that have been specified for caching.
+   */
+  public String[] getURIsToCache()
+  {
+    return (String[])resourcesToCache.keySet().toArray(new String[resourcesToCache.size()]);
+  }
+  
+  /**
+   * Get the resource to cache if one has been specified.
+   * 
+   * @param url The URL of the resource to cache.
+   * @return A ToCacheResource object representing the URL or null if none has been specified.
+   */
+  public ToCacheResource getResourceToCache(String url)
+  {
+	return (ToCacheResource)resourcesToCache.get(url);
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheResource.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheResource.java
new file mode 100644
index 0000000..3f6778b
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/ToCacheResource.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal;
+
+/**
+ * A resource that is specified to be cached. The resource has a URL and an optional
+ * license.
+ */
+public class ToCacheResource 
+{
+  private String url;
+  private String license;
+  
+  /**
+   * Constructor.
+   * 
+   * @param url The URL of the resource to be cached.
+   * @param license The URL of the optional license for this resource.
+   */
+  public ToCacheResource(String url, String license)
+  {
+	this.url = url;
+	this.license = license;
+  }
+  
+  /**
+   * Get the URL of the resource to be cached.
+   * 
+   * @return The URL of the resource to be cached.
+   */
+  public String getURL()
+  {
+	return url;
+  }
+  
+  /**
+   * Get the license URL of the resource to be cached.
+   * 
+   * @return The license URL of the resource to be cached.
+   */
+  public String getLicense()
+  {
+	return license;
+  }
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/CachePreferencePage.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/CachePreferencePage.java
new file mode 100644
index 0000000..5b8bd6c
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/CachePreferencePage.java
@@ -0,0 +1,323 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal.preferences;
+
+import java.util.Arrays;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.preference.PreferencePage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.List;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.internet.cache.internal.Cache;
+import org.eclipse.wst.internet.cache.internal.CachePlugin;
+
+/**
+ * This class represents a preference page that is contributed to the
+ * Preferences dialog. This page contains options for the cache. The cache can
+ * be disabled, the list of entries in the cache can be viewed, and individual
+ * entries or the entire cache can be deleted.
+ */
+
+public class CachePreferencePage extends PreferencePage implements
+    IWorkbenchPreferencePage
+{
+  private static final String _UI_CONFIRM_CLEAR_CACHE_DIALOG_TITLE = "_UI_CONFIRM_CLEAR_CACHE_DIALOG_TITLE";
+
+  private static final String _UI_CONFIRM_CLEAR_CACHE_DIALOG_MESSAGE = "_UI_CONFIRM_CLEAR_CACHE_DIALOG_MESSAGE";
+
+  private static final String _UI_BUTTON_CLEAR_CACHE = "_UI_BUTTON_CLEAR_CACHE";
+
+  private static final String _UI_BUTTON_DELETE_ENTRY = "_UI_BUTTON_DELETE_ENTRY";
+
+  private static final String _UI_PREF_CACHE_ENTRIES_TITLE = "_UI_PREF_CACHE_ENTRIES_TITLE";
+
+  private static final String _UI_PREF_CACHE_CACHE_OPTION = "_UI_PREF_CACHE_CACHE_OPTION";
+
+  private static final String _UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_TITLE = "_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_TITLE";
+
+  private static final String _UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_MESSAGE = "_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_MESSAGE";
+  
+  private static final String _UI_PREF_CACHE_ABOUT = "_UI_PREF_CACHE_ABOUT";
+  
+  private static final String _UI_PREF_PROMPT_FOR_DISAGREED_LICENSES = "_UI_PREF_PROMPT_FOR_DISAGREED_LICENSES";
+
+  protected Button clearButton;
+
+  protected Button deleteButton;
+
+  protected Button enabledButton;
+  
+  protected Button disagreedLicensesButton;
+
+  protected List entries;
+
+  protected Composite composite = null;
+
+  /**
+   * @see org.eclipse.jface.dialogs.IDialogPage#dispose()
+   */
+  public void dispose()
+  {
+    if (composite != null)
+    {
+      composite.dispose();
+    }
+    super.dispose();
+  }
+
+  /**
+   * Constructor.
+   */
+  public CachePreferencePage()
+  {
+    setPreferenceStore(CachePlugin.getDefault().getPreferenceStore());
+  }
+
+  /**
+   * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
+   */
+  public void init(IWorkbench workbench)
+  {
+  }
+
+  /**
+   * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
+   */
+  protected Control createContents(Composite parent)
+  {
+    noDefaultAndApplyButton();
+
+    composite = new Composite(parent, SWT.NULL);
+    GridLayout layout = new GridLayout();
+    layout.numColumns = 2;
+    layout.horizontalSpacing = convertHorizontalDLUsToPixels(4);
+    layout.verticalSpacing = convertVerticalDLUsToPixels(3);
+    layout.marginWidth = 0;
+    layout.marginHeight = 0;
+    composite.setLayout(layout);
+    GridData gd = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
+    composite.setLayoutData(gd);
+    
+    Label aboutLabel = new Label(composite, SWT.WRAP);
+    aboutLabel.setText(CachePlugin.getResourceString(_UI_PREF_CACHE_ABOUT));
+    GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+    gridData.horizontalSpan = 2;
+    aboutLabel.setLayoutData(gridData);
+    new Label(composite, SWT.None);
+    try
+    {
+      // Created the disable cache option.
+      enabledButton = new Button(composite, SWT.CHECK | SWT.LEFT);
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+      gridData.horizontalSpan = 2;
+      enabledButton.setLayoutData(gridData);
+      enabledButton.setText(CachePlugin
+          .getResourceString(_UI_PREF_CACHE_CACHE_OPTION));
+      enabledButton.setSelection(!CachePlugin.getDefault().getPreferenceStore()
+          .getBoolean(PreferenceConstants.CACHE_ENABLED));
+      enabledButton.addSelectionListener(new SelectionListener()
+      {
+
+        public void widgetDefaultSelected(SelectionEvent e)
+        {
+          widgetSelected(e);
+
+        }
+
+        public void widgetSelected(SelectionEvent e)
+        {
+          boolean disabled = enabledButton.getSelection();
+          CachePlugin.getDefault().setCacheEnabled(!disabled);
+        }
+
+      });
+      
+      disagreedLicensesButton = new Button(composite, SWT.CHECK | SWT.LEFT);
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+      gridData.horizontalSpan = 2;
+      disagreedLicensesButton.setLayoutData(gridData);
+      disagreedLicensesButton.setText(CachePlugin
+          .getResourceString(_UI_PREF_PROMPT_FOR_DISAGREED_LICENSES));
+      disagreedLicensesButton.setSelection(CachePlugin.getDefault().getPreferenceStore()
+          .getBoolean(PreferenceConstants.PROMPT_DISAGREED_LICENSES));
+      disagreedLicensesButton.addSelectionListener(new SelectionListener()
+      {
+
+        public void widgetDefaultSelected(SelectionEvent e)
+        {
+          widgetSelected(e);
+
+        }
+
+        public void widgetSelected(SelectionEvent e)
+        {
+          boolean prompt = disagreedLicensesButton.getSelection();
+          CachePlugin.getDefault().setPromptDisagreedLicenses(prompt);
+        }
+
+      });
+
+      // Create the entities group.
+      Label entriesLabel = new Label(composite, SWT.WRAP);
+      entriesLabel.setText(CachePlugin.getResourceString(_UI_PREF_CACHE_ENTRIES_TITLE));
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
+      gridData.horizontalSpan = 2;
+      entriesLabel.setLayoutData(gridData);
+
+      entries = new List(composite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
+      PlatformUI.getWorkbench().getHelpSystem().setHelp(entries,
+          ContextIds.PREF_ENTRIES);
+      String[] cacheEntries = Cache.getInstance().getCachedURIs();
+      Arrays.sort(cacheEntries);
+      entries.setItems(cacheEntries);
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL
+          | GridData.VERTICAL_ALIGN_FILL);
+      gridData.grabExcessHorizontalSpace = true;
+      gridData.grabExcessVerticalSpace = true;
+      entries.setLayoutData(gridData);
+      entries.addSelectionListener(new SelectionAdapter()
+      {
+        public void widgetSelected(SelectionEvent event)
+        {
+          setPreferenceWidgets();
+        }
+      });
+
+      Composite buttonComposite = new Composite(composite, SWT.NULL);
+      GridLayout gridLayout = new GridLayout();
+      gridLayout.horizontalSpacing = 0;
+      gridLayout.verticalSpacing = convertVerticalDLUsToPixels(3);
+      gridLayout.marginWidth = 0;
+      gridLayout.marginHeight = 0;
+      gridLayout.numColumns = 1;
+      buttonComposite.setLayout(gridLayout);
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_FILL | SWT.TOP);
+      buttonComposite.setLayoutData(gridData);
+      // Create the Delete button
+      deleteButton = new Button(buttonComposite, SWT.PUSH);
+      deleteButton.setText(CachePlugin
+          .getResourceString(_UI_BUTTON_DELETE_ENTRY));
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_BEGINNING);
+      gridData.grabExcessHorizontalSpace = true;
+      deleteButton.setLayoutData(gridData);
+      deleteButton.addSelectionListener(new SelectionAdapter()
+      {
+        public void widgetSelected(SelectionEvent event)
+        {
+          if (MessageDialog
+              .openConfirm(
+                  Display.getDefault().getActiveShell(),
+                  CachePlugin.getResourceString(_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_TITLE),
+                  CachePlugin.getResourceString(_UI_CONFIRM_DELETE_CACHE_ENTRY_DIALOG_MESSAGE)))
+          {
+            String[] selectedEntries = entries.getSelection();
+            int numSelectedEntries = selectedEntries.length;
+
+            Cache cache = Cache.getInstance();
+            for (int i = 0; i < numSelectedEntries; i++)
+            {
+              cache.deleteEntry(selectedEntries[i]);
+            }
+            String[] cacheEntries = cache.getCachedURIs();
+            Arrays.sort(cacheEntries);
+            entries.setItems(cacheEntries);
+            setPreferenceWidgets();
+          }
+        }
+      });
+
+      // Create the Clear Cache button
+      clearButton = new Button(buttonComposite, SWT.PUSH);
+      clearButton.setText(CachePlugin.getResourceString(_UI_BUTTON_CLEAR_CACHE));
+      gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_BEGINNING);
+      gridData.grabExcessHorizontalSpace = true;
+      clearButton.setLayoutData(gridData);
+      clearButton.addSelectionListener(new SelectionAdapter()
+      {
+        public void widgetSelected(SelectionEvent event)
+        {
+          if (MessageDialog.openConfirm(Display.getDefault().getActiveShell(),
+              CachePlugin.getResourceString(_UI_CONFIRM_CLEAR_CACHE_DIALOG_TITLE),
+              CachePlugin.getResourceString(_UI_CONFIRM_CLEAR_CACHE_DIALOG_MESSAGE)))
+          {
+            Cache cache = Cache.getInstance();
+            cache.clear();
+            String[] cacheEntries = cache.getCachedURIs();
+            Arrays.sort(cacheEntries);
+            entries.setItems(cacheEntries);
+            setPreferenceWidgets();
+          }
+        }
+      });
+
+    } catch (Throwable e)
+    {
+      //TODO: Log error
+    }
+    setPreferenceWidgets();
+
+    return composite;
+  }
+
+  /**
+   * Set the preference page widgets. There are a few rules. 1. If disabled, all
+   * of the widgets are diabled except for the disabled check box. 2. If
+   * enabled, all widgets are enabled except a. The delete button is enabled
+   * only if there is a selection in the list. b. The clear button is enabled
+   * only if there are items in the list.
+   */
+  public void setPreferenceWidgets()
+  {
+    if (composite != null && composite.getEnabled())
+    {
+      if (entries.getSelectionCount() > 0)
+      {
+        deleteButton.setEnabled(true);
+      } 
+      else
+      {
+        deleteButton.setEnabled(false);
+      }
+      if (entries.getItemCount() > 0)
+      {
+        clearButton.setEnabled(true);
+      } 
+      else
+      {
+        clearButton.setEnabled(false);
+      }
+      
+    }
+  }
+
+  /*
+   * @see PreferencePage#createControl(Composite)
+   */
+  public void createControl(Composite parent)
+  {
+    super.createControl(parent);
+    PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), ContextIds.PREF); //$NON-NLS-1$
+  }
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/ContextIds.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/ContextIds.java
new file mode 100644
index 0000000..259cafb
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/ContextIds.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 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.wst.internet.cache.internal.preferences;
+
+import org.eclipse.wst.internet.cache.internal.CachePlugin;
+
+/**
+ * Constant ids for context help.
+ */
+public interface ContextIds {
+	public static final String PREF = CachePlugin.PLUGIN_ID + ".cpr0000";
+	public static final String PREF_ENTRIES = CachePlugin.PLUGIN_ID + ".cpr0002";
+}
\ No newline at end of file
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceConstants.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceConstants.java
new file mode 100644
index 0000000..a51c4ae
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceConstants.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal.preferences;
+
+/**
+ * Constant definitions for plug-in preferences
+ */
+public class PreferenceConstants 
+{
+  public static final String CACHE_ENABLED = "cacheEnabled";
+  
+  public static final String PROMPT_DISAGREED_LICENSES = "promptDisagreedLicenses";
+	
+}
diff --git a/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceInitializer.java b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceInitializer.java
new file mode 100644
index 0000000..a7b4af9
--- /dev/null
+++ b/plugins/org.eclipse.wst.internet.cache/src/org/eclipse/wst/internet/cache/internal/preferences/PreferenceInitializer.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2004 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.wst.internet.cache.internal.preferences;
+
+import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer;
+import org.eclipse.jface.preference.IPreferenceStore;
+
+import org.eclipse.wst.internet.cache.internal.CachePlugin;
+
+/**
+ * Class used to initialize default preference values.
+ */
+public class PreferenceInitializer extends AbstractPreferenceInitializer 
+{
+  /**
+   * @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer#initializeDefaultPreferences()
+   */
+  public void initializeDefaultPreferences() 
+  {
+	IPreferenceStore store = CachePlugin.getDefault().getPreferenceStore();
+    store.setDefault(PreferenceConstants.CACHE_ENABLED, true);
+    store.setDefault(PreferenceConstants.PROMPT_DISAGREED_LICENSES, false);
+  }
+}