http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java
 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java
new file mode 100644
index 0000000..7e19122
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestSuite.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.testing_framework.manifest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.jena.shared.JenaException;
+
+/**
+ * Class that runs the Manifest annotated tests.
+ * 
+ * Used with <code>@RunWith( ManifestSuite.class )</code> this class loads a 
+ * manifest test suite file and adds the tests.
+ * <p>
+ * Tests annotated with <code>@RunWith( ManifestSuite.class )</code> must
+ * have a <code>ManifestFile</code> annotation specifying the path to the 
manifest file
+ * </p>
+ */
+public class ManifestSuite extends ParentRunner<Runner> {
+       private static final Logger LOG = LoggerFactory
+                       .getLogger(ManifestSuite.class);
+       private final List<Runner> fRunners;
+       private final ManifestItemHandler itemHandler;
+       private final ManifestFile mf;
+
+       /**
+        * Called reflectively on classes annotated with
+        * <code>@RunWith(Suite.class)</code>
+        * 
+        * @param cls
+        *            the root class
+        * @param builder
+        *            builds runners for classes in the suite
+        * @throws Throwable
+        */
+       public ManifestSuite(Class<? extends ManifestItemHandler> cls,
+                       RunnerBuilder builder) throws Throwable {
+               super(cls);
+
+               List<Throwable> errors = new ArrayList<Throwable>();
+
+               mf = cls.getAnnotation(ManifestFile.class);
+               if (mf == null) {
+                       throw new IllegalStateException(
+                                       "ManifestSuite requries ManifestFile 
annotation");
+               }
+               itemHandler = cls.newInstance();
+
+               Runner[] runner = new Runner[1];
+               try {
+                       runner[0] = oneManifest(new Manifest(mf.value()),
+                                       new ArrayList<Runner>());
+               } catch (JenaException ex) {
+                       runner[0] = new ErrorReportingRunner(null, ex);
+               }
+
+               if (!errors.isEmpty()) {
+                       throw new InitializationError(errors);
+               }
+               fRunners = Collections.unmodifiableList(Arrays.asList(runner));
+       }
+
+       private Runner oneManifest(final Manifest manifest, List<Runner> r) {
+
+               // Recurse
+               for (Iterator<String> iter = manifest.includedManifests(); iter
+                               .hasNext();) {
+                       try {
+                               r.add(oneManifest(new Manifest(iter.next()),
+                                               new ArrayList<Runner>()));
+                       } catch (JenaException ex) {
+                               r.add(new ErrorReportingRunner(null, ex));
+                       }
+               }
+               itemHandler.setTestRunnerList(r);
+               manifest.apply(itemHandler);
+               try {
+                       return new Suite((Class<?>) null, r) {
+
+                               @Override
+                               protected String getName() {
+                                       return manifest.getName();
+                               }
+
+                       };
+               } catch (InitializationError e) {
+                       return new ErrorReportingRunner(null, e);
+               }
+       }
+
+       @Override
+       protected List<Runner> getChildren() {
+               return fRunners;
+       }
+
+       @Override
+       protected Description describeChild(Runner child) {
+               return child.getDescription();
+       }
+
+       @Override
+       protected void runChild(Runner child, RunNotifier notifier) {
+               child.run(notifier);
+       }
+
+       /**
+        * Returns a name used to describe this Runner
+        */
+       @Override
+       protected String getName() {
+               return String.format("%s - %s", super.getName(), mf.value());
+       }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java
 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java
new file mode 100644
index 0000000..cfde240
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTest.java
@@ -0,0 +1,13 @@
+package org.apache.jena.testing_framework.manifest;
+
+public abstract class ManifestTest {
+
+       protected ManifestItem manifestItem;
+
+       public final void setManifestItem(ManifestItem manifestItem) {
+               this.manifestItem = manifestItem;
+       }
+
+       abstract public void runTest();
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java
 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java
new file mode 100644
index 0000000..616b444
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/manifest/ManifestTestRunner.java
@@ -0,0 +1,75 @@
+package org.apache.jena.testing_framework.manifest;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.runner.Description;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+
+public class ManifestTestRunner extends BlockJUnit4ClassRunner {
+
+       private ManifestItem manifestItem;
+
+       public ManifestTestRunner(ManifestItem manifestItem,
+                       Class<? extends ManifestTest> cls) throws 
InitializationError {
+               super(cls);
+               this.manifestItem = manifestItem;
+       }
+
+       /**
+        * Returns the name that describes {@code method} for {@link 
Description}s.
+        * Default implementation is the method's name
+        */
+       @Override
+       protected String testName(FrameworkMethod method) {
+               return manifestItem.getTestName();
+       }
+
+       /**
+        * Returns the methods that run tests. Default implementation returns 
all
+        * methods annotated with {@code @Test} on this class and superclasses 
that
+        * are not overridden.
+        */
+       @Override
+       protected List<FrameworkMethod> computeTestMethods() {
+               FrameworkMethod[] lst = new FrameworkMethod[1];
+
+               try {
+                       lst[0] = new 
FrameworkMethod(getTestClass().getJavaClass()
+                                       .getMethod("runTest")) {
+
+                               @Override
+                               public String getName() {
+                                       return manifestItem.getTestName();
+                               }
+                       };
+               } catch (NoSuchMethodException e) {
+                       throw new IllegalStateException(e);
+               } catch (SecurityException e) {
+                       throw new IllegalStateException(e);
+               }
+               return Arrays.asList(lst);
+       }
+
+       @Override
+       public Description getDescription() {
+               return Description.createTestDescription(this.getTestClass()
+                               .getJavaClass(), manifestItem.getTestName(), 
new Annotation[0]);
+       }
+
+       /**
+        * Returns a new fixture for running a test. Default implementation 
executes
+        * the test class's no-argument constructor (validation should have 
ensured
+        * one exists).
+        */
+       @Override
+       protected Object createTest() throws Exception {
+               ManifestTest instance = (ManifestTest) super.createTest();
+               instance.setManifestItem(manifestItem);
+               return instance;
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java 
b/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java
new file mode 100644
index 0000000..96f3e42
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/package-info.java
@@ -0,0 +1,109 @@
+/*
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+ */
+package org.apache.jena.testing_framework;
+
+/**
+ * Foo set of classes providing support for testing.
+ * <p>
+ * Testing guidelines/suggestions.
+ * </p><p>
+ * Interface tests are built so that developers may test that implementations 
meet the contract
+ * set out in the interface and accompanying documentation.
+ * </p>
+ * <h4>Producers</h4>
+ * <p>
+ * The test and suites use an instance of the [INTERFACE]ProducerInterface to 
create an instance
+ * of the the Object being tested.   
+ * </p>
+ * <h4>Tests</h4>
+ * <p>
+ * Interface tests are noted as Abstract[INTERFACE]Test.  Implementations of 
[INTERFACE] should
+ * create a concrete implementation of Abstract[INTERFACE]Test with an 
[INTERFACE]Producer to create
+ * instances of the Object.  Passing the test indicates a compliance with the 
base interface 
+ * definition.
+ * </p><p>
+ * In general to implement a test requires a few lines of code as is noted in 
the example below
+ * where the new Foo graph implementation is being tested.</p>
+ * <pre><code>
+ * public class FooGraphTest extends AbstractGraphTest {
+ * 
+ *   // the graph producer to use while running
+ *   GraphProducerInterface graphProducer = new FooGraphTest.GraphProducer();
+ * 
+ *   @Override
+ *   protected GraphProducerInterface getGraphProducer() {
+ *     return graphProducer;
+ *   }
+ * 
+ *   // the implementation of the graph producer.
+ *   public static class GraphProducer extends AbstractGraphProducer {
+ * 
+ *     @Override
+ *     protected Graph createNewGraph() {
+ *       return new FooGraph();
+ *     }
+ *   }
+ * }
+ * </code></pre>
+ * <h4>Suites</h4>
+ * <p>
+ * Test suites are named as Abstract[INTERFACE]Suite.  Suites contain several 
tests (see above)
+ * that exercise all of the tests for the components of the object under test. 
 For example the 
+ * graph suite includes tests for the graph itself, the reifier, finding 
literals, recursive 
+ * subgraph extraction, event manager, and transactions.  Running the suites 
is a bit more 
+ * complicated then running the tests.
+ * </p>
+ * Suites are created using the JUnit 4 <code>@RunWith(Suite.class)</code and 
+ * <code>@Suite.SuiteClasses({ })</code> annotations.  This has several 
effects that the developer 
+ * should know about:</p>
+ * <ul>
+ * <li>The suite class does not get instantiated during the run.</li>
+ * <li>The test class names must be known at coding time (not run time) as 
they are listed in the
+ * annotation.</li>
+ * <li>Configuration of the tests has to occur during the static 
initialization phase of class 
+ * loading.</li>
+ * </ul>
+ * <p>
+ * To meet these requirements the AbstractGraphSuite has a static variable 
that holds the instance
+ * of the GraphProducerInterface and a number of local static implementations 
of the Abstract tests
+ * that implement the "getGraphProducer()" method by returning the static 
instance.  The names of 
+ * the local graphs are then used in the @Suite.SuiteClasses annotation.  This 
makes creating an
+ * instance of the AbstractGraphSuite for a graph implementation fairly simple 
as is noted below.
+ * </p>
+ * <pre><code>
+ * public class FooGraphSuite extends AbstractGraphSuite {
+ *   @BeforeClass
+ *   public static void beforeClass() {
+ *     setGraphProducer(new GraphProducer());
+ *   }
+ *   
+ *   public static class GraphProducer extends AbstractGraphProducer {
+ *     @Override
+ *     protected Graph createNewGraph() {
+ *       return new FooGraph();
+ *     }
+ *   }
+ * }
+ * </code></pre>
+ * <p>
+ * <b>Note:</b> that the beforeClass() method is annotated with @BeforeClass.  
the @BeforeClass 
+ * causes it to be run once before any of the test methods in the class. This 
will set the static
+ * instance of the graph producer before the suite is run so that it is 
provided to the enclosed
+ * tests.
+ * </p> 
+ */

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java
 
b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java
new file mode 100644
index 0000000..65aff5b
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleItem.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.testing_framework.tuples;
+
+/**
+ * The unit found in a line of a tuple. Can be a string (quoted, possibly with
+ * the datatype, or unquoted) or a URI.
+ */
+public class TupleItem {
+       public static final int URI = 0;
+       public static final int STRING = 1;
+       public static final int UNKNOWN = 2;
+       public static final int UNQUOTED = 3;
+       public static final int ANON = 4;
+
+       String rep;
+       String datatype;
+       String asFound;
+       int itemType;
+
+       TupleItem(String value, String valAsFound, int type, String dt) {
+               rep = value;
+               asFound = valAsFound;
+               itemType = type;
+               datatype = dt;
+       }
+
+       public int getType() {
+               return itemType;
+       }
+
+       public boolean isURI() {
+               return itemType == URI;
+       }
+
+       public boolean isString() {
+               return itemType == STRING;
+       }
+
+       public boolean isUnknown() {
+               return itemType == UNKNOWN;
+       }
+
+       public boolean isUnquoted() {
+               return itemType == UNQUOTED;
+       }
+
+       public boolean isAnon() {
+               return itemType == ANON;
+       }
+
+       public String get() {
+               return rep;
+       }
+
+       public String getDT() {
+               return datatype;
+       }
+
+       public String asQuotedString() {
+               return asFound;
+       }
+
+       @Override
+       public String toString() {
+               return rep;
+       }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/b293ee8a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java
----------------------------------------------------------------------
diff --git 
a/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java
 
b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java
new file mode 100644
index 0000000..8e78879
--- /dev/null
+++ 
b/jena-core/src/test/java/org/apache/jena/testing_framework/tuples/TupleSet.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.testing_framework.tuples;
+
+import java.io.*;
+import java.util.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TupleSet implements Iterator<List<TupleItem>> {
+       BufferedReader in;
+       public String line = null;
+       public int lineNumber = 0;
+
+       static final char COMMENTCHAR = '#';
+       List<TupleItem> current = null;
+       boolean finished = false;
+
+       protected static Logger logger = 
LoggerFactory.getLogger(TupleSet.class);
+
+       /** Creates new TupleSet */
+       public TupleSet(Reader r) {
+               if (!(r instanceof BufferedReader))
+                       in = new BufferedReader(r);
+               else
+                       in = (BufferedReader) r;
+       }
+
+       @Override
+       public boolean hasNext() {
+               if (finished)
+                       return false;
+
+               if (current == null)
+                       current = tuple();
+               return current != null;
+       }
+
+       @Override
+       public List<TupleItem> next() {
+               if (hasNext()) {
+                       List<TupleItem> x = current;
+                       current = null;
+                       return x;
+               } else
+                       return null;
+       }
+
+       @Override
+       public void remove() {
+               throw new 
java.lang.UnsupportedOperationException("TupleSet.remove");
+       }
+
+       private List<TupleItem> tuple() {
+
+               try {
+                       lineNumber++;
+                       line = in.readLine();
+               } catch (IOException e) {
+               }
+
+               if (line == null) {
+                       finished = true;
+                       return null;
+               }
+
+               // System.out.println("Line: "+line) ;
+               List<TupleItem> tuple = new ArrayList<TupleItem>();
+               int i = 0;
+               int j = 0;
+               boolean errorFound = false;
+
+               tupleLoop: for (;;) {
+                       // Move to beginning of next item.
+                       i = skipwhitespace(line, j);
+
+                       if (i < 0)
+                               break;
+
+                       int iStart = -2; // Points to the beginning of the item 
as found
+                       int jStart = -2; // Points to the item without quotes
+                       int iFinish = -2; // Points after the end of the item 
as found
+                       int jFinish = -2; // Points after the end of the item 
without quotes
+                       int dtStart = -2; // Points to start of datatype (after 
< quote)
+                       int dtFinish = -2; // Points to end of datatype
+                       int type = TupleItem.UNKNOWN;
+
+                       switch (line.charAt(i)) {
+                       case COMMENTCHAR:
+                               break tupleLoop;
+                       case '<':
+                               type = TupleItem.URI;
+                               iStart = i;
+                               jStart = i + 1;
+                               int newPosn = parseURI(i, line);
+                               if (newPosn < 0) {
+                                       errorFound = true;
+                                       break tupleLoop;
+                               }
+                               j = newPosn;
+
+                               iFinish = j + 1;
+                               jFinish = j;
+                               break;
+                       case '"':
+                               type = TupleItem.STRING;
+                               iStart = i;
+                               jStart = i + 1;
+                               boolean inEscape = false;
+                               for (j = i + 1; j < line.length(); j++) {
+                                       char ch = line.charAt(j);
+                                       if (inEscape) {
+                                               // ToDo: escape
+                                               inEscape = false;
+                                               continue;
+                                       }
+                                       // Not an escape
+                                       if (ch == '"')
+                                               break;
+
+                                       if (ch == '\\')
+                                               inEscape = true;
+                                       if (ch == '\n' || ch == '\r') {
+                                               errorFound = true;
+                                               break tupleLoop;
+
+                                       }
+                               }
+
+                               // Malformed
+                               if (j == line.length()) {
+                                       errorFound = true;
+                                       break tupleLoop;
+                               }
+
+                               iFinish = j + 1;
+                               jFinish = j;
+                               // RDF literals may be followed by their type.
+
+                               if (j < line.length() - 3 && line.charAt(j + 1) 
== '^'
+                                               && line.charAt(j + 2) == '^'
+                                               && line.charAt(j + 3) == '<') {
+                                       dtFinish = parseURI(j + 3, line);
+                                       dtStart = j + 4;
+                                       if (dtFinish < 0) {
+                                               errorFound = true;
+                                               break tupleLoop;
+                                       }
+                                       j = dtFinish + 1;
+                                       // String dt = line.substring(dtStart, 
dtFinish) ;
+                                       // System.out.println("I see a 
datatype:"+dt) ;
+                               }
+
+                               break;
+                       case '_':
+                               type = TupleItem.ANON;
+                               iStart = i;
+                               for (j = i + 1; j < line.length(); j++) {
+                                       char ch = line.charAt(j);
+                                       if (ch == ' ' || ch == '\t' || ch == 
'.')
+                                               break;
+                                       if (!Character.isLetterOrDigit(ch) && 
!(ch == '_')
+                                                       && !(ch == ':')) {
+                                               errorFound = true;
+                                               break tupleLoop;
+                                       }
+                               }
+                               iFinish = j;
+                               jStart = iStart;
+                               jFinish = iFinish;
+                               break;
+                       case '.':
+                       case '\n':
+                       case '\r':
+                               return tuple;
+                       default:
+                               type = TupleItem.UNQUOTED;
+                               iStart = i;
+                               jStart = i;
+                               for (j = i + 1; j < line.length(); j++) {
+                                       char ch = line.charAt(j);
+                                       if (ch == ' ' || ch == '\t' || ch == 
'.')
+                                               break;
+
+                                       // if ( ! 
Character.isLetterOrDigit(line.charAt(i)) )
+                                       // {
+                                       // errorFound = true ;
+                                       // break tupleLoop;
+                                       // }
+                               }
+                               // Malformed
+                               if (j == line.length() + 1) {
+                                       errorFound = true;
+                                       break tupleLoop;
+                               }
+                               iFinish = j;
+                               jFinish = j;
+                               break;
+                       }
+                       String item = line.substring(jStart, jFinish);
+                       String literal = line.substring(iStart, iFinish);
+                       String dt = null;
+                       if (dtStart > 0)
+                               dt = line.substring(dtStart, dtFinish);
+
+                       tuple.add(new TupleItem(item, literal, type, dt));
+                       j++;
+                       // End of item.
+               }
+               // End of this line.
+               if (errorFound) {
+                       logger.error("Error in TupleSet.tuple: " + line);
+
+                       String s = "";
+                       int k = 0;
+                       for (; k < i; k++)
+                               s = s + " ";
+                       s = s + "^";
+                       for (; k < j - 1; k++)
+                               s = s + " ";
+                       s = s + "^";
+                       logger.error(s);
+                       return null;
+               }
+
+               if (tuple.size() == 0) {
+                       // Nothing found : loop by tail recursion
+                       return tuple();
+               }
+               return tuple;
+       }
+
+       private int skipwhitespace(String s, int i) {
+               for (; i < s.length(); i++) {
+                       char ch = s.charAt(i);
+                       // Horizonal whitespace
+                       if (ch != ' ' && ch != '\t')
+                               return i;
+               }
+               return -1;
+       }
+
+       private int parseURI(int i, String line) {
+               int j;
+               for (j = i + 1; j < line.length(); j++) {
+                       char ch = line.charAt(j);
+                       if (ch == '>')
+                               break;
+                       if (ch == '\n' || ch == '\r')
+                               return -1;
+               }
+               // Malformed
+               if (j == line.length())
+                       return -2;
+               return j;
+       }
+}

Reply via email to