You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@abdera.apache.org by jm...@apache.org on 2007/11/26 20:13:37 UTC

svn commit: r598389 - in /incubator/abdera/java/trunk: dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/ dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/ dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/ dependencies/i18n...

Author: jmsnell
Date: Mon Nov 26 11:13:35 2007
New Revision: 598389

URL: http://svn.apache.org/viewvc?rev=598389&view=rev
Log:
Add in preliminary URI/IRI Template support to the IRI module. Current draft: http://bitworking.org/projects/URI-Templates/draft-gregorio-uritemplate-02.html

Replace all calls t\o StringBuffer with StringBuilder in the IRI module. I haven't tested this in JDK 1.4.2 to see what effect it has there, but this should help improve overall performance when running under JDK 1.5 or higher.

Added:
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/AbstractContext.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/CachingContext.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Context.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Evaluator.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/HashMapContext.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ObjectContext.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Operation.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Template.java
    incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestTemplate.java
    incubator/abdera/java/trunk/examples/src/main/java/org/apache/abdera/examples/ext/URITemplates.java
Modified:
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/CharUtils.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Escaping.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/HttpScheme.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IDNA.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IRI.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Nameprep.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Punycode.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/Lang.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/Normalizer.java
    incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/UnicodeCharacterDatabase.java
    incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestSuite.java
    incubator/abdera/java/trunk/protocol/src/main/java/org/apache/abdera/protocol/util/EncodingUtil.java

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/CharUtils.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/CharUtils.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/CharUtils.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/io/CharUtils.java Mon Nov 26 11:13:35 2007
@@ -17,6 +17,7 @@
 */
 package org.apache.abdera.i18n.io;
 
+import java.io.IOException;
 import java.util.Arrays;
 
 /**
@@ -118,11 +119,15 @@
     return contains(n,sets);
   }
   
-  public static void append(StringBuffer buf, int c) {
-    if (isSupplementary(c)) {
-      buf.append(getHighSurrogate(c));
-      buf.append(getLowSurrogate(c));
-    } else buf.append((char)c);
+  public static void append(Appendable buf, int c) {
+    try {
+      if (isSupplementary(c)) {
+        buf.append(getHighSurrogate(c));
+        buf.append(getLowSurrogate(c));
+      } else buf.append((char)c);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
   }
   
   public static char getHighSurrogate(int c) {
@@ -176,7 +181,7 @@
     return c;
   }
   
-  public static int charAt(StringBuffer s, int i) {
+  public static int charAt(CharSequence s, int i) {
     char c = s.charAt(i);
     if (c < 0xD800 || c > 0xDFFF) return c;
     if (isHighSurrogate(c)) {
@@ -193,33 +198,49 @@
     return c;
   }
   
-  public static void insert(StringBuffer s, int i, int c) {
-    if (i > 0 && i < s.length()) {
-      char ch = s.charAt(i);
-      boolean low = isLowSurrogate(ch);
-      if (low) {
-        if (low && isHighSurrogate(s.charAt(i-1))) {
-          i--;
-        }
-      }
+  public static void insert(CharSequence s, int i, int c) {
+    if (!(s instanceof StringBuilder) && 
+        !(s instanceof StringBuffer)) { 
+      insert(new StringBuilder(s),i,c);
+    } else {
+      if (i > 0 && i < s.length()) {
+        char ch = s.charAt(i);
+        boolean low = isLowSurrogate(ch);
+        if (low) {
+          if (low && isHighSurrogate(s.charAt(i-1))) {
+            i--;
+          }
+        }
+      }
+      if (s instanceof StringBuffer) 
+        ((StringBuffer)s).insert(i, toString(c));
+      else if (s instanceof StringBuilder)
+        ((StringBuilder)s).insert(i, toString(c));
     }
-    s.insert(i, toString(c));
   }
   
-  public static void setChar(StringBuffer s, int i, int c) {
-    int l = 1;
-    char ch = s.charAt(i);
-    boolean high = isHighSurrogate(ch);
-    boolean low = isLowSurrogate(ch);
-    if (high || low) {
-      if (high && (i+1) < s.length() && isLowSurrogate(s.charAt(i+1))) l++;
-      else {
-        if (low && i > 0 && isHighSurrogate(s.charAt(i-1))) {
-          i--; l++;
+  public static void setChar(CharSequence s, int i, int c) {
+    if (!(s instanceof StringBuilder) && 
+        !(s instanceof StringBuffer)) { 
+      setChar(new StringBuilder(s),i,c);
+    } else {
+      int l = 1;
+      char ch = s.charAt(i);
+      boolean high = isHighSurrogate(ch);
+      boolean low = isLowSurrogate(ch);
+      if (high || low) {
+        if (high && (i+1) < s.length() && isLowSurrogate(s.charAt(i+1))) l++;
+        else {
+          if (low && i > 0 && isHighSurrogate(s.charAt(i-1))) {
+            i--; l++;
+          }
         }
       }
+      if (s instanceof StringBuffer)
+        ((StringBuffer)s).replace(i, i+l, toString(c));
+      else if (s instanceof StringBuilder)
+        ((StringBuilder)s).replace(i, i+l, toString(c));
     }
-    s.replace(i, i+l, toString(c));
   }
   
   public static int size(int c) {
@@ -276,7 +297,7 @@
   }
   
   private static String wrap(String s, char c1, char c2) {
-    StringBuffer buf = new StringBuffer(s);
+    StringBuilder buf = new StringBuilder(s);
     if (buf.length() > 1) {
       if (buf.charAt(0) != c1) buf.insert(0, c1);
       if (buf.charAt(buf.length()-1) != c2) buf.append(c2);
@@ -463,6 +484,13 @@
         }
       }
     ),
+    UNRESERVED(
+      new Check() {
+        public boolean escape(int codepoint) {
+          return !isUnreserved(codepoint);
+        }
+      }
+    ),
     SCHEMESPECIFICPART(
       new Check() {
         public boolean escape(int codepoint) {
@@ -491,6 +519,13 @@
         }        
       }
     ),
+    PCT(
+      new Check() {
+        public boolean escape(int codepoint) {
+          return !CharUtils.isPctEnc(codepoint);
+        }
+      }
+    ),
     STD3ASCIIRULES(
       new Check() {
         public boolean escape(int codepoint) {
@@ -546,7 +581,11 @@
   }
   
   public static boolean isUnreserved(int codepoint) {
-    return isAlphaNum(codepoint) || isMark(codepoint);
+    return isAlphaNum(codepoint) ||  
+           codepoint == '-' || 
+           codepoint == '.' ||
+           codepoint == '_' ||
+           codepoint == '~';
   }
 
   public static boolean isReserved(int codepoint) {

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Escaping.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Escaping.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Escaping.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Escaping.java Mon Nov 26 11:13:35 2007
@@ -18,6 +18,7 @@
 package org.apache.abdera.i18n.iri;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
 
@@ -37,11 +38,15 @@
   
   private Escaping() {}
   
-  private static void encode(StringBuffer sb, byte... bytes) {
-    for (byte c : bytes) {
-      sb.append("%");
-      sb.append(HEX[(c >> 4) & 0x0f]);
-      sb.append(HEX[(c >> 0) & 0x0f]);
+  private static void encode(Appendable sb, byte... bytes) {
+    try {
+      for (byte c : bytes) {
+        sb.append("%");
+        sb.append(HEX[(c >> 4) & 0x0f]);
+        sb.append(HEX[(c >> 0) & 0x0f]);
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
     }
   }
   
@@ -64,9 +69,9 @@
   
   private static boolean check(int codepoint, Profile... profiles) {
     for (Profile profile : profiles) {
-      if (profile.check(codepoint)) return true;
+      if (!profile.check(codepoint)) return false;
     }
-    return false;
+    return true;
   }
   
   public static String encode(
@@ -75,7 +80,7 @@
     Profile... profiles) 
       throws UnsupportedEncodingException {
     if (s == null) return s;
-    StringBuffer sb = new StringBuffer();
+    StringBuilder sb = new StringBuilder();
     char[] chars = s.toCharArray();
     for (int n = 0; n < chars.length; n++) {
       char c = (char) chars[n];
@@ -83,7 +88,7 @@
         encode(sb,String.valueOf(c).getBytes(enc));
       } else if (CharUtils.isHighSurrogate(c)) {
         if (check(c,profiles)) {
-          StringBuffer buf = new StringBuffer();
+          StringBuilder buf = new StringBuilder();
           buf.append(c);
           buf.append(chars[++n]);
           byte[] b = buf.toString().getBytes(enc);

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/HttpScheme.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/HttpScheme.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/HttpScheme.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/HttpScheme.java Mon Nov 26 11:13:35 2007
@@ -35,7 +35,7 @@
 
   @Override
   public IRI normalize(IRI iri) {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     int port = (iri.getPort() == getDefaultPort()) ? -1 : iri.getPort();
     String host = iri.getHost();
     if (host != null) host = host.toLowerCase();

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IDNA.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IDNA.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IDNA.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IDNA.java Mon Nov 26 11:13:35 2007
@@ -98,7 +98,7 @@
       if (regname == null) return null;
       if (regname.length() == 0) return regname;
       String[] labels = regname.split("\\\u002E");
-      StringBuffer buf = new StringBuffer();
+      StringBuilder buf = new StringBuilder();
       for (String label : labels) {        
         label = Nameprep.prep(label);
         char[] chars = label.toCharArray();
@@ -109,7 +109,7 @@
         if (!CharUtils.inRange(chars,(char)0x000,(char)0x007F)) {
           if (label.startsWith("xn--"))
             throw new IOException("ToASCII violation");
-          String pc = Punycode.encode(chars,null).insert(0, "xn--").toString();
+          String pc = "xn--" + Punycode.encode(chars,null);
           chars = pc.toCharArray();
         }
         if (chars.length > 63)
@@ -127,7 +127,7 @@
     if (regname == null) return null;
     if (regname.length() == 0) return regname;
     String[] labels = regname.split("\\\u002E");
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     for (String label : labels) {
       char[] chars = label.toCharArray();
       if (!CharUtils.inRange(chars,(char)0x000,(char)0x007F)) {

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IRI.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IRI.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IRI.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/IRI.java Mon Nov 26 11:13:35 2007
@@ -85,7 +85,7 @@
       this.path = path;
       this.query = query;
       this.fragment = fragment;
-      StringBuffer buf = new StringBuffer();
+      StringBuilder buf = new StringBuilder();
       buildAuthority(buf,userinfo, host, port);
       this.authority = (buf.length()!=0)?buf.toString():null;
       init();
@@ -259,7 +259,7 @@
   }
   
   void buildAuthority(
-    StringBuffer buf, 
+    StringBuilder buf, 
     String aui, 
     String ah, 
     int port) {
@@ -278,7 +278,7 @@
   
   private String buildASCIIAuthority() {
     if (_scheme instanceof HttpScheme) {
-      StringBuffer buf = new StringBuffer();
+      StringBuilder buf = new StringBuilder();
       String aui = getASCIIUserInfo();
       String ah = getASCIIHost();
       int port = getPort();
@@ -324,7 +324,7 @@
     String path,
     String query,
     String fragment) {
-      StringBuffer buf = new StringBuffer();
+      StringBuilder buf = new StringBuilder();
       if (authority != null) {
         buf.append("//");
         buf.append(authority);
@@ -494,7 +494,7 @@
     if (path == null || path.length() == 0) return "/";
     String[] segments = path.split("/");
     if (segments.length < 2) return path;
-    StringBuffer buf = new StringBuffer("/");
+    StringBuilder buf = new StringBuilder("/");
     for (int n = 0; n < segments.length; n++) {
       String segment = segments[n].intern();
       if (segment == ".") {
@@ -529,7 +529,7 @@
       return (!cpath.startsWith("/")) ? "/" + cpath : cpath;
     }
     if (bpath != null && cpath == null) return bpath;
-    StringBuffer buf = new StringBuffer("");
+    StringBuilder buf = new StringBuilder("");
     int n = bpath.lastIndexOf('/');
     if (n > -1) buf.append(bpath.substring(0,n+1));
     if (cpath.length() != 0) buf.append(cpath);
@@ -545,7 +545,7 @@
   }
   
   public String toString() {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     String scheme = getScheme();
     if (scheme != null && scheme.length() != 0) {
       buf.append(scheme);
@@ -557,7 +557,7 @@
   }
   
   public String toASCIIString() {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     String scheme = getScheme();
     if (scheme != null && scheme.length() != 0) {
       buf.append(scheme);

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Nameprep.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Nameprep.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Nameprep.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Nameprep.java Mon Nov 26 11:13:35 2007
@@ -36,7 +36,7 @@
   public static String prep(String s, boolean allowunassigned) {
     NameprepCodepointIterator r = null;
     try {
-      StringBuffer buf = new StringBuffer();
+      StringBuilder buf = new StringBuilder();
       CodepointIterator ci = CodepointIterator.forCharSequence(s);
       r = new NameprepCodepointIterator(ci,allowunassigned);
       while(r.hasNext()) {

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Punycode.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Punycode.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Punycode.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/iri/Punycode.java Mon Nov 26 11:13:35 2007
@@ -79,11 +79,11 @@
     return k + (base - tmin + 1) * delta / (delta + skew);
   }
 
-  public static StringBuffer encode(
+  public static String encode(
     char[] chars,
     boolean[] case_flags) 
       throws IOException {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     CodepointIterator ci = CodepointIterator.forCharArray(chars);
     int n, delta, h, b, bias, m, q, k, t;
     n = initial_n;
@@ -136,7 +136,7 @@
       }
       ++delta; ++n;
     }
-    return buf;
+    return buf.toString();
   }
 
   public static String encode(String s) {
@@ -159,11 +159,11 @@
     }
   }
   
-  public static StringBuffer decode(
+  public static String decode(
     char[] chars, 
     boolean[] case_flags) 
       throws IOException {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     int n, out, i, bias, b, j, in, oldi, w, k, digit, t;
     n = initial_n;
     out = i = 0;
@@ -204,7 +204,7 @@
       }
       CharUtils.insert(buf, i++, n);
     }
-    return buf;
+    return buf.toString();
   }
   
 }

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/Lang.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/Lang.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/Lang.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/lang/Lang.java Mon Nov 26 11:13:35 2007
@@ -89,7 +89,7 @@
   }
 
   public String toString() {
-    StringBuffer buf = new StringBuffer();
+    StringBuilder buf = new StringBuilder();
     for (String s: tags) {
       if (buf.length() > 0) buf.append('\u002D');
       buf.append(s);

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/AbstractContext.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/AbstractContext.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/AbstractContext.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/AbstractContext.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,45 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+/**
+ * Abstract base for custom Context implementations
+ */
+public abstract class AbstractContext 
+  implements Context {
+  
+  protected boolean iri = false;
+  protected boolean normalizing = false;
+  
+  public boolean isIri() {
+    return iri;
+  }
+  
+  public void setIri(boolean isiri) {
+    this.iri = isiri;
+  }
+
+  public boolean isNormalizing() {
+    return normalizing;
+  }
+
+  public void setNormalizing(boolean normalizing) {
+    this.normalizing = normalizing;
+  }
+  
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/CachingContext.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/CachingContext.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/CachingContext.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/CachingContext.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,48 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Abstract Context implementation that caches resolved values so that 
+ * do not have to be resolved again
+ */
+@SuppressWarnings("unchecked") 
+public abstract class CachingContext 
+  extends AbstractContext {
+
+  private Map<String,Object> cache = 
+    new HashMap<String,Object>();
+  
+  public <T> T resolve(String var) {
+    T t = (T) cache.get(var);
+    if (t == null) {
+      t = (T)resolveActual(var);
+      if (t != null) cache.put(var,t);
+    }
+    return t;
+  }
+  
+  protected abstract <T> T resolveActual(String var);
+
+  public void clear() {
+    cache.clear();
+  }
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Context.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Context.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Context.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Context.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,61 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.io.Serializable;
+
+/**
+ * Used to resolve values for template variables
+ */
+public interface Context 
+  extends Cloneable, 
+          Serializable,
+          Iterable<String> {
+
+  /**
+   * Resolve a value for the specified variable.  The method
+   * can return either a String, an Array or a Collection.
+   */
+  <T>T resolve(String var);
+  
+  /**
+   * True if IRI expansion is enabled
+   */
+  boolean isIri();
+  
+  /**
+   * True if IRI expansion is to be enabled
+   */
+  void setIri(boolean isiri);
+  
+  /**
+   * True if replacement values are to be Unicode NFC normalized
+   */
+  boolean isNormalizing();
+  
+  /**
+   * True if replacement values are to be Unicode NFC normalized
+   */
+  void setNormalizing(boolean normalizing);
+  
+  /**
+   * Clear this context
+   */
+  void clear();
+  
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Evaluator.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Evaluator.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Evaluator.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Evaluator.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,78 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Evaluates template tokens
+ */
+@SuppressWarnings("unchecked") 
+final class Evaluator {
+  
+  protected static final Pattern PATTERN =
+    Pattern.compile("(?:-([^\\|]+)\\|)?(?:([^\\|]+)\\|)?(.*)");
+  
+  /**
+   * Returns a listing of variable names specified by the 
+   * given template token
+   */
+  String[] getVariables(String token) {
+    Matcher matcher = PATTERN.matcher(token);
+    if (matcher.find()) {
+      String op = matcher.group(1);
+      String var = matcher.group(3);
+      return Operation.get(op).getVariables(var);
+    }
+    return new String[0];
+  }
+  
+  /**
+   * Writes a plain-text description of the template token
+   */
+  void explain(
+    String token, 
+    StringBuilder buf) {
+    Matcher matcher = PATTERN.matcher(token);
+    if (matcher.find()) {
+      String op = matcher.group(1);
+      String arg = matcher.group(2);
+      String var = matcher.group(3);
+      Operation.get(op).explain(var, arg, buf);
+    }
+  }
+  
+  /**
+   * Evaluates the template token and returns the resolved value
+   */
+  String evaluate(
+    String token, 
+    Context context) {
+      String value = null;
+      Matcher matcher = PATTERN.matcher(token);
+      if (matcher.find()) {
+        String op = matcher.group(1);
+        String arg = matcher.group(2);
+        String var = matcher.group(3);
+        value = Operation.get(op).evaluate(var, arg, context);        
+      }
+      return value != null ? value : "";
+  }
+
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/HashMapContext.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/HashMapContext.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/HashMapContext.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/HashMapContext.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,85 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Context implementation based on a HashMap
+ */
+@SuppressWarnings("unchecked") 
+public final class HashMapContext
+  extends HashMap<String,Object>
+  implements Context {
+  
+  private static final long serialVersionUID = 2206000974505975049L;
+
+  private boolean isiri = false;
+  private boolean normalizing = false;
+  
+  public HashMapContext() {}
+  
+  public HashMapContext(Map<String,Object> map) {
+    super(map);
+  }
+  
+  public <T> T resolve(String var) {
+    return (T)get(var);
+  }
+
+  public boolean isIri() {
+    return isiri;
+  }
+
+  public void setIri(boolean isiri) {
+    this.isiri = isiri;
+  }
+
+  public Iterator<String> iterator() {
+    return keySet().iterator();
+  }
+
+  @Override 
+  public int hashCode() {
+    final int prime = 31;
+    int result = super.hashCode();
+    result = prime * result + (isiri ? 1231 : 1237);
+    return result;
+  }
+
+  @Override 
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (!super.equals(obj)) return false;
+    if (getClass() != obj.getClass()) return false;
+    final HashMapContext other = (HashMapContext) obj;
+    if (isiri != other.isiri) return false;
+    return true;
+  }
+
+  public boolean isNormalizing() {
+    return normalizing;
+  }
+
+  public void setNormalizing(boolean normalizing) {
+    this.normalizing = normalizing;
+  }
+  
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ObjectContext.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ObjectContext.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ObjectContext.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/ObjectContext.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,129 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+@SuppressWarnings("unchecked") 
+public final class ObjectContext 
+  extends CachingContext {
+  
+  private static final long serialVersionUID = -1387599933658718221L;
+  private final Object target;
+  private final Map<String,AccessibleObject> accessors = 
+    new HashMap<String,AccessibleObject>();
+  
+  public ObjectContext(Object object) {
+    this(object,false);
+  }
+  
+  public ObjectContext(Object object, boolean isiri) {
+    if (object == null) throw new IllegalArgumentException();
+    this.target = object;
+    setIri(isiri);
+    initMethods();    
+  }
+  
+  private void initMethods() {
+    Class _class = target.getClass();
+    if (_class.isAnnotation() || 
+        _class.isArray() || 
+        _class.isEnum() || 
+        _class.isPrimitive())
+      throw new IllegalArgumentException();
+    if (!_class.isInterface()) {
+      Field[] fields = _class.getFields();
+      for (Field field : fields) {
+        if (!Modifier.isPrivate(field.getModifiers())) {
+          accessors.put(field.getName().toLowerCase(), field);
+        }
+      }
+    }
+    Method[] methods = _class.getMethods();
+    for (Method method : methods) {
+      String name = method.getName();
+      if (!Modifier.isPrivate(method.getModifiers()) &&
+          method.getParameterTypes().length == 0 && 
+          !method.getReturnType().equals(Void.class) && 
+          !isReserved(name)) {
+        name = name.toLowerCase();
+        if (name.startsWith("get")) name = name.substring(3);
+        else if (name.startsWith("is")) name = name.substring(2);
+        accessors.put(name, method);
+      }
+    }
+  }
+  
+  private boolean isReserved(String name) {
+    return (name.equals("toString") || 
+            name.equals("hashCode") || 
+            name.equals("notify") || 
+            name.equals("notifyAll") || 
+            name.equals("getClass") || 
+            name.equals("wait"));
+  }
+  
+  @Override 
+  protected <T> T resolveActual(String var) {
+    try {
+      var = var.toLowerCase();
+      AccessibleObject accessor = accessors.get(var);
+      if (accessor == null) return null;
+      if (accessor instanceof Method) {
+        Method method = (Method) accessor;
+        return (T)method.invoke(target);
+      } else if (accessor instanceof Field) {
+        Field field = (Field) accessor;
+        return (T)field.get(target);
+      } else return null;
+    } catch (Throwable e) {
+      throw new RuntimeException(e);
+    }
+  }
+  
+  public Iterator<String> iterator() {
+    return accessors.keySet().iterator();
+  }
+
+  @Override 
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((target == null) ? 0 : target.hashCode());
+    return result;
+  }
+
+  @Override 
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    final ObjectContext other = (ObjectContext) obj;
+    if (target == null) {
+      if (other.target != null) return false;
+    } else if (!target.equals(other.target)) return false;
+    return true;
+  }
+    
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Operation.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Operation.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Operation.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Operation.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,378 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.abdera.i18n.io.CharUtils;
+import org.apache.abdera.i18n.iri.Escaping;
+import org.apache.abdera.i18n.unicode.Normalizer;
+
+@SuppressWarnings("unchecked") 
+public abstract class Operation
+  implements Serializable {
+
+  protected final String name;
+  protected final boolean multivar;
+  
+  protected Operation(String name) {
+    this(name, false);
+  }
+  
+  protected Operation(String name, boolean multivar) {
+    this.name = name;
+    this.multivar = multivar;
+  }
+  
+  public final String name() {
+    return name;
+  }
+  
+  public abstract String evaluate(String var, String arg, Context context);
+  
+  public abstract void explain(String var, String arg, StringBuilder buf);
+  
+  public String[] getVariables(String var) {
+    List<String> list = new ArrayList<String>();
+    if (!multivar) {
+      String name = tokenName(var);
+      if (!list.contains(name)) list.add(name);
+    } else {
+      String[] vardefs = var.split("\\+?\\s*,\\s*");
+      for (int n = 0; n < vardefs.length; n++) {
+        String vardef = vardefs[n];
+        String name = vardef.split("=",2)[0];
+        if (!list.contains(name)) list.add(name);
+      }
+    }
+    return list.toArray(new String[list.size()]);
+  }
+  
+  private static Map<String,Operation> operations = getOperations();
+  
+  private static Map<String,Operation> getOperations() {
+    Map<String,Operation> ops = new HashMap<String,Operation>();
+    ops.put("", new DefaultOperation());
+    ops.put("prefix", new PrefixOperation());
+    ops.put("append", new AppendOperation());
+    ops.put("join", new JoinOperation());
+    ops.put("listjoin", new ListJoinOperation());
+    ops.put("opt", new OptOperation());
+    ops.put("neg", new NegOperation());
+    return ops;
+  }
+  
+  public static void register(Operation operation) {
+    operations.put(operation.name(),operation);
+  }
+  
+  public static Operation get(String name) {
+    if (name == null) name = "";
+    Operation op = operations.get(name);
+    if (op != null) return op;
+    throw new UnsupportedOperationException(name);
+  }
+  
+  private static String tokenName(String token) {
+    String[] vardef = token.split("=",2);
+    return vardef[0];
+  }
+  
+  private static String evallist(String token, Context context, String sep) {
+    StringBuilder buf = new StringBuilder();
+    Object value = context.resolve(token);
+    if (value != null) {
+      if (value instanceof String) {
+        String val = (String) value;
+        if (val != null && val.length() > 0)
+          buf.append(encode(val,context.isIri(),context.isNormalizing()));
+      } else if (value.getClass().isArray()) {
+        Object[] values = (Object[])value;
+        for (Object obj : values) {
+          String val = toString(obj);
+          if (val != null && val.length() > 0) {
+            if (buf.length() > 0) buf.append(sep);
+            buf.append(encode(val,context.isIri(),context.isNormalizing()));
+          }
+        }
+      } else if (value instanceof Iterable) {
+        Iterable iterable = (Iterable)value;
+        for (Object obj : iterable) {
+          String val = toString(obj);
+          if (val != null && val.length() > 0) {
+            if (buf.length() > 0) buf.append(sep);
+            buf.append(encode(val,context.isIri(),context.isNormalizing()));
+          }          
+        }
+      }
+    }
+    return buf.toString();
+  }
+  
+  protected static String eval(String token, Context context) {
+    String[] vardef = token.split("=",2);
+    String var = vardef[0];
+    String def = vardef.length > 1 ? vardef[1] : null;
+    Object rep = context.resolve(var);
+    String val = toString(rep);
+    return val != null && val.length() > 0 ? 
+        encode(rep.toString(),context.isIri(),context.isNormalizing()) : 
+        def != null ? 
+          def : null;
+  }
+  
+  private static String toString(Object val) {
+    return val != null ? val.toString() : null;
+  }
+  
+  protected static String eval(String token, String arg, Context context) {
+    String[] vardef = token.split("=",2);
+    String var = vardef[0];
+    String def = vardef.length > 1 ? vardef[1] : null;
+    Object rep = context.resolve(var);
+    if (rep != null) {
+      StringBuilder buf = new StringBuilder();
+      if (rep.getClass().isArray()) {
+        Object[] array = (Object[]) rep;
+        for (Object obj : array) {
+          String val = toString(obj);
+          if (val != null && val.length() > 0) {
+            if (buf.length() > 0) buf.append(arg);
+            buf.append(var);
+            buf.append("=");
+            buf.append(encode(val,context.isIri(),context.isNormalizing()));
+          }
+        }
+      } else if (rep instanceof Iterable) {
+        Iterable list = (Iterable)rep;
+        for (Object obj : list) {
+          String val = toString(obj);
+          if (val != null && val.length() > 0) {
+            if (buf.length() > 0) buf.append(arg);
+            buf.append(var);
+            buf.append("=");
+            buf.append(encode(val,context.isIri(),context.isNormalizing()));
+          }
+        }
+      } else {
+        String val = toString(rep);
+        if (val != null && val.length() > 0) {
+          buf.append(var);
+          buf.append("=");
+          buf.append(encode(val,context.isIri(),context.isNormalizing()));
+        }
+      }
+      return buf.toString();
+    } else if (def != null && def.length() > 0){
+      StringBuilder buf = new StringBuilder();
+      buf.append(var);
+      buf.append("=");
+      buf.append(def);
+      return buf.toString();
+    } else return null;
+  }
+  
+  protected static boolean isdefined(String token, Context context) {
+    String[] vardef = token.split("=",2);
+    String var = vardef[0];
+    String def = vardef.length > 1 ? vardef[1] : null;
+    Object rep = context.resolve(var);
+    if (rep == null) rep = def;
+    if (rep == null) return false;
+    if (rep.getClass().isArray()) {
+      Object[] a = (Object[])rep;
+      return a.length > 0;
+    } else return true;
+  }
+  
+  private static String encode(
+    String val, 
+    boolean isiri, 
+    boolean normalizing) {
+      try {
+        return Escaping.encode(
+            !normalizing ? val : 
+            Normalizer.normalize(
+              val, 
+              Normalizer.Form.C).toString(), 
+            isiri ? 
+              CharUtils.Profile.IUNRESERVED : 
+              CharUtils.Profile.UNRESERVED, 
+            CharUtils.Profile.PCT);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+  }  
+  
+  private static final class DefaultOperation extends Operation {
+    private static final long serialVersionUID = -1279818778391836528L;
+    public DefaultOperation() { super(""); }
+    public String evaluate(String var, String arg, Context context) {
+      return eval(var, context);
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("Replaced with the value of '");
+      buf.append(var);
+      buf.append("'");
+    }
+  }
+  
+  private static final class PrefixOperation extends Operation {
+    private static final long serialVersionUID = 2738115969196268525L;
+    public PrefixOperation() { super("prefix"); }    
+    public String evaluate(String var, String arg, Context context) {
+      String value = eval(var,context);
+      return value == null || value.length() == 0 ? "" : arg != null ? arg + value : value;
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("If '");
+      buf.append(var);
+      buf.append("' is defined then prefix the value of '");
+      buf.append(var);
+      buf.append("' with '");
+      buf.append(arg);
+      buf.append("'");
+    }
+  }
+  
+  private static final class AppendOperation extends Operation {
+    private static final long serialVersionUID = -2742793539643289075L;
+    public AppendOperation() { super("append"); }    
+    public String evaluate(String var, String arg, Context context) {
+      String value = eval(var,context);
+      return value == null || value.length() == 0 ? "" : arg != null ? value + arg : value;
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("If '");
+      buf.append(var);
+      buf.append("' is defined then append '");
+      buf.append(arg);
+      buf.append("' to the value of '");
+      buf.append(var);
+      buf.append("'");
+    }
+  }
+  
+  private static final class JoinOperation extends Operation {
+    private static final long serialVersionUID = -4102440981071994082L;
+    public JoinOperation() { super("join",true); }
+    public String evaluate(String var, String arg, Context context) {
+      StringBuilder buf = new StringBuilder();
+      String[] vardefs = var.split("\\+?\\s*,\\s*");
+      String val = null;
+      for (int n = 0; n < vardefs.length; n++) {
+        String vardef = vardefs[n];
+        val = eval(vardef,arg,context);
+        if (val != null && val.length() > 0) {
+          if (buf.length() > 0) buf.append(arg);
+          buf.append(val);
+        }
+      }
+      String value = buf.toString();
+      return value;
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("Join 'var=value' with '" + arg + "' for each variable in [");
+      String[] vars = getVariables(var);
+      boolean b = false;
+      for (String v : vars) {
+        if (b) buf.append(',');
+        else b = true;
+        buf.append("'");
+        buf.append(v);
+        buf.append("'");
+      }
+      buf.append("]");
+    }
+  }
+  
+  private static final class ListJoinOperation extends Operation {
+    private static final long serialVersionUID = -8314383556644740425L;
+    public ListJoinOperation() { super("listjoin"); }
+    public String evaluate(String var, String arg, Context context) {
+      return evallist(var,context,arg);
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("Join the members of the list '");
+      buf.append(var);
+      buf.append("' together with '");
+      buf.append(arg);
+      buf.append("'");
+    }
+  }
+  
+  private static final class OptOperation extends Operation {  
+    private static final long serialVersionUID = 7808433764609641180L;
+    public OptOperation() { super("opt",true); }
+    public String evaluate(String var, String arg, Context context) {
+      String[] vardefs = var.split("\\s*,\\s*");
+      for (String v : vardefs) {
+        if (isdefined(v,context)) return arg;
+      }
+      return null;
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("If [");
+      String[] vars = getVariables(var);
+      boolean b = false;
+      for (String v : vars) {
+        if (b) buf.append(',');
+        else b = true;
+        buf.append("'");
+        buf.append(v);
+        buf.append("'");
+      }
+      buf.append("] is defined and a string, or a list with one or more members, then insert '");
+      buf.append(arg);
+      buf.append("'");
+    }
+  }
+  
+  private static final class NegOperation extends Operation {    
+    private static final long serialVersionUID = 1936380358902743528L;
+    public NegOperation() { super("neg",true); }
+    public String evaluate(String var, String arg, Context context) {
+      String[] vardefs = var.split("\\s*,\\s*");
+      for (String v : vardefs) {
+        if (!isdefined(v,context)) return arg;
+      }
+      return null;
+    }
+    public void explain(String var, String arg, StringBuilder buf) {
+      buf.append("If [");
+      String[] vars = getVariables(var);
+      boolean b = false;
+      for (String v : vars) {
+        if (b) buf.append(',');
+        else b = true;
+        buf.append("'");
+        buf.append(v);
+        buf.append("'");
+      }
+      buf.append("] is undefined, or a zero length list, then insert '");
+      buf.append(arg);
+      buf.append("'");
+    }
+  }
+  
+}

Added: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Template.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Template.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Template.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/templates/Template.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,283 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.templates;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Used to evaluate a URI Template.  
+ * Instances are immutable, cloneable, serializable and threadsafe.
+ */
+@SuppressWarnings("unchecked") 
+public final class Template
+  implements Iterable<String>, 
+             Cloneable, 
+             Serializable {
+
+  private static final long serialVersionUID = -613907262632631896L;
+  
+  private static final Evaluator EVALUATOR = new Evaluator();
+  private static final Pattern VARIABLE = Pattern.compile("\\{[^{}]+\\}");
+  private static final String TOKEN_START = "\\{";
+  private static final String TOKEN_STOP = "\\}";
+  
+  private final String pattern;
+  private final String[] tokens;
+  private final String[] variables;
+  
+  /**
+   * @param pattern A URI Template
+   */
+  public Template(
+    String pattern) {
+      this.pattern = pattern;
+      this.tokens = initTokens();
+      this.variables = initVariables();
+  }
+
+  /**
+   * Return the URI Template pattern
+   */
+  public String getPattern() {
+    return pattern;
+  }
+  
+  /**
+   * Iterate the template tokens
+   */
+  public Iterator<String> iterator() {
+    return Arrays.asList(tokens).iterator();
+  }
+  
+  /**
+   * Return the array of template variables
+   */
+  private String[] initTokens() {
+    Matcher matcher = VARIABLE.matcher(pattern);
+    List<String> tokens = new ArrayList<String>();
+    while (matcher.find()) {
+      String token = matcher.group();
+      token = token.substring(1,token.length()-1);
+      if (!tokens.contains(token))
+        tokens.add(token);
+    }
+    return tokens.toArray(new String[tokens.size()]);
+  }  
+  
+  private String[] initVariables() {
+    List<String> list = new ArrayList<String>();
+    for (String token : this) {
+      String[] vars = EVALUATOR.getVariables(token);
+      for (String var : vars) {
+        if (!list.contains(var)) list.add(var);
+      }
+    }
+    return list.toArray(new String[list.size()]);
+  }
+  
+  /**
+   * Return the array of template variables
+   */
+  public String[] getVariables() {
+    return variables;
+  }
+  
+  /**
+   * Expand the URI Template using the specified Context. 
+   * @param context The Context impl used to resolve variable values
+   * @return An expanded URI
+   */
+  public String expand(
+    Context context) {
+      String pattern = this.pattern;
+      for(String token : this) {
+        pattern = replace(
+          pattern, 
+          token, 
+          EVALUATOR.evaluate(
+            token, 
+            context));
+      }
+      return pattern;
+  }
+  
+  /**
+   * Expand the URI Template using the non-private fields and methods of
+   * the specified object to resolve the template tokens
+   */
+  public String expand(Object object) {
+    return expand(object,false);
+  }
+
+  /**
+   * Expand the template using the non-private fields and methods of
+   * the specified object to resolve the template tokens. If isiri 
+   * is true, IRI escaping rules will be used.
+   */
+  public String expand(Object object, boolean isiri) {
+    return expand(
+      object instanceof Context ? 
+        (Context)object :
+        object instanceof Map ? 
+          new HashMapContext((Map)object) :
+          new ObjectContext(object,isiri));
+  }
+  
+  private String replace(
+    String pattern, 
+    String token, 
+    String value) {
+      return pattern.replaceAll(
+        TOKEN_START + Pattern.quote(token) + TOKEN_STOP,
+        value);
+  }
+
+  /**
+   * Clone this Template instance
+   */
+  public Template clone() {
+    try {
+      return (Template)super.clone();
+    } catch (Throwable e) {
+      return new Template(pattern);  // not going to happen, but just in case
+    }
+  }
+  
+  @Override 
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
+    return result;
+  }
+
+  @Override 
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+    final Template other = (Template) obj;
+    if (pattern == null) {
+      if (other.pattern != null) return false;
+    } else if (!pattern.equals(other.pattern)) return false;
+    return true;
+  }
+
+  @Override
+  public String toString() {
+   StringBuilder buf = new StringBuilder();
+   buf.append("Template:");
+   buf.append('\n');
+   buf.append("\t"+pattern);
+   buf.append('\n');
+   buf.append('\n');
+   buf.append(" Variables:");
+   buf.append('\n');
+   String[] vars = getVariables();
+   for (String var : vars) {
+     buf.append('\t');
+     buf.append(var);
+     buf.append('\n');
+   }
+   buf.append('\n');
+   buf.append(" Tokens:");
+   buf.append('\n');
+   for (String token : this) {
+     buf.append('\t');
+     buf.append("{" + token + "} \n\t\t ");
+     EVALUATOR.explain(token, buf);
+     buf.append('\n');
+   }
+   buf.append('\n');
+   buf.append(" Example:");
+   buf.append('\n');
+   
+   HashMapContext c = new HashMapContext();
+   for (String var : vars) {
+     c.put(var,"foo");
+     buf.append("\t" + var + " = " + "foo");
+     buf.append('\n');
+   }
+   buf.append('\n');
+   buf.append("\t" + expand(c));
+   
+   buf.append('\n');
+   buf.append('\n');
+   
+   c.clear();
+   for (int i = 0; i < vars.length;i++) {
+     String var = vars[i];
+     if (i % 2 == 1) {
+       c.put(var, "foo");
+       buf.append("\t" + var + " = " + "foo");
+       buf.append('\n');
+     } else {
+       buf.append("\t" + var + " = null");
+       buf.append('\n');       
+     }
+   }
+   buf.append('\n');
+   buf.append("\t" + expand(c));
+   
+   buf.append('\n');
+   buf.append('\n');
+   
+   c.clear();
+   for (int i = 0; i < vars.length;i++) {
+     String var = vars[i];
+     if (i % 2 == 0) {
+       c.put(var, "foo");
+       buf.append("\t" + var + " = " + "foo");
+       buf.append('\n');
+     } else {
+       buf.append("\t" + var + " = null");
+       buf.append('\n');       
+     }
+   }
+   buf.append('\n');
+   buf.append("\t" + expand(c));
+   
+   return buf.toString();
+  }
+  
+  public static String expand(String pattern, Context context) {
+    Template template = new Template(pattern);
+    return template.expand(context);
+  }
+  
+  public static String expand(String pattern, Object object) {
+    return expand(pattern,object,false);
+  }
+  
+  public static String expand(String pattern, Object object, boolean isiri) {
+    Template template = new Template(pattern);
+    return template.expand(object,isiri);
+  }
+  
+  public static String explain(String pattern) {
+    Template template = new Template(pattern);
+    return template.toString();
+  }
+}

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/Normalizer.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/Normalizer.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/Normalizer.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/Normalizer.java Mon Nov 26 11:13:35 2007
@@ -65,41 +65,41 @@
   /**
    * Normalize the string using NFKC
    */
-  public static StringBuffer normalize(String source) throws IOException {
+  public static String normalize(String source) throws IOException {
     return normalize(source, Form.KC);
   }
   
   /**
    * Normalize the string using the specified Form
    */
-  public static StringBuffer normalize(
+  public static String normalize(
     String source, 
     Form form) 
       throws IOException {
-    return normalize(source, form, new StringBuffer());
+    return normalize(source, form, new StringBuilder());
   }
   
   /**
-   * Normalize the string into the given StringBuffer using the given Form
+   * Normalize the string into the given StringBuilder using the given Form
    */
-  public static StringBuffer normalize(
+  public static String normalize(
     String source, 
     Form form, 
-    StringBuffer buf) 
+    StringBuilder buf) 
       throws IOException {
       if (source.length() != 0) {
         decompose(source, form, buf);
         compose(form, buf);
       }
-      return buf;
+      return buf.toString();
   }
   
   private static void decompose(
     String source, 
     Form form, 
-    StringBuffer buf) 
+    StringBuilder buf) 
       throws IOException {
-      StringBuffer internal = new StringBuffer();
+      StringBuilder internal = new StringBuilder();
       CodepointIterator ci = CodepointIterator.forCharSequence(source);
       boolean canonical = form.isCanonical();
       while (ci.hasNext()) {
@@ -117,7 +117,7 @@
   }
   
   private static int findInsertionPoint( 
-    StringBuffer buf, int c) {
+    StringBuilder buf, int c) {
     int cc = UnicodeCharacterDatabase.getCanonicalClass(c);
     int i = buf.length();
     if (cc != 0) {
@@ -132,7 +132,7 @@
   
   private static void compose(
     Form form, 
-    StringBuffer buf) 
+    StringBuilder buf) 
       throws IOException {
     if (!form.isComposition()) return;
     int pos = 0;

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/UnicodeCharacterDatabase.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/UnicodeCharacterDatabase.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/UnicodeCharacterDatabase.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/main/java/org/apache/abdera/i18n/unicode/UnicodeCharacterDatabase.java Mon Nov 26 11:13:35 2007
@@ -2187,7 +2187,7 @@
   public static void decompose(
     int c, 
     boolean canonical, 
-    StringBuffer buf) {
+    StringBuilder buf) {
       if (c >= 44032 && c <= 55203) {
         int z = c - 0xAC00;
         int t = z % 0x001C;

Modified: incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestSuite.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestSuite.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestSuite.java (original)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestSuite.java Mon Nov 26 11:13:35 2007
@@ -30,5 +30,6 @@
     addTestSuite(TestNameprep.class);
     addTestSuite(TestNFKC.class);
     addTestSuite(TestPunycode.class);
+    addTestSuite(TestTemplate.class);
   }
 }

Added: incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestTemplate.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestTemplate.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestTemplate.java (added)
+++ incubator/abdera/java/trunk/dependencies/i18n/src/test/java/org/apache/abdera/i18n/test/iri/TestTemplate.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,270 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.i18n.test.iri;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.abdera.i18n.templates.HashMapContext;
+import org.apache.abdera.i18n.templates.Template;
+
+public final class TestTemplate 
+  extends TestCase {
+
+  public static void test1() throws Exception {
+    String t = "http://bitworking.org/news/{entry}";
+    String e = "http://bitworking.org/news/RESTLog_Specification";
+    HashMapContext c = new HashMapContext();
+    c.put("entry", "RESTLog_Specification");
+    eval(t,e,c);
+  }
+  
+  public static void test2() throws Exception {
+    String t = "http://example.org/wiki/{entry=FrontPage}";
+    String e = "http://example.org/wiki/FrontPage";
+    HashMapContext c = new HashMapContext();
+    eval(t,e,c);
+  }
+  
+  public static void test3() throws Exception {
+    String t = "http://bitworking.org/news/{-listjoin|/|entry}";
+    String e = "http://bitworking.org/news/240/Newsqueak";
+    HashMapContext c = new HashMapContext();
+    c.put("entry", new String[] {"240","Newsqueak"});
+    eval(t,e,c);
+  }
+  
+  public static void test4() throws Exception {
+    String t = "http://technorati.com/search/{term}";
+    String e = "http://technorati.com/search/240%2FNewsqueak";
+    HashMapContext c = new HashMapContext();
+    c.put("term", "240/Newsqueak");
+    eval(t,e,c);
+  }
+  
+  public static void test5() throws Exception {
+    String t = "http://example.org/{fruit=orange}/";
+    String e = "http://example.org/apple/";
+    String f = "http://example.org/orange/";
+    HashMapContext c = new HashMapContext();
+    c.put("fruit","apple");
+    eval(t,e,c);
+    c.remove("fruit");
+    eval(t,f,c);
+  }
+  
+  public static void test6() throws Exception {
+    String t = "bar{-prefix|/|var}/";
+    String e = "bar/foo/";
+    HashMapContext c = new HashMapContext();
+    c.put("var","foo");
+    eval(t,e,c);
+  }
+  
+  public static void test7() throws Exception {
+    String t = "bar/{-append|#home|var}";
+    String e = "bar/foo#home";
+    HashMapContext c = new HashMapContext();
+    c.put("var","foo");
+    eval(t,e,c);
+  }  
+
+  public static void test8() throws Exception {
+    String t = "{-join|&|name,location,age}";
+    String e = "name=joe&location=NYC";
+    HashMapContext c = new HashMapContext();
+    c.put("name","joe");
+    c.put("location","NYC");
+    eval(t,e,c);
+  }  
+
+  public static void test9() throws Exception {
+    String t = "{-listjoin|/|segments}";
+    String e = "a/b/c";
+    HashMapContext c = new HashMapContext();
+    c.put("segments",new String[] {"a","b","c"});
+    eval(t,e,c);
+  }  
+
+  public static void test10() throws Exception {
+    String t = "{-opt|/|segments}";
+    String e = "/";
+    HashMapContext c = new HashMapContext();
+    c.put("segments",new String[] {"a","b","c"});
+    eval(t,e,c);
+  }
+  
+  public static void test11() throws Exception {
+    String t = "{-neg|/|segments}";
+    String e = "";
+    HashMapContext c = new HashMapContext();
+    c.put("segments",new String[] {"a","b","c"});
+    eval(t,e,c);    
+  }  
+  
+  public static void test12() throws Exception {
+    String t = "http://www.google.com/search?q={term}";
+    String e = "http://www.google.com/search?q=ben%26jerrys";
+    HashMapContext c = new HashMapContext();
+    c.put("term","ben&jerrys");  
+    eval(t,e,c);
+    t = "http://www.google.com/search?q={term}";
+    e = "http://www.google.com/search?q=2%2B2%3D5";
+    c = new HashMapContext();
+    c.put("term","2+2=5");  
+    eval(t,e,c);
+    t = "http://www.google.com/base/feeds/snippets/?{-join|&|author}";
+    e = "http://www.google.com/base/feeds/snippets/?author=test%40example.com";
+    c = new HashMapContext();
+    c.put("author","test@example.com");  
+    eval(t,e,c);
+  }
+  
+  public static void test13() throws Exception {
+    String t = "http://www.google.com/search?q={term}";
+    String e = "http://www.google.com/search?q=%C3%8E%C3%B1%C5%A3%C3%A9r%C3%B1%C3%A5%C5%A3%C3%AE%C3%B6%C3%B1%C3%A5%C4%BC%C3%AE%C5%BE%C3%A5%C5%A3%C3%AE%C3%B6%C3%B1";
+    HashMapContext c = new HashMapContext();
+    c.put("term",new String("Îñţérñåţîöñåļîžåţîöñ".getBytes("UTF-8"),"UTF-8"));  // not all java impl's are capable of handling this properly, so we have to force the conversion to UTF-8
+    eval(t,e,c);
+  }
+  
+  public static void test14() throws Exception {
+    String t = "{-opt|/-/|categories}{-listjoin|/|categories}";
+    String e = "/-/A%7C-B/-C";
+    HashMapContext c = new HashMapContext();
+    c.put("categories", new String[] {"A|-B","-C"});
+    eval(t,e,c);
+  }
+  
+  public static void test15() throws Exception {
+    String t = "http://www.google.com/notebook/feeds/{userID}{-prefix|/notebooks/|notebookID}{-opt|/-/|categories}{-listjoin|/|categories}?{-join|&|updated-min,updated-max,alt,start-index,max-results,entryID,orderby}";
+    String e = "http://www.google.com/notebook/feeds/a/notebooks/b?updated-min=c&max-results=d";
+    HashMapContext c = new HashMapContext();
+    c.put("userID", "a");
+    c.put("notebookID","b");
+    c.put("updated-min","c");
+    c.put("max-results", "d");
+    eval(t,e,c);
+  }
+
+  public static void test16() throws Exception {
+    String t = "http://www.google.com/search?q={term}";
+    String e = "http://www.google.com/search?q=Îñţérñåţîöñåļîžåţîöñ";
+    HashMapContext c = new HashMapContext();
+    c.setIri(true);
+    c.put("term",new String("Îñţérñåţîöñåļîžåţîöñ".getBytes("UTF-8"),"UTF-8"));  // not all java impl's are capable of handling this properly, so we have to force the conversion to UTF-8
+    eval(t,e,c);  // use the IriEvaluator so that pct-encoding is done correctly for IRI's
+  }
+  
+  public static void test17() throws Exception {
+    String t = new String("bar{-prefix|/é/|var}/".getBytes("UTF-8"),"UTF-8");
+    String e = new String("bar/é/foo/".getBytes("UTF-8"),"UTF-8");
+    HashMapContext c = new HashMapContext();
+    c.setIri(true);
+    c.put("var","foo");
+    eval(t,e,c);
+  }
+  
+  public static void test18() throws Exception {
+    String t = "http://www.google.com/notebook/feeds/{userID}{-prefix|/notebooks/|notebookID}{-opt|/-/|categories}{-listjoin|/|categories}?{-join|&|updated-min,updated-max,alt,start-index,max-results,entryID,orderby}";
+    Template template = new Template(t);
+    String[] variables = template.getVariables();
+    assertEquals(variables[0],"userID");
+    assertEquals(variables[1],"notebookID");
+    assertEquals(variables[2],"categories");
+    assertEquals(variables[3],"updated-min");
+    assertEquals(variables[4],"updated-max");
+    assertEquals(variables[5],"alt");
+    assertEquals(variables[6],"start-index");
+    assertEquals(variables[7],"max-results");
+    assertEquals(variables[8],"entryID");
+    assertEquals(variables[9],"orderby");
+  }
+
+  public static void test19() throws Exception {
+    String t = "http://www.google.com/notebook/feeds/{userID}{-prefix|/notebooks/|notebookID}{-opt|/-/|categories}{-listjoin|/|categories}?{-join|&|updated-min,updated-max,alt,start-index,max-results,entryID,orderby}";
+    Template template = new Template(t);
+    Template t2 = template.clone();
+    assertEquals(template,t2);
+    assertEquals(template.hashCode(),t2.hashCode());
+  }
+  
+  public static void test20() throws Exception {
+    Map<String,Object> map = new HashMap<String,Object>();
+    map.put("a","foo");
+    map.put("b","bar");
+    map.put("data","10,20,30");
+    map.put("points", new String[] {"10","20", "30"});
+    map.put("list0", new String [0]);
+    map.put("str0","");  
+    map.put("reserved",":/?#[]@!$&'()*+,;=");
+    map.put("u","\u2654\u2655");
+    map.put("a_b","baz");
+    
+    Map<String,String> tests = new HashMap<String,String>();
+    tests.put("http://example.org/?q={a}","http://example.org/?q=foo");
+    tests.put("http://example.org/{foo}","http://example.org/");
+    tests.put("relative/{reserved}/","relative/%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D/");
+    tests.put("http://example.org/{foo=fred}","http://example.org/fred");
+    tests.put("http://example.org/{foo=%25}/","http://example.org/%25/");
+    tests.put("/{-prefix|#|foo}","/");
+    tests.put("./{-prefix|#|str0}","./");
+    tests.put("/{-append|/|a}{-opt|data|points}{-neg|@|a}{-prefix|#|b}","/foo/data#bar");
+    tests.put("http://example.org/q={u}","http://example.org/q=%E2%99%94%E2%99%95");
+    tests.put("http://example.org/?{-join|&|a,data}","http://example.org/?a=foo&data=10%2C20%2C30");
+    tests.put("http://example.org/?d={-listjoin|,|points}&{-join|&|a,b}","http://example.org/?d=10,20,30&a=foo&b=bar");
+    tests.put("http://example.org/?d={-listjoin|,|list0}&{-join|&|foo}","http://example.org/?d=&");
+    tests.put("http://example.org/?d={-listjoin|&d=|points}","http://example.org/?d=10&d=20&d=30");
+    tests.put("http://example.org/{a}{b}/{a_b}","http://example.org/foobar/baz");
+    tests.put("http://example.org/{a}{-prefix|/-/|a}/","http://example.org/foo/-/foo/");
+    
+    for (String t : tests.keySet())
+      assertEquals(Template.expand(t,map),tests.get(t));
+  }
+  
+  public static void test21() throws Exception {
+    String t = "http://example.org/{foo}/{bar}{-opt|/|categories}{-listjoin|/|categories}?{-join|&|baz,tag}";
+    String e = "http://example.org/abc/xyz/a/b?baz=true&tag=x&tag=y&tag=z";
+    assertEquals(Template.expand(t,new MyObject()),e);
+  }
+  
+  public static class MyObject {
+    public String foo = "abc";
+    public String getBar() {
+      return "xyz";
+    }
+    public boolean isBaz() { 
+      return true; 
+    }
+    public String[] getCategories() {
+      return new String[] {"a","b"};
+    }
+    public List<String> getTag() {
+      return Arrays.asList(new String[] {"x","y","z"});
+    }
+  }
+  
+  private static void eval(String t, String e, HashMapContext c) {
+    assertEquals(Template.expand(t,c), e);
+  }
+}
+

Added: incubator/abdera/java/trunk/examples/src/main/java/org/apache/abdera/examples/ext/URITemplates.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/examples/src/main/java/org/apache/abdera/examples/ext/URITemplates.java?rev=598389&view=auto
==============================================================================
--- incubator/abdera/java/trunk/examples/src/main/java/org/apache/abdera/examples/ext/URITemplates.java (added)
+++ incubator/abdera/java/trunk/examples/src/main/java/org/apache/abdera/examples/ext/URITemplates.java Mon Nov 26 11:13:35 2007
@@ -0,0 +1,124 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.examples.ext;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.abdera.i18n.templates.CachingContext;
+import org.apache.abdera.i18n.templates.HashMapContext;
+import org.apache.abdera.i18n.templates.Template;
+
+@SuppressWarnings("unchecked") 
+public final class URITemplates {
+
+  private static final Template template = 
+    new Template("http://example.org{-opt|/~|user}{user}{-opt|/-/|categories}{-listjoin|/|categories}{-opt|?|foo,bar}{-join|&|foo,bar}");
+  
+  public static void main(String... args) throws Exception {
+   
+    // two examples of resolving the template
+    exampleWithObject();
+    exampleIRIWithObject();
+    exampleWithMap();
+    exampleWithHashMapContext();
+    exampleWithCustomContext();
+    
+    // explain the template
+    System.out.println(template);
+  }
+
+  // Using a Java object
+  private static void exampleWithObject() {
+    MyObject myObject = new MyObject();
+    System.out.println(template.expand(myObject));
+  }
+
+  // Using a Java object
+  private static void exampleIRIWithObject() {
+    MyObject myObject = new MyObject();
+    System.out.println(template.expand(myObject,true));
+  }
+  
+  // Using a Map 
+  private static void exampleWithMap() {
+    Map<String,Object> map = new HashMap();
+    map.put("user","james");
+    map.put("categories", new String[] {"a","b","c"});
+    map.put("foo", "abc");
+    map.put("bar", "xyz");    
+    System.out.println(template.expand(map));
+  }
+  
+  // Using a HashMap based context
+  private static void exampleWithHashMapContext() {
+    HashMapContext context = new HashMapContext();
+    context.put("user","james");
+    context.put("categories", new String[] {"a","b","c"});
+    context.put("foo", "abc");
+    context.put("bar", "xyz");    
+    System.out.println(template.expand(context));
+  }
+  
+  // Using a custom context implementation
+  private static void exampleWithCustomContext() {
+    CachingContext context = new CachingContext() { 
+      private static final long serialVersionUID = 4896250661828139020L;
+      protected <T> T resolveActual(String var) {
+        if (var.equals("user")) return (T)"james";
+        else if (var.equals("categories")) return (T)new String[] {"a","b","c"};
+        else if (var.equals("foo")) return (T)"abc";
+        else if (var.equals("bar")) return (T)"xyz";
+        else return null;
+      }
+      public Iterator<String> iterator() {
+        return Arrays.asList(new String[] {"user","categories","foo","bar"}).iterator();
+      }      
+    };
+    System.out.println(template.expand(context));
+  }
+  
+  public static class MyObject {
+    public String user = "james";
+    public List getCategories() {
+      List<String> list = 
+        new ArrayList<String>();
+      list.add("a");
+      list.add("b");
+      list.add("c");
+      return list;
+    }
+    public Foo[] getFoo() {
+      return new Foo[] {
+        new Foo(), 
+        new Foo()
+      };
+    }
+    public String getBar() {
+      return "xyz";
+    }
+  }
+  
+  private static class Foo {
+    public String toString() { return "abcæ"; }
+  }
+}

Modified: incubator/abdera/java/trunk/protocol/src/main/java/org/apache/abdera/protocol/util/EncodingUtil.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/protocol/src/main/java/org/apache/abdera/protocol/util/EncodingUtil.java?rev=598389&r1=598388&r2=598389&view=diff
==============================================================================
--- incubator/abdera/java/trunk/protocol/src/main/java/org/apache/abdera/protocol/util/EncodingUtil.java (original)
+++ incubator/abdera/java/trunk/protocol/src/main/java/org/apache/abdera/protocol/util/EncodingUtil.java Mon Nov 26 11:13:35 2007
@@ -80,10 +80,9 @@
       if (lower) slug = slug.toLowerCase();
       if (form != null) {
         try {
-          StringBuffer value = 
+          slug = 
             Normalizer.normalize(
               slug, form);          
-          slug = value.toString();
         } catch (Exception e) {}
       }
       if (filler != null) {