abdera-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jmsn...@apache.org
Subject svn commit: r1173209 [9/49] - in /abdera/abdera2: ./ .settings/ activities/ activities/src/ activities/src/main/ activities/src/main/java/ activities/src/main/java/org/ activities/src/main/java/org/apache/ activities/src/main/java/org/apache/abdera2/ a...
Date Tue, 20 Sep 2011 15:57:20 GMT
Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControlUtil.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControlUtil.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControlUtil.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControlUtil.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,256 @@
+/*
+ * 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.abdera2.common.http;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.abdera2.common.text.CharUtils;
+
+/**
+ * Provides parsing and properly handling of the HTTP Cache-Control header.
+ */
+public class CacheControlUtil {
+
+    private static long value(String val) {
+        return (val != null) ? Long.parseLong(val) : -1;
+    }
+
+    private static void append(StringBuilder buf, String value) {
+        if (buf.length() > 0)
+            buf.append(", ");
+        buf.append(value);
+    }
+
+    /**
+     * Construct the Cache-Control header from info in the request object
+     */
+    public static String buildCacheControl(CacheControl cacheControl) {
+        StringBuilder buf = new StringBuilder();
+        if (cacheControl.isPrivate()) {
+            append(buf, "private");
+            String[] headers = cacheControl.getPrivateHeaders();
+            if (headers != null && headers.length > 0) {
+              buf.append("=\"");
+              for (int n = 0; n < headers.length; n++) {
+                if (n > 0) buf.append(",");
+                buf.append(headers[n]);
+              }
+              buf.append("\"");
+            }
+        }
+        if (cacheControl.isPublic())
+            append(buf, "public");
+        if (cacheControl.isNoCache()) {
+            append(buf, "no-cache");
+            String[] headers = cacheControl.getNoCacheHeaders();
+            if (headers != null && headers.length > 0) {
+              buf.append("=\"");
+              for (int n = 0; n < headers.length; n++) {
+                if (n > 0) buf.append(",");
+                buf.append(headers[n]);
+              }
+              buf.append("\"");
+            }   
+        }
+        if (cacheControl.isNoStore())
+            append(buf, "no-store");
+        if (cacheControl.isNoTransform())
+            append(buf, "no-transform");
+        if (cacheControl.isOnlyIfCached())
+            append(buf, "only-if-cached");
+        if (cacheControl.isMustRevalidate())
+            append(buf, "must-revalidate");
+        if (cacheControl.isProxyRevalidate())
+            append(buf, "proxy-revalidate");
+        if (cacheControl.getMaxAge() != -1)
+            append(buf, String.format("max-age=%d", cacheControl.getMaxAge()));
+        if (cacheControl.getMaxStale() != -1)
+            append(buf, String.format("max-stale=%d", cacheControl.getMaxStale()));
+        if (cacheControl.getMinFresh() != -1)
+            append(buf, String.format("min-fresh=%d", cacheControl.getMinFresh()));
+        if (cacheControl.getStaleIfError() != -1)
+            append(buf, String.format("stale-if-error=%d", cacheControl.getStaleIfError()));
+        if (cacheControl.getStaleWhileRevalidate() != -1)
+            append(buf, String.format("stale-while-revalidate=%d", cacheControl.getStaleWhileRevalidate()));
+        for (String ext : cacheControl.listExtensions()) {
+          append(buf, ext);
+          Object val = cacheControl.getExtension(ext);
+          if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte) {
+            buf.append('=')
+               .append(val);            
+          } else {
+            String v = val.toString();
+            if (val != null && v.length() > 0)
+              buf.append('=')
+                 .append('"')
+                 .append(val)
+                 .append('"');
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Parse the Cache-Control header
+     */
+    public static void parseCacheControl(String cc, CacheControl cacheControl) {
+        if (cc == null) return;
+        cacheControl.setDefaults();
+        CacheControlParser parser = new CacheControlParser(cc);
+        for (Directive directive : parser)
+          directive.set(cacheControl, parser);
+        cacheControl.setExtensions(parser.getExtensions());
+    }
+
+    /**
+     * Cache Control Directives
+     */
+    public enum Directive {
+        MAXAGE,
+        MAXSTALE, 
+        MINFRESH, 
+        NOCACHE, 
+        NOSTORE, 
+        NOTRANSFORM, 
+        ONLYIFCACHED, 
+        MUSTREVALIDATE, 
+        PROXYREVALIDATE,
+        PRIVATE,
+        PUBLIC, 
+        STALEWHILEREVALIDATE,
+        STALEIFERROR,
+        UNKNOWN;
+
+        public static Directive select(String d) {
+            try {
+                d = d.toUpperCase().replaceAll("-", "");
+                return Directive.valueOf(d);
+            } catch (Exception e) {
+            }
+            return UNKNOWN;
+        }
+        
+        public void set(CacheControl cacheControl, CacheControlParser parser) {
+          switch (this) {
+          case NOCACHE:
+              cacheControl.setNoCache(true);
+              cacheControl.setNoCacheHeaders(parser.getValues(this));
+              break;
+          case NOSTORE:
+              cacheControl.setNoStore(true);
+              break;
+          case NOTRANSFORM:
+              cacheControl.setNoTransform(true);
+              break;
+          case ONLYIFCACHED:
+              cacheControl.setOnlyIfCached(true);
+              break;
+          case MAXAGE:
+              cacheControl.setMaxAge(value(parser.getValue(this)));
+              break;
+          case MAXSTALE:
+              cacheControl.setMaxStale(value(parser.getValue(this)));
+              break;
+          case MINFRESH:
+              cacheControl.setMinFresh(value(parser.getValue(this)));
+              break;
+          case STALEIFERROR:
+              cacheControl.setStaleIfError(value(parser.getValue(this)));
+              break;
+          case MUSTREVALIDATE:
+              cacheControl.setMustRevalidate(true);
+              break;
+          case PROXYREVALIDATE:
+              cacheControl.setProxyRevalidate(true);
+              break;
+          case PUBLIC:
+              cacheControl.setPublic(true);
+              break;
+          case PRIVATE:
+              cacheControl.setPrivate(true);
+              cacheControl.setPrivateHeaders(parser.getValues(this));
+            break;
+          case STALEWHILEREVALIDATE:
+            cacheControl.setStaleWhileRevalidate(value(parser.getValue(this)));
+            break;
+          }
+        }
+    }
+
+    /**
+     * Parser for the Cache-Control header
+     */
+    public static class CacheControlParser 
+      implements Iterable<Directive> {
+
+        private static final String REGEX =
+            "\\s*([\\w\\-]+)\\s*(=)?\\s*(\\d+|\\\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)+\\\")?\\s*";
+
+        private static final Pattern pattern = Pattern.compile(REGEX);
+
+        private final HashMap<Directive, String> values = new HashMap<Directive, String>();
+        private final HashMap<String,Object> exts = new HashMap<String,Object>();
+
+        public CacheControlParser(String value) {
+            Matcher matcher = pattern.matcher(value);
+            while (matcher.find()) {
+                String d = matcher.group(1);
+                Directive directive = Directive.select(d);
+                if (directive != Directive.UNKNOWN) {
+                    values.put(directive, matcher.group(3));
+                } else {
+                  String val = matcher.group(3);
+                  try {
+                    Long l = Long.parseLong(val);
+                    exts.put(d, l);
+                  } catch (Throwable t) {
+                    exts.put(d, val != null ? CharUtils.unquote(val) : "");
+                  }
+                }
+            }
+        }
+
+        public Map<String,Object> getExtensions() {
+          return exts;
+        }
+        
+        public Map<Directive, String> getValues() {
+            return values;
+        }
+
+        public String getValue(Directive directive) {
+            return values.get(directive);
+        }
+
+        public Iterator<Directive> iterator() {
+            return values.keySet().iterator();
+        }
+
+        public String[] getValues(Directive directive) {
+            String value = getValue(directive);
+            return value == null ?
+                null :
+                CharUtils.splitAndTrim(value);
+        }
+
+    }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/CacheControlUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/EntityTag.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/EntityTag.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/EntityTag.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/EntityTag.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,380 @@
+/*
+ * 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.abdera2.common.http;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.abdera2.common.text.UrlEncoding;
+
+/**
+ * Implements an EntityTag.
+ */
+public class EntityTag 
+    implements Cloneable, Serializable, Comparable<EntityTag> {
+
+    private static final long serialVersionUID = 1559972888659121461L;
+
+    private static final String INVALID_ENTITY_TAG = "Invalid entity tag";
+
+    public static final EntityTag WILD = new EntityTag("*");
+
+    public static EntityTag parse(String entity_tag) {
+      if (entity_tag == null || entity_tag.length() == 0)
+          throw new IllegalArgumentException("Invalid");
+      int l = entity_tag.length()-1;
+      boolean wild = entity_tag.charAt(0) == '*' && 
+                     l == 0;
+      if (wild) return EntityTag.WILD;
+      boolean weak = (entity_tag.charAt(0) == 'W' || 
+                     entity_tag.charAt(0) == 'w');
+      if (weak && entity_tag.charAt(1) != '/')
+        throw new IllegalArgumentException("Invalid");
+      int pos = weak?2:0;
+      if (entity_tag.charAt(pos) != '"' || 
+          entity_tag.charAt(l) != '"')
+        throw new IllegalArgumentException("Invalid");
+      String tag = entity_tag.substring(pos+1,l);
+      return new EntityTag(tag, weak, false);
+    }
+
+    public static Iterable<EntityTag> parseTags(String entity_tags) {
+        if (entity_tags == null || 
+            entity_tags.length() == 0)
+            return Collections.emptyList();
+        String[] tags = 
+          entity_tags.split("((?<=\")\\s*,\\s*(?=([wW]/)?\"|\\*))");
+        List<EntityTag> etags = 
+          new ArrayList<EntityTag>();
+        for (String tag : tags) {
+            etags.add(EntityTag.parse(tag.trim()));
+        }
+        return etags;
+    }
+
+    public static boolean matchesAny(EntityTag tag1, String tags) {
+        return matchesAny(tag1, parseTags(tags), false);
+    }
+
+    public static boolean matchesAny(EntityTag tag1, String tags, boolean weak) {
+        return matchesAny(tag1, parseTags(tags), weak);
+    }
+
+    public static boolean matchesAny(String tag1, String tags) {
+        return matchesAny(parse(tag1), parseTags(tags), false);
+    }
+
+    public static boolean matchesAny(String tag1, String tags, boolean weak) {
+        return matchesAny(parse(tag1), parseTags(tags), weak);
+    }
+
+    public static boolean matchesAny(EntityTag tag1, Iterable<EntityTag> tags) {
+        return matchesAny(tag1, tags, false);
+    }
+
+    @SuppressWarnings("unused")
+    private static boolean empty(Iterable<EntityTag> tags) {
+      for (EntityTag e : tags) return false;
+      return true;
+    }
+    
+    public static boolean matchesAny(EntityTag tag1, Iterable<EntityTag> tags, boolean weak) {
+        if (tags == null)
+            return (tag1 == null) ? true : false;
+        if (tag1.isWild() && !empty(tags))
+            return true;
+        for (EntityTag tag : tags) {
+            if (tag1.equals(tag,weak) || tag.isWild())
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean matches(EntityTag tag1, EntityTag tag2) {
+        return tag1.equals(tag2);
+    }
+    
+    public static boolean matches(EntityTag tag1, EntityTag tag2, boolean weak) {
+      return tag1.equals(tag2,weak);
+    }
+
+    public static boolean matches(String tag1, String tag2) {
+        EntityTag etag1 = parse(tag1);
+        EntityTag etag2 = parse(tag2);
+        return etag1.equals(etag2);
+    }
+
+    public static boolean matches(EntityTag tag1, String tag2) {
+        return tag1.equals(parse(tag2));
+    }
+
+    /**
+     * Produces a variation of the entity tag by appending one or 
+     * more labels to the tag. This is helpful when generating 
+     * alternative entity tags for different content-encoding
+     * variations of a resource.
+     */
+    public static EntityTag variation(
+        EntityTag tag, 
+        String label, 
+        String... labels) {
+        if (label == null || tag == null | tag.isWild()) 
+          throw new IllegalArgumentException();
+        StringBuilder buf = 
+          new StringBuilder(tag.getTag());
+        buf.append('-')
+           .append(label);
+        for (String l : labels)
+          buf.append('-')
+             .append(l);
+        return new EntityTag(buf.toString(),tag.isWeak());
+    }
+    
+    private final String tag;
+
+    private final boolean weak;
+    private final boolean wild;
+
+    public EntityTag(String tag) {
+        this(tag, false);
+    }
+
+    public EntityTag(String tag, boolean weak) {
+        EntityTag etag = attemptParse(tag);
+        if (etag == null) {
+            if (tag.indexOf('"') > -1)
+                throw new IllegalArgumentException(INVALID_ENTITY_TAG);
+            this.tag = tag;
+            this.weak = weak;
+            this.wild = tag.equals("*");
+        } else {
+            this.tag = etag.tag;
+            this.weak = etag.weak;
+            this.wild = etag.wild;
+        }
+    }
+
+    private EntityTag(String tag, boolean weak, boolean wild) {
+        this.tag = tag;
+        this.weak = weak;
+        this.wild = wild;
+    }
+
+    public EntityTag variation(String label, String... labels) {
+      return variation(this,label,labels);
+    }
+    
+    private EntityTag attemptParse(String tag) {
+        try {
+            return parse(tag);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public boolean isWild() {
+        return wild;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public boolean isWeak() {
+        return weak;
+    }
+
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        if (wild) {
+            buf.append("*");
+        } else {
+            if (weak)
+                buf.append("W/");
+            buf.append('"')
+               .append(tag)
+               .append('"');
+        }
+        return buf.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((tag == null) ? 0 : tag.hashCode());
+        result = prime * result + (weak ? 1231 : 1237);
+        result = prime * result + (wild ? 1231 : 1237);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return equals(obj,false);
+    }
+    
+    public boolean equals(Object obj, boolean weakmatch) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final EntityTag other = (EntityTag)obj;
+        if (isWild() || other.isWild())
+            return true;
+        if (tag == null) {
+            if (other.tag != null)
+                return false;
+        } else if (!tag.equals(other.tag))
+            return false;
+        if (!weakmatch && (weak != other.weak))
+            return false;
+        if (wild != other.wild)
+            return false;
+        return true;
+    }
+
+    @Override
+    protected EntityTag clone() {
+        try {
+            return (EntityTag)super.clone();
+        } catch (CloneNotSupportedException e) {
+            return new EntityTag(tag, weak, wild); // not going to happen
+        }
+    }
+
+    /**
+     * Utility method for generating ETags. Works by concatenating the UTF-8 bytes of the provided strings then
+     * generating an MD5 hash of the result.
+     */
+    public static EntityTag generate(String... material) {
+        String etag = null;
+        try {
+            MessageDigest md = MessageDigest.getInstance("md5");
+            for (String s : material) {
+                if (s != null)
+                    md.update(s.getBytes("utf-8"));
+            }
+            byte[] digest = md.digest();
+            StringBuilder buf = new StringBuilder();
+            UrlEncoding.hex(buf,0,digest.length,digest);
+            etag = buf.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new UnsupportedOperationException("Hashing algorithm unavailable");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException("UTF-8 unsupported", e);
+        }
+        return new EntityTag(etag);
+    }
+    
+    /**
+     * Checks that the passed in ETag matches the ETag generated by the generate method
+     */
+    public static boolean matches(EntityTag etag, String... material) {
+        EntityTag etag2 = generate(material);
+        return EntityTag.matches(etag, etag2);
+    }
+
+    public static String toString(EntityTag tag, EntityTag... tags) {
+        if (tag == null) return null;
+        StringBuilder buf = new StringBuilder();
+        buf.append(tag.toString());
+        for (EntityTag t : tags)
+            buf.append(", ")
+               .append(t.toString());
+        return buf.toString();
+    }
+
+    public static String toString(String tag, String... tags) {
+        if (tag == null) return null;
+        StringBuilder buf = new StringBuilder();
+        buf.append(new EntityTag(tag).toString());
+        for (String t : tags)
+            buf.append(", ")
+               .append(new EntityTag(t).toString());
+        return buf.toString();
+    }
+    
+    public static String toString(Iterable<EntityTag> tags) {
+      StringBuilder buf = new StringBuilder();
+      boolean first = true;
+      for (EntityTag tag : tags) {
+        if (!first) buf.append(", ");
+        else first = !first;
+        buf.append(tag.toString());
+      }
+      return buf.toString();
+    }
+    
+    public int compareTo(EntityTag o) {
+        if (o.isWild() && !isWild())
+            return 1;
+        if (isWild() && !o.isWild())
+            return -1;
+        if (o.isWeak() && !isWeak())
+            return -1;
+        if (isWeak() && !o.isWeak())
+            return 1;
+        return tag.compareTo(o.tag);
+    }
+    
+    public static interface EntityTagGenerator<T> {
+      EntityTag generateFor(T t);
+    }
+    
+    @Retention(RUNTIME)
+    @Target( {TYPE})
+    public @interface ETagGenerator {
+      Class<? extends EntityTagGenerator<?>> value();
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static <T>EntityTag generate(T t) {
+      EntityTag etag = null;
+      try {
+        if (t == null)
+          throw new IllegalArgumentException();
+        Class<?> _class = t.getClass();
+        if (_class.isAnnotationPresent(ETagGenerator.class)) {
+          ETagGenerator g = _class.getAnnotation(ETagGenerator.class);
+          Class<? extends EntityTagGenerator<T>> gen = 
+            (Class<? extends EntityTagGenerator<T>>) g.value();
+          EntityTagGenerator<T> etg = 
+            gen.newInstance();
+          etag = etg.generateFor(t);
+        } else etag = generate(new String[] {t.toString()});
+      } catch (Throwable e) {
+        throw new RuntimeException(e);
+      }
+      return etag;
+    }
+    
+    public static <T>EntityTag generator(T t, EntityTagGenerator<T> gen) {
+      if (t == null)
+        throw new IllegalArgumentException();
+      return gen.generateFor(t);
+    }
+    
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/EntityTag.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Method.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Method.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Method.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Method.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,155 @@
+/*
+ * 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.abdera2.common.http;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class Method {
+  
+  private static final Map<String, Method> methods = 
+    new ConcurrentHashMap<String, Method>();
+  
+  public static final Method GET = 
+    new Method("GET", true, true);
+  public static final Method POST = 
+    new Method("POST", false, false);
+  public static final Method PUT = 
+    new Method("PUT", true, false);
+  public static final Method DELETE = 
+    new Method("DELETE", true, false);
+  public static final Method OPTIONS = 
+    new Method("OPTIONS", true, true);
+  public static final Method PATCH = 
+    new Method("PATCH", false, false);
+  public static final Method HEAD = 
+    new Method("HEAD", true, true);
+  public static final Method TRACE = 
+    new Method("TRACE", true, true);
+  
+  static {
+    methods.put(GET.name,GET);
+    methods.put(POST.name,POST);
+    methods.put(PUT.name, PUT);
+    methods.put(DELETE.name,DELETE);
+    methods.put(OPTIONS.name,OPTIONS);
+    methods.put(PATCH.name, PATCH);
+    methods.put(HEAD.name, HEAD);
+    methods.put(TRACE.name, TRACE);
+  }
+  
+  static void add(Method method) {
+    methods.put(method.name(),method);
+  }
+  
+  public static boolean isIdempotent(String method) {
+    Method m = get(method);
+    return m != null ? m.idempotent() : false;
+  }
+  
+  public static boolean isSafe(String method) {
+    Method m = get(method);
+    return m != null ? m.safe() : false;
+  }
+  
+  public static Method add(String name, boolean idempotent, boolean safe) {
+    if (name == null)
+      throw new IllegalArgumentException();
+    if (!methods.containsKey(name)) {
+      name = name.toUpperCase();
+      Method method = new Method(name,idempotent,safe);
+      add(method);
+      return method;
+    } else return null;
+  }
+  
+  public static Method get(String name) {
+    if (name == null) 
+      throw new IllegalArgumentException();
+    return methods.get(name.toUpperCase());
+  }
+  
+  public static Method get(String name, boolean create) {
+    Method method = get(name);
+    if (method == null && create) {
+      method = add(name,false,false);
+    }
+    return method;
+  }
+  
+  private final String name;
+  private final boolean idempotent;
+  private final boolean safe;
+  
+  Method(String name) {
+    this(name,false,false);
+  }
+  
+  Method(String name, boolean idempotent, boolean safe) {
+    this.name = name.toUpperCase();
+    this.idempotent = idempotent;
+    this.safe = safe;
+  }
+  
+  public String name() {
+    return name;
+  }
+  
+  public boolean idempotent() {
+    return idempotent;
+  }
+  
+  public boolean safe() {
+    return safe;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + (idempotent ? 1231 : 1237);
+    result = prime * result + ((name == null) ? 0 : name.hashCode());
+    result = prime * result + (safe ? 1231 : 1237);
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    Method other = (Method) obj;
+    if (idempotent != other.idempotent)
+      return false;
+    if (name == null) {
+      if (other.name != null)
+        return false;
+    } else if (!name.equals(other.name))
+      return false;
+    if (safe != other.safe)
+      return false;
+    return true;
+  }
+  
+  public String toString() {
+    return name;
+  }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Method.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Preference.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Preference.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Preference.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Preference.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,263 @@
+package org.apache.abdera2.common.http;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.abdera2.common.text.CharUtils;
+import static org.apache.abdera2.common.text.CharUtils.quotedIfNotToken;
+import org.apache.abdera2.common.text.Codec;
+import org.apache.abdera2.common.text.CharUtils.Profile;
+
+/**
+ * Implementation of the Prefer HTTP Header, e.g.
+ * 
+ * Prefer: return-no-content, my-preference=abc;xyz=123
+ */
+public class Preference implements Serializable {
+  
+  public static final String RETURN_NO_CONTENT = "return-no-content";
+  public static final String RETURN_ACCEPTED = "return-accepted";
+  public static final String RETURN_CONTENT = "return-content";
+  public static final String RETURN_STATUS = "return-status";
+  
+  /** 
+   * The "return-no-content" token indicates that the client prefers that
+   * the server not include an entity in the response to a successful
+   * request.  Typically, such responses would use the 204 No Content
+   * status code as defined in Section 10.2.5 of [RFC2616], but other
+   * status codes can be used as appropriate.
+   */
+  public static final Preference PREF_RETURN_NO_CONTENT = 
+    new Preference(RETURN_NO_CONTENT);
+  
+  /**
+   * The "return-accepted" token indicates that the client prefers that
+   * the server respond with a 202 Accepted response indicating that the
+   * request has been accepted for processing.
+   */
+  public static final Preference PREF_RETURN_ACCEPTED =
+    new Preference(RETURN_ACCEPTED);
+  
+  /**
+   * The "return-content" token indicates that the client prefers that the
+   * server include an entity representing the current state of the
+   * resource in the response to a successful request.
+   */
+  public static final Preference PREF_RETURN_CONTENT =
+    new Preference(RETURN_CONTENT);
+  
+  /**
+   * The "return-status" token indicates that the client prefers that the
+   * server include an entity describing the status of the request in the
+   * response to a successful request.
+   */
+  public static final Preference PREF_RETURN_STATUS = 
+    new Preference(RETURN_STATUS);
+  
+  private static final long serialVersionUID = -6238673046322517740L;
+  private final String token;
+  private String value;
+  private final Map<String,String> params = 
+    new HashMap<String,String>();
+  
+  public Preference(String token) {
+    CharUtils.verify(token, Profile.TOKEN);
+    this.token = token.toLowerCase();
+  }
+  
+  public String getToken() {
+    return token;
+  }
+  
+  public String getValue() {
+    return value;
+  }
+  
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  private static final Set<String> reserved = 
+    new HashSet<String>();
+  static {
+    // no reserved yet
+  }
+  private static boolean reserved(String name) {
+    return reserved.contains(name);
+  }
+  
+  public void addParam(String name, String value) {
+    if (name == null || reserved(name)) 
+      throw new IllegalArgumentException();
+    if (value == null && params.containsKey(name))
+      params.remove(name);
+    else {
+      params.put(name, value);
+    }
+  }
+  
+  public boolean matches(String token) {
+    if (token == null) return false;
+    return this.token.equalsIgnoreCase(token.toLowerCase());
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((params == null) ? 0 : params.hashCode());
+    result = prime * result + ((token == null) ? 0 : token.hashCode());
+    result = prime * result + ((value == null) ? 0 : value.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;
+    Preference other = (Preference) obj;
+    if (params == null) {
+      if (other.params != null)
+        return false;
+    } else if (!params.equals(other.params))
+      return false;
+    if (token == null) {
+      if (other.token != null)
+        return false;
+    } else if (!token.equals(other.token))
+      return false;
+    if (value == null) {
+      if (other.value != null)
+        return false;
+    } else if (!value.equals(other.value))
+      return false;
+    return true;
+  }
+
+  public String getParam(String name) {
+    if (name == null || reserved(name))
+      throw new IllegalArgumentException();
+    return params.get(name);
+  }
+  
+  public String toString() {
+    StringBuilder buf = new StringBuilder();
+    buf.append(token);
+    
+    if (value != null) {
+      String encval = Codec.encode(value, Codec.STAR);
+      if (value.equals(encval)) {
+        buf.append('=')
+           .append(quotedIfNotToken(value));
+      } else {
+        buf.append('*')
+           .append('=')
+           .append(encval);
+      }
+    }
+
+   for (Map.Entry<String, String> entry : params.entrySet()) {
+     String val = entry.getValue();
+     String encval = Codec.encode(val,Codec.STAR);
+     buf.append(';')
+        .append(entry.getKey());
+     if (!val.equals(encval)) {
+       buf.append('*')
+          .append('=')
+          .append(encval);
+     } else {
+       buf.append('=')
+          .append(quotedIfNotToken(val));
+     }
+   }
+    
+    return buf.toString();
+  }
+  
+  private final static String TOKEN = "[\\!\\#\\$\\%\\&\\'\\*\\+\\-\\.\\^\\_\\`\\|\\~a-zA-Z0-9]+";
+  private final static String PARAM = TOKEN+"\\s*={1}\\s*(?:(?:\"[^\"]+\")|(?:"+TOKEN+"))";
+  private final static String PREF = TOKEN+"(?:\\s*={1}\\s*(?:(?:\"[^\"]+\")|(?:"+TOKEN+"))){0,1}";
+  private final static String PARAMS = "(?:\\s*;\\s*(" + PARAM + "(?:\\s*;\\s*"+PARAM+")))*";
+  private final static String PATTERN = "("+PREF+")" + PARAMS;
+
+  private final static Pattern pattern = 
+    Pattern.compile(PATTERN);
+  private final static Pattern param = 
+    Pattern.compile("("+PARAM+")");
+  
+  public static Iterable<Preference> parse(String text) {
+    List<Preference> prefs = new ArrayList<Preference>();
+    Matcher matcher = pattern.matcher(text);
+    while (matcher.find()) {
+      String pref = matcher.group(1);
+      String params = matcher.group(2);
+      String token = null, tokenval = null;
+      
+      if (pref != null) {
+        String[] ps = pref.split("\\s*=\\s*", 2);
+        token = ps[0].trim();
+        if (ps.length == 2)
+          tokenval = Codec.decode(CharUtils.unquote(ps[1]));
+      }
+      
+      Preference preference = new Preference(token);
+      preference.setValue(tokenval);
+      prefs.add(preference);
+      
+      if (params != null) {
+        Matcher mparams = param.matcher(params);
+        while(mparams.find()) {
+          String p = mparams.group(1);
+          String[] ps = p.split("\\s*=\\s*", 2);
+          preference.addParam(ps[0], Codec.decode(CharUtils.unquote(ps[1])));
+        }
+      }
+    }
+    return prefs;
+  }
+  
+  public static String toString(
+    Preference preference, 
+    Preference... preferences) {
+    if (preference == null)
+      return null;
+    StringBuilder buf = new StringBuilder();
+    buf.append(preference.toString());
+    for (Preference pref : preferences) {
+      buf.append(',').append(pref.toString());
+    }
+    return buf.toString();
+  }
+  
+  public static String toString(Iterable<Preference> preferences) {
+    StringBuilder buf = new StringBuilder();
+    boolean first = true;
+    for (Preference pref : preferences) {
+      if (!first) buf.append(',');
+      else first = !first;
+      buf.append(pref.toString());
+    }
+    return buf.toString();
+  }
+  
+  public static boolean contains(
+    Iterable<Preference> preferences, 
+    String token) {
+    for (Preference pref : preferences)
+      if (pref.matches(token))
+        return true;
+    return false;
+  }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/Preference.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/QualityHelper.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/QualityHelper.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/QualityHelper.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/QualityHelper.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,132 @@
+/*
+ * 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.abdera2.common.http;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+public final class QualityHelper {
+
+  private QualityHelper() {}
+
+  /**
+   * A "Qualified Token"... HTTP Headers used for content negotation 
+   * (e.g. Accept-Language) assign a Q-value for each acceptable option.
+   * The QValue is between 0.0 and 1.0 inclusive, with 1.0 being the 
+   * most acceptable option and 0.0 being completely unacceptable.
+   * 
+   * The QToken class wraps a qualified option and provides access to
+   * it's Q-Value. 
+   */
+  public final static class QToken 
+    implements Comparable<QToken>, Serializable {
+
+    private static final long serialVersionUID = -4461686265000839487L;
+      private final String token;
+      private final double qvalue;
+      QToken(String token, double qvalue) {
+          this.token = token;
+          this.qvalue = Math.max(0,Math.min(qvalue,1.0));
+      }
+      public int compareTo(QToken other) {
+        return Double.compare(other.qvalue, qvalue);
+      }
+      public String token() {
+        return token;
+      }
+      public double q() {
+        return qvalue;
+      }
+      public String toString() {
+        return token;
+      }
+      @Override
+      public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        long temp;
+        temp = Double.doubleToLongBits(qvalue);
+        result = prime * result + (int) (temp ^ (temp >>> 32));
+        result = prime * result + ((token == null) ? 0 : token.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;
+        QToken other = (QToken) obj;
+        if (Double.doubleToLongBits(qvalue) != Double
+            .doubleToLongBits(other.qvalue))
+          return false;
+        if (token == null) {
+          if (other.token != null)
+            return false;
+        } else if (!token.equals(other.token))
+          return false;
+        return true;
+      }
+  }
+
+  /**
+   * <p>
+   * Utility method for parsing HTTP content negotiation headers and sorting their 
+   * qualified tokens according to q parameter values.
+   * </p>
+   * <p>
+   * e.g. Accept: audio/*; q=0.2, audio/basic, audio/mpeg; q=0.1
+   * </p>
+   * <p>
+   * would sort into:
+   * </p>
+   * 
+   * <pre>
+   *   audio/basic
+   *   audio/*
+   *   audio/mpeg
+   * </pre>
+   */
+  public static QToken[] orderByQ(String header) {
+      if (header == null || header.length() == 0)
+          return new QToken[0];
+      String[] tokens = header.split("\\s*,\\s*");
+      QToken[] qtokens = new QToken[tokens.length];
+      for (int i = 0; i < tokens.length; i++) {
+          String[] qvalues = tokens[i].split("\\s*;\\s*");
+          String t = qvalues[0];
+          if (qvalues.length > 1) {
+              for (String qvalue : qvalues) {
+                  String[] v = qvalue.split("\\s*=\\s*");
+                  if (v[0].equalsIgnoreCase("q")) {
+                      double qv = Double.parseDouble(v[1]);
+                      qtokens[i] = new QToken(t, qv);
+                      break;
+                  }
+              }
+          }
+          if (qtokens[i] == null)
+              qtokens[i] = new QToken(t, 1.0);
+      }
+      Arrays.sort(qtokens);
+      return qtokens;
+  }
+  
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/QualityHelper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/ResponseType.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/ResponseType.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/ResponseType.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/ResponseType.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,34 @@
+/*
+ * 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.abdera2.common.http;
+
+public enum ResponseType {
+  INFORMATIONAL,
+  SUCCESSFUL, 
+  REDIRECTION, 
+  CLIENT_ERROR, 
+  SERVER_ERROR, 
+  UNKNOWN;
+
+  public static ResponseType select(int status) {
+    return status < 100 || status >= 600 ?
+        UNKNOWN :
+        values()[(status-100)/100];
+  }
+
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/ResponseType.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/WebLink.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/WebLink.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/WebLink.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/WebLink.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,466 @@
+package org.apache.abdera2.common.http;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.activation.MimeType;
+
+import org.apache.abdera2.common.iri.IRI;
+import org.apache.abdera2.common.lang.Lang;
+import org.apache.abdera2.common.mediatype.MimeTypeParseException;
+import org.apache.abdera2.common.text.CharUtils;
+import org.apache.abdera2.common.text.Codec;
+import org.apache.abdera2.common.text.CharUtils.Profile;
+
+import static org.apache.abdera2.common.text.CharUtils.scanFor;
+import static org.apache.abdera2.common.text.CharUtils.quotedIfNotToken;
+
+/**
+ * Implements the HTTP Link Header
+ * (http://tools.ietf.org/html/rfc5988)
+ */
+public class WebLink implements Serializable {
+
+  private static final long serialVersionUID = 3875558439575297581L;
+  public static final String MEDIA_SCREEN = "screen";
+  public static final String MEDIA_TTY = "tty";
+  public static final String MEDIA_TV = "tv";
+  public static final String MEDIA_PROJECTION = "projection";
+  public static final String MEDIA_HANDHELD = "handheld";
+  public static final String MEDIA_PRINT = "print";
+  public static final String MEDIA_BRAILLE = "braille";
+  public static final String MEDIA_AURAL = "aural";
+  public static final String MEDIA_ALL = "all";
+  
+  private final IRI iri;
+  private final Set<String> rel = 
+    new LinkedHashSet<String>();
+  private IRI anchor;
+  private final Set<String> rev =
+    new LinkedHashSet<String>();
+  private Lang lang;
+  private final Set<String> media = 
+    new LinkedHashSet<String>();
+  private String title;
+  private MimeType mediaType;
+  private final Map<String,String> params = 
+    new HashMap<String,String>();
+  
+  public WebLink(String iri) {
+    this(new IRI(iri));
+  }
+  
+  public WebLink(String iri, String rel) {
+    this(new IRI(iri),rel);
+  }
+  
+  public WebLink(IRI iri, String rel) {
+    if (iri == null) 
+      throw new IllegalArgumentException();
+    this.iri = iri.normalize();
+    if (rel != null) this.rel.add(rel); // verify
+    this.anchor = null;
+    this.lang = null;
+    this.title = null;
+    this.mediaType = null;
+  }
+  
+  public WebLink(IRI iri) {
+    if (iri == null) 
+      throw new IllegalArgumentException();
+    this.iri = iri;
+    this.anchor = null;
+    this.lang = null;
+    this.title = null;
+    this.mediaType = null;
+  }
+  
+  public IRI getResolvedIri(IRI base) {
+    IRI context = getContextIri(base);
+    return context != null ? context.resolve(iri) : iri;
+  }
+  
+  public IRI getContextIri(IRI base) {
+    if (anchor == null) return base;
+    return base != null ? base.resolve(anchor) : anchor;
+  }
+  
+  public IRI getIri() {
+    return iri;
+  }
+  
+  public void addRel(String rel) {
+    this.rel.add(new IRI(rel).toASCIIString());
+  }
+  
+  public void addRel(IRI rel) {
+    addRel(rel.toASCIIString());
+  }
+  
+  public Iterable<String> getRel() {
+    return this.rel;
+  }
+  
+  public IRI getAnchor() {
+    return anchor;
+  }
+  
+  public void setAnchor(IRI iri) {
+    this.anchor = iri;
+  }
+  
+  public void setAnchor(String iri) {
+    setAnchor(new IRI(iri));
+  }
+  
+  public void addRev(String rev) {
+    this.rev.add(new IRI(rev).toASCIIString());
+  }
+  
+  public void addRev(IRI rev) {
+    addRev(rev.toASCIIString());
+  }
+  
+  public Iterable<String> getRev() {
+    return this.rev;
+  }
+  
+  public Lang getHrefLang() {
+    return lang;
+  }
+  
+  public void setHrefLang(Lang lang) {
+    this.lang = lang;
+  }
+  
+  public void setHrefLang(String lang) {
+    setHrefLang(Lang.parse(lang));
+  }
+  
+  public void setHrefLang(Locale locale) {
+    setHrefLang(Lang.fromLocale(locale));
+  }
+  
+  public void addMedia(String media) {
+    CharUtils.verify(media, Profile.TOKEN);
+    this.media.add(media.toLowerCase());
+  }
+  
+  public Iterable<String> getMedia() {
+    return this.media;
+  }
+  
+  public String getTitle() {
+    return title;
+  }
+  
+  public void setTitle(String title) {
+    this.title = title;
+  }
+  
+  public MimeType getMediaType() {
+    return mediaType;
+  }
+  
+  public void setMediaType(MimeType mediaType) {
+    this.mediaType = mediaType;
+  }
+  
+  public void setMediaType(String mediaType) {
+    try {
+      setMediaType(new MimeType(mediaType));
+    } catch (javax.activation.MimeTypeParseException t) {
+      throw new MimeTypeParseException(t);
+    }
+  }
+  
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((anchor == null) ? 0 : anchor.hashCode());
+    result = prime * result + ((iri == null) ? 0 : iri.hashCode());
+    result = prime * result + ((lang == null) ? 0 : lang.hashCode());
+    result = prime * result + ((media == null) ? 0 : media.hashCode());
+    result = prime * result + ((mediaType == null) ? 0 : mediaType.toString().hashCode());
+    result = prime * result + ((params == null) ? 0 : params.hashCode());
+    result = prime * result + ((rel == null) ? 0 : rel.hashCode());
+    result = prime * result + ((rev == null) ? 0 : rev.hashCode());
+    result = prime * result + ((title == null) ? 0 : title.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;
+    WebLink other = (WebLink) obj;
+    if (anchor == null) {
+      if (other.anchor != null)
+        return false;
+    } else if (!anchor.equals(other.anchor))
+      return false;
+    if (iri == null) {
+      if (other.iri != null)
+        return false;
+    } else if (!iri.equals(other.iri))
+      return false;
+    if (lang == null) {
+      if (other.lang != null)
+        return false;
+    } else if (!lang.equals(other.lang))
+      return false;
+    if (media == null) {
+      if (other.media != null)
+        return false;
+    } else if (!media.equals(other.media))
+      return false;
+    if (mediaType == null) {
+      if (other.mediaType != null)
+        return false;
+    } else if (!mediaType.equals(other.mediaType))
+      return false;
+    if (params == null) {
+      if (other.params != null)
+        return false;
+    } else if (!params.equals(other.params))
+      return false;
+    if (rel == null) {
+      if (other.rel != null)
+        return false;
+    } else if (!rel.equals(other.rel))
+      return false;
+    if (rev == null) {
+      if (other.rev != null)
+        return false;
+    } else if (!rev.equals(other.rev))
+      return false;
+    if (title == null) {
+      if (other.title != null)
+        return false;
+    } else if (!title.equals(other.title))
+      return false;
+    return true;
+  }
+
+
+
+  private static final Set<String> reserved = 
+    new HashSet<String>();
+  static {
+    reserved.add("rel");
+    reserved.add("anchor");
+    reserved.add("rev");
+    reserved.add("hreflang");
+    reserved.add("media");
+    reserved.add("title");
+    reserved.add("type");
+    reserved.add("type");
+  }
+  private static boolean reserved(String name) {
+    return reserved.contains(name);
+  }
+  
+  public void addParam(String name, String value) {
+    if (name == null || reserved(name)) 
+      throw new IllegalArgumentException();
+    if (value == null && params.containsKey(name))
+      params.remove(name);
+    else {
+      params.put(name, value);
+    }
+  }
+  
+  public String getParam(String name) {
+    if (name == null || reserved(name))
+      throw new IllegalArgumentException();
+    return params.get(name);
+  }
+  
+  public String toString() {
+    StringBuilder buf = new StringBuilder();
+    buf.append('<')
+       .append(iri.toASCIIString())
+       .append('>');
+    
+    if (rel.size() > 0) {
+      buf.append(';')
+         .append("rel=");
+      boolean first = true;
+      if (rel.size() > 1)
+        buf.append('"');
+      for (String r : rel) {
+        if (!first) buf.append(' ');
+        else first = false;
+        buf.append(quotedIfNotToken(r));
+      }
+      if (rel.size() > 1)
+        buf.append('"');
+    }
+    
+    if (anchor != null) {
+      buf.append(';')
+         .append("anchor=<")
+         .append(anchor.toASCIIString())
+         .append('>');
+    }
+    
+    if (rev.size() > 0) {
+      buf.append(';')
+         .append("rev=");
+      boolean first = true;
+      if (rev.size() > 1)
+        buf.append('"');
+      for (String r : rev) {
+        if (!first) buf.append(' ');
+        else first = false;
+        buf.append(quotedIfNotToken(r));
+      }
+      if (rev.size() > 1)
+        buf.append('"');
+    }
+    
+    if (lang != null) {
+      buf.append(';')
+         .append("hreflang=")
+         .append(lang.toString());
+    }
+    
+    if (media.size() > 0) {
+      buf.append(';')
+         .append("media=");
+      boolean first = true;
+      if (media.size() > 1)
+        buf.append('"');
+      for (String r : media) {
+        if (!first) buf.append(' ');
+        else first = false;
+        buf.append(quotedIfNotToken(r));
+      }
+      if (media.size() > 1)
+        buf.append('"');
+    }
+    
+    if (title != null) {
+      String enctitle = Codec.encode(title,Codec.STAR);
+      buf.append(';')
+         .append("title");
+      if (!title.equals(enctitle))
+        buf.append('*')
+           .append('=')
+           .append(enctitle);
+      else
+        buf.append('=')
+           .append(quotedIfNotToken(title));
+    }
+    
+   if (mediaType != null) {
+     buf.append(';')
+        .append("type=")
+        .append(quotedIfNotToken(mediaType.toString()));
+   }
+   
+   for (Map.Entry<String, String> entry : params.entrySet()) {
+     String val = entry.getValue();
+     String encval = Codec.encode(val,Codec.STAR);
+     buf.append(';')
+        .append(entry.getKey());
+     if (!val.equals(encval)) {
+       buf.append('*')
+          .append('=')
+          .append(encval);
+     } else {
+       buf.append('=')
+          .append(quotedIfNotToken(entry.getValue()));
+     }
+   }
+    
+    return buf.toString();
+  }
+
+  public static Iterable<WebLink> parse(String text) {
+    List<WebLink> links = new ArrayList<WebLink>();
+    WebLink weblink = null;
+    if (text == null) return Collections.emptyList();
+    
+    int z = scanFor('<', text, 0, true);
+
+    while(z != -1) {
+      int s = z;
+      int e = scanFor('>', text, s, false);
+      if (e == -1)
+        throw new IllegalArgumentException();
+      
+      String uri = text.substring(s+1,e).trim();
+      weblink = new WebLink(uri);
+      
+      s = scanFor(';', text,e+1,false);
+      while(s != -1 && text.charAt(s) != ',') {
+        e = scanFor('=', text,s+1,false);
+        String name = text.substring(s+1,text.charAt(e-1)=='*'?e-1:e).trim();
+        s = scanFor(';', text,e+1,false);
+        String val = s!=-1?text.substring(e+1,s).trim():text.substring(e+1).trim();
+        val = Codec.decode(val);
+        if (name.equalsIgnoreCase("rel")) {
+          String[] vals = CharUtils.unquote(val).split("\\s+");
+          for (String v : vals)
+            weblink.addRel(v);
+        } else if (name.equalsIgnoreCase("anchor")) {
+          weblink.setAnchor(CharUtils.unwrap(val, '<', '>'));
+        } else if (name.equalsIgnoreCase("rev")) {
+          String[] vals = CharUtils.unquote(val).split("\\s+");
+          for (String v : vals)
+            weblink.addRev(v);
+        } else if (name.equalsIgnoreCase("hreflang")) {
+          weblink.setHrefLang(CharUtils.unquote(val));
+        } else if (name.equalsIgnoreCase("media")) {
+          String[] vals = CharUtils.unquote(val).split("\\s+");
+          for (String v : vals)
+            weblink.addMedia(v);
+        } else if (name.equalsIgnoreCase("title")) {
+          weblink.setTitle(CharUtils.unquote(val));
+        } else if (name.equalsIgnoreCase("type")) {
+          weblink.setMediaType(CharUtils.unquote(val));
+        } else {
+          weblink.addParam(name,CharUtils.unquote(val));
+        }
+      }
+      links.add(weblink);
+      if (s == -1) break;
+      z = scanFor('<', text, s+1, false);
+    }
+    return links;
+  }
+    
+  public static String toString(WebLink link, WebLink... links) {
+    if (link == null) return null;
+    StringBuilder buf = new StringBuilder();
+    buf.append(link.toString());
+    for (WebLink l : links)
+      buf.append(", ").append(l.toString());
+    return buf.toString();
+  }
+  
+  public static String toString(Iterable<WebLink> links ) {
+    StringBuilder buf = new StringBuilder();
+    boolean first = true;
+    for (WebLink link : links) {
+      if (!first) buf.append(", ");
+      else first = !first;
+      buf.append(link.toString());
+    }
+    return buf.toString();
+  }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/http/WebLink.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/CharsetSniffingInputStream.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/CharsetSniffingInputStream.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/CharsetSniffingInputStream.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/CharsetSniffingInputStream.java Tue Sep 20 15:56:46 2011
@@ -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.abdera2.common.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Will attempt to autodetect the character encoding from the stream By default, this will preserve the BOM if it exists
+ */
+public class CharsetSniffingInputStream extends FilterInputStream {
+
+    public static enum Encoding {
+        UTF32be("UTF-32", true, new byte[] {0x00, 0x00, 0xFFFFFFFE, 0xFFFFFFFF}), UTF32le("UTF-32", true,
+            new byte[] {0xFFFFFFFF, 0xFFFFFFFE, 0x00, 0x00}), INVALID(null, true, new byte[] {0xFFFFFFFE, 0xFFFFFFFF,
+                                                                                              0x00, 0x00},
+            new byte[] {0x00, 0x00, 0xFFFFFFFF, 0xFFFFFFFE}), UTF16be("UTF-16", true, new byte[] {0xFFFFFFFE,
+                                                                                                  0xFFFFFFFF}), UTF16le(
+            "UTF-16", true, new byte[] {0xFFFFFFFF, 0xFFFFFFFE}), UTF8("UTF-8", true, new byte[] {0xFFFFFFEF,
+                                                                                                  0xFFFFFFBB,
+                                                                                                  0xFFFFFFBF}), UTF32be2(
+            "UTF-32be", false, new byte[] {0x00, 0x00, 0x00, 0x3C}), UTF32le2("UTF-32le", false,
+            new byte[] {0x3C, 0x00, 0x00, 0x00}), UTF16be2("UTF-16be", false, new byte[] {0x00, 0x3C, 0x00, 0x3F}), UTF16le2(
+            "UTF-16le", false, new byte[] {0x3C, 0x00, 0x3F, 0x00});
+
+        private final String enc;
+        private final byte[][] checks;
+        private final boolean bom;
+
+        Encoding(String name, boolean bom, byte[]... checks) {
+            this.enc = name;
+            this.checks = checks;
+            this.bom = bom;
+        }
+
+        public String getEncoding() {
+            return enc;
+        }
+
+        public boolean getBom() {
+            return bom;
+        }
+
+        public int equals(byte[] bom) {
+            for (byte[] check : checks) {
+                if (CharsetSniffingInputStream.equals(bom, check.length, check))
+                    return check.length;
+            }
+            return 0;
+        }
+    }
+
+    protected String encoding;
+    protected boolean bomset = false;
+    protected final boolean preserve;
+
+    public CharsetSniffingInputStream(InputStream in) {
+        this(in, true);
+    }
+
+    public CharsetSniffingInputStream(InputStream in, boolean preserveBom) {
+        super(!(in instanceof PeekAheadInputStream) ? new PeekAheadInputStream(in, 4) : in);
+        this.preserve = preserveBom;
+        try {
+            encoding = detectEncoding();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean isBomSet() {
+        return bomset;
+    }
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    protected PeekAheadInputStream getInternal() {
+        return (PeekAheadInputStream)in;
+    }
+
+    private static boolean equals(byte[] a1, int len, byte[] a2) {
+        for (int n = 0, i = 0; n < len; n++, i++) {
+            if (a1[n] != a2[i])
+                return false;
+        }
+        return true;
+    }
+
+    protected String detectEncoding() throws IOException {
+        PeekAheadInputStream pin = (PeekAheadInputStream)this.in;
+        byte[] bom = new byte[4];
+        pin.peek(bom);
+        bomset = false;
+        for (Encoding enc : Encoding.values()) {
+            int bomlen = enc.equals(bom);
+            if (bomlen > 0) {
+                bomset = enc.getBom();
+                if (bomset && !preserve) // consume the bom
+                    pin.read(new byte[bomlen]);
+                return enc.getEncoding();
+            }
+        }
+        return null;
+    }
+
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/CharsetSniffingInputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/Compression.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/Compression.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/Compression.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/Compression.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,176 @@
+/*
+ * 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.abdera2.common.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.InflaterInputStream;
+
+import org.apache.abdera2.common.text.CharUtils;
+
+public class Compression {
+
+    public enum CompressionCodec {
+        GZIP, XGZIP, DEFLATE;
+
+        public static CompressionCodec value(String encoding) {
+            if (encoding == null)
+                throw new IllegalArgumentException();
+            return valueOf(encoding.toUpperCase().replaceAll("-", ""));
+        }
+
+        public OutputStream wrap(OutputStream out) throws IOException {
+          switch (this) {
+            case XGZIP:
+            case GZIP:
+              return new GZIPOutputStream(out);
+            case DEFLATE:
+              return new DeflaterOutputStream(out);
+            default: throw new IllegalArgumentException(
+              "Unknown Compression Codec");
+          }          
+        }
+     
+        public InputStream wrap(InputStream in) throws IOException {
+          switch (this) {
+            case GZIP:
+            case XGZIP:
+                return new GZIPInputStream(in);
+            case DEFLATE:
+                return new InflaterInputStream(in);
+            default: throw new IllegalArgumentException(
+              "Unknown Compression Codec");
+          }
+        }
+    }
+
+    public static CompressionCodec getCodec(String name) {
+        CompressionCodec codec = null;
+        if (name == null)
+            return null;
+        try {
+            codec = CompressionCodec.valueOf(name.toUpperCase().trim());
+        } catch (Exception e) {}
+        return codec;
+    }
+
+    public static OutputStream wrap(
+        OutputStream out, 
+        CompressionCodec... codecs)
+        throws IOException {
+      if (out == null)
+        throw new IllegalArgumentException(
+            "OutputStream must not be null");
+      if (codecs.length == 0)
+        throw new IllegalArgumentException(
+            "At least one codec must be specified");
+      for (int n = codecs.length - 1; n >= 0; n--)
+        out = codecs[n].wrap(out);
+      return out;      
+    }
+    
+    public static OutputStream wrap(
+        OutputStream out, 
+        CompressionCodec codec,
+        CompressionCodec... codecs)
+        throws IOException {
+        if (out == null)
+          throw new IllegalArgumentException(
+              "OutputStream must not be null");
+        if (codec == null)
+          throw new IllegalArgumentException(
+              "At least one codec must be specified");
+        for (int n = codecs.length - 1; n >= 0; n--)
+          out = codecs[n].wrap(out);
+        out = codec.wrap(out);
+        return out;
+    }
+
+    public static InputStream wrap(
+      InputStream in, 
+      CompressionCodec... codecs)
+    throws IOException {
+      if (in == null)
+        throw new IllegalArgumentException(
+            "InputStream must not be null");
+      if (codecs.length == 0)
+        throw new IllegalArgumentException(
+            "At least one codec must be specified");
+      for (int n = codecs.length - 1; n >= 0; n--)
+        in = codecs[n].wrap(in);
+      return in;
+    }
+    
+    public static InputStream wrap(
+        InputStream in, 
+        CompressionCodec codec,
+        CompressionCodec... codecs) 
+          throws IOException {
+        if (in == null)
+          throw new IllegalArgumentException(
+              "InputStream must not be null");
+        if (codec == null)
+          throw new IllegalArgumentException(
+              "At least one codec must be specified");
+        for (int n = codecs.length - 1; n >= 0; n--)
+          in = codecs[n].wrap(in);
+        in = codec.wrap(in);
+        return in;
+    }
+
+    public static InputStream wrap(
+        InputStream in, 
+        String ce) 
+          throws IOException {
+        if (in == null)
+          throw new IllegalArgumentException(
+              "InputStream must not be null");
+        String[] encodings = CharUtils.splitAndTrim(ce);
+        if (encodings.length == 0) 
+          throw new IllegalArgumentException(
+            "At least one codec must be specified");
+        for (int n = encodings.length - 1; n >= 0; n--) {
+            CompressionCodec encoding = 
+              getCodec(encodings[n]);
+            if (encoding == null) 
+              throw new IllegalArgumentException(
+                "Invalid Compression Codec");
+            in = encoding.wrap(in);
+        }
+        return in;
+    }
+
+    public static String describe(
+        CompressionCodec codec, 
+        CompressionCodec... codecs) {
+        if (codec == null)
+          throw new IllegalArgumentException(
+            "At least one codec must be specified");
+        StringBuilder buf = new StringBuilder("\"");
+        buf.append(codec.name().toLowerCase());
+        for (int n = codecs.length - 1; n >= 0; n--)
+          buf.append(',')
+             .append(codecs[n].name().toLowerCase());
+        buf.append('"');
+        return buf.toString();
+    }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/Compression.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/DynamicPushbackInputStream.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/DynamicPushbackInputStream.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/DynamicPushbackInputStream.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/DynamicPushbackInputStream.java Tue Sep 20 15:56:46 2011
@@ -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.abdera2.common.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+
+/**
+ * PushbackInputStream implementation that performs dynamic resizing of the unread buffer
+ */
+public class DynamicPushbackInputStream extends PushbackInputStream {
+
+    private final int origsize;
+
+    public DynamicPushbackInputStream(InputStream in) {
+        super(in);
+        this.origsize = 1;
+    }
+
+    public DynamicPushbackInputStream(InputStream in, int initialSize) {
+        super(in, initialSize);
+        this.origsize = initialSize;
+    }
+
+    /**
+     * Clear the buffer
+     */
+    public int clear() {
+        int m = buf.length;
+        buf = new byte[origsize];
+        pos = origsize;
+        return m;
+    }
+
+    /**
+     * Shrink the buffer. This will reclaim currently unused space in the buffer, reducing memory but potentially
+     * increasing the cost of resizing the buffer
+     */
+    public int shrink() {
+        byte[] old = buf;
+        if (pos == 0)
+            return 0; // nothing to do
+        int n = old.length - pos;
+        int m, p, s, l;
+        if (n < origsize) {
+            buf = new byte[origsize];
+            p = pos;
+            s = origsize - n;
+            l = old.length - p;
+            m = old.length - origsize;
+            pos = s;
+        } else {
+            buf = new byte[n];
+            p = pos;
+            s = 0;
+            l = n;
+            m = old.length - l;
+            pos = 0;
+        }
+        System.arraycopy(old, p, buf, s, l);
+        return m;
+    }
+
+    private void resize(int len) {
+        byte[] old = buf;
+        buf = new byte[old.length + len];
+        System.arraycopy(old, 0, buf, len, old.length);
+    }
+
+    public void unread(byte[] b, int off, int len) throws IOException {
+        if (len > pos && pos + len > buf.length) {
+            resize(len - pos);
+            pos += len - pos;
+        }
+        super.unread(b, off, len);
+    }
+
+    public void unread(int b) throws IOException {
+        if (pos == 0) {
+            resize(1);
+            pos++;
+        }
+        super.unread(b);
+    }
+
+    public int read() throws IOException {
+        int m = super.read();
+        if (pos >= buf.length && buf.length > origsize)
+            shrink();
+        return m;
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        this.available(); // workaround for a problem in PushbackInputStream, without this, the amount of bytes read
+                          // from some streams will be incorrect
+        int r = super.read(b, off, len);
+        if (pos >= buf.length && buf.length > origsize)
+            shrink();
+        return r;
+    }
+
+    public long skip(long n) throws IOException {
+        long r = super.skip(n);
+        if (pos >= buf.length && buf.length > origsize)
+            shrink();
+        return r;
+    }
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/DynamicPushbackInputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/FilteredCharReader.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/FilteredCharReader.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/FilteredCharReader.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/FilteredCharReader.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,107 @@
+/*
+ * 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.abdera2.common.io;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.abdera2.common.text.CharUtils.Profile;
+
+/**
+ * A reader implementation that profiles out unwanted characters By default, unwanted characters are simply removed from
+ * the stream. Alternatively, a replacement character can be provided so long as it is acceptable to the specified
+ * profile
+ */
+public class FilteredCharReader extends FilterReader {
+
+    /**
+     * The XMLVersion determines which set of restrictions to apply depending on the XML version being parsed
+     */
+    private final Profile profile;
+    private final char replacement;
+
+    public FilteredCharReader(InputStream in, Profile profile) {
+        this(new InputStreamReader(in), profile);
+    }
+
+    public FilteredCharReader(InputStream in, String charset, Profile profile) throws UnsupportedEncodingException {
+        this(new InputStreamReader(in, charset), profile);
+    }
+
+    public FilteredCharReader(InputStream in, Profile profile, char replacement) {
+        this(new InputStreamReader(in), profile, replacement);
+    }
+
+    public FilteredCharReader(InputStream in, String charset, Profile profile, char replacement)
+        throws UnsupportedEncodingException {
+        this(new InputStreamReader(in, charset), profile, replacement);
+    }
+
+    public FilteredCharReader(Reader in) {
+        this(in, Profile.NONOP, (char)0);
+    }
+
+    public FilteredCharReader(Reader in, Profile profile) {
+        this(in, profile, (char)0);
+    }
+
+    public FilteredCharReader(Reader in, char replacement) {
+        this(in, Profile.NONOP, replacement);
+    }
+
+    public FilteredCharReader(Reader in, Profile profile, char replacement) {
+        super(in);
+        this.profile = profile;
+        this.replacement = replacement;
+        if (replacement != 0 && ((!Character.isValidCodePoint(replacement)) || profile.filter(replacement)))
+            throw new IllegalArgumentException();
+    }
+
+    @Override
+    public int read() throws IOException {
+        int c = -1;
+        if (replacement == 0) {
+            while (((c = super.read()) != -1 && profile.filter(c))) {
+            }
+        } else {
+            c = super.read();
+            if (c != -1 && profile.filter(c))
+                c = replacement;
+        }
+        return c;
+    }
+
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException {
+        int n = off;
+        for (; n < Math.min(len, cbuf.length - off); n++) {
+            int r = read();
+            if (r != -1)
+                cbuf[n] = (char)r;
+            else
+                break;
+        }
+        n -= off;
+        return n <= 0 ? -1 : n;
+    }
+
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/FilteredCharReader.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/MultipartInputStream.java
URL: http://svn.apache.org/viewvc/abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/MultipartInputStream.java?rev=1173209&view=auto
==============================================================================
--- abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/MultipartInputStream.java (added)
+++ abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/MultipartInputStream.java Tue Sep 20 15:56:46 2011
@@ -0,0 +1,213 @@
+/*
+ * 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.abdera2.common.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * InputStream that reads a given inputStream and skips the boundary tokens.
+ */
+public class MultipartInputStream extends FilterInputStream {
+
+    private InputStream input;
+    private byte[] boundary;
+    private byte[] storedBuffer;
+    private int storedBufferPosition;
+    private boolean fakeEof;
+    private boolean realEof;
+    private boolean bufferEnd;
+    private int[] lastTable = new int[256];
+
+    public MultipartInputStream(InputStream input, byte[] boundary) {
+        super(input);
+        this.input = input;
+        this.boundary = boundary;
+        computeLastTable();
+    }
+
+    public void skipBoundary() throws IOException {
+        byte[] buffer = new byte[256];
+        while (read(buffer) != -1) {
+        }
+    }
+
+    @Override
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        if (read(b) == -1) {
+            return -1;
+        }
+        return b[0] & 0xff;
+    }
+
+    @Override
+    public int read(byte[] bytes) throws IOException {
+        return read(bytes, 0, bytes.length);
+    }
+
+    @Override
+    public int read(byte[] buffer, int offset, int length) throws IOException {
+        if (length == 0) {
+            return 0;
+        }
+
+        int bytesReaded = -1;
+        if (!checkEndOfFile()) {
+            int bufferLength =
+                Math.max(boundary.length * 3/* number of tokens into the stream */, length + boundary.length);
+
+            byte[] newBuffer = new byte[bufferLength];
+
+            int position = cleanStoredBuffer(newBuffer, 0, bufferLength);
+            if (bufferLength >= position) {
+                position += readBuffer(newBuffer, position, bufferLength - position);
+            }
+
+            if (realEof && position == 0) {
+                return -1;
+            }
+
+            bytesReaded = getBytesReaded(buffer, newBuffer, offset, position, length);
+        }
+        return bytesReaded != 0 ? bytesReaded : -1;
+    }
+
+    private int readBuffer(byte[] buffer, int offset, int length) throws IOException {
+        int count = 0;
+        int read = 0;
+
+        do {
+            read = input.read(buffer, offset + count, length - count);
+            if (read > 0) {
+                count += read;
+            }
+        } while (read > 0 && count < length);
+
+        if (read < 0) {
+            realEof = true;
+        }
+
+        return count;
+    }
+
+    private boolean checkEndOfFile() {
+        if (fakeEof) {
+            fakeEof = false;
+            return true;
+        }
+
+        if (realEof && storedBuffer == null) {
+            return true;
+        }
+        if (realEof && !bufferEnd) {
+            bufferEnd = true;
+        }
+
+        return false;
+    }
+
+    private int getBytesReaded(byte[] buffer, byte[] newBuffer, int offSet, int position, int length) {
+        int boundaryPosition = locateBoundary(newBuffer, boundary.length - 1, position);
+        int bytesReaded;
+
+        if (length < boundaryPosition || boundaryPosition == -1) {// boundary not found
+            bytesReaded = Math.min(length, position);
+            createStoredBuffer(newBuffer, bytesReaded, position);
+        } else {
+            bytesReaded = boundaryPosition;
+            createStoredBuffer(newBuffer, bytesReaded + boundary.length, position);
+
+            if (bytesReaded == 0) {
+                return -1;
+            }
+
+            fakeEof = true;
+        }
+
+        System.arraycopy(newBuffer, 0, buffer, offSet, bytesReaded);
+        return bytesReaded;
+    }
+
+    private void createStoredBuffer(byte[] buffer, int start, int end) {
+        int length = end - start;
+
+        if (length > 0) {
+            if (bufferEnd && storedBuffer != null) {
+                storedBufferPosition -= length;
+            } else {
+                int bufferLength = (storedBuffer == null ? 0 : storedBuffer.length - storedBufferPosition);
+                byte[] newBuffer = new byte[length + bufferLength];
+                System.arraycopy(buffer, start, newBuffer, 0, length);
+
+                if (storedBuffer != null) {
+                    System.arraycopy(storedBuffer, storedBufferPosition, newBuffer, length, bufferLength);
+                }
+
+                storedBuffer = newBuffer;
+                storedBufferPosition = 0;
+            }
+        }
+    }
+
+    private int cleanStoredBuffer(byte[] buffer, int offset, int length) {
+        int i = 0;
+
+        if (storedBuffer != null) {
+            for (i = 0; i < length && storedBufferPosition < storedBuffer.length; i++) {
+                buffer[offset + i] = storedBuffer[storedBufferPosition++];
+            }
+
+            if (storedBufferPosition >= storedBuffer.length) {
+                storedBuffer = null;
+                storedBufferPosition = 0;
+            }
+        }
+
+        return i;
+    }
+
+    /* computation of the last table */
+    private void computeLastTable() {
+        Arrays.fill(lastTable, boundary.length);
+        for (int i = 0; i < boundary.length - 1; i++) {
+            lastTable[boundary[i] & 0xff] = boundary.length - i - 1;
+        }
+    }
+
+    /* simplified boyer-moore algorithm */
+    private int locateBoundary(byte[] bytes, int start, int end) {
+        int position = -1;
+        if (end > boundary.length) {
+            int j = 0;
+            int k = 0;
+            for (int i = start; i < end; i += lastTable[bytes[i] & 0xff]) {
+                for (k = i, j = boundary.length - 1; j >= 0 && boundary[j] == bytes[k]; j--) {
+                    k--;
+                }
+                if (j == -1) {
+                    position = k + 1;
+                }
+            }
+        }
+        return position;
+    }
+
+}

Propchange: abdera/abdera2/common/src/main/java/org/apache/abdera2/common/io/MultipartInputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



Mime
View raw message