Index: src/test/org/apache/nutch/metadata/TestMetadata.java
===================================================================
--- src/test/org/apache/nutch/metadata/TestMetadata.java	(revision 0)
+++ src/test/org/apache/nutch/metadata/TestMetadata.java	(revision 0)
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nutch.metadata;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+import org.apache.nutch.metadata.Metadata;
+
+
+/**
+ * JUnit based tests of class {@link org.apache.nutch.metadata.Metadata}.
+ */
+public class TestMetadata extends TestCase {
+  
+  private static final String CONTENTTYPE = "contenttype";
+
+  public TestMetadata(String testName) {
+    super(testName);
+  }
+  
+  public static Test suite() {
+    return new TestSuite(TestMetadata.class);
+  }
+  
+  public static void main(String[] args) {
+    TestRunner.run(suite());
+  }
+  
+  /** Test for the <code>add(String, String)</code> method. */
+  public void testAdd() {
+    String[] values = null;
+    Metadata meta = new Metadata();
+
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(0, values.length);
+
+    meta.add(CONTENTTYPE, "value1");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(1, values.length);
+    assertEquals("value1", values[0]);
+    
+    meta.add(CONTENTTYPE, "value2");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(2, values.length);
+    assertEquals("value1", values[0]);
+    assertEquals("value2", values[1]);
+
+    // NOTE : For now, the same value can be added many times.
+    //        Should it be changed?
+    meta.add(CONTENTTYPE, "value1");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(3, values.length);
+    assertEquals("value1", values[0]);
+    assertEquals("value2", values[1]);
+    assertEquals("value1", values[2]);
+  }
+
+  /** Test for the <code>set(String, String)</code> method. */
+  public void testSet() {
+    String[] values = null;
+    Metadata meta = new Metadata();
+
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(0, values.length);
+
+    meta.set(CONTENTTYPE, "value1");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(1, values.length);
+    assertEquals("value1", values[0]);
+    
+    meta.set(CONTENTTYPE, "value2");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(1, values.length);
+    assertEquals("value2", values[0]);
+    
+    meta.set(CONTENTTYPE, "new value 1");
+    meta.add("contenttype", "new value 2");
+    values = meta.getValues(CONTENTTYPE);
+    assertEquals(2, values.length);
+    assertEquals("new value 1", values[0]);
+    assertEquals("new value 2", values[1]);
+  }
+  
+  /** Test for <code>setAll(Properties)</code> method */
+  public void testSetProperties() {
+    String[] values = null;
+    Metadata meta = new Metadata();
+    Properties props = new Properties();
+    
+    meta.setAll(props);
+    assertEquals(0, meta.size());
+    
+    props.setProperty("name-one", "value1.1");
+    meta.setAll(props);
+    assertEquals(1, meta.size());
+    values = meta.getValues("name-one");
+    assertEquals(1, values.length);
+    assertEquals("value1.1", values[0]);
+    
+    props.setProperty("name-two", "value2.1");
+    meta.setAll(props);
+    assertEquals(2, meta.size());
+    values = meta.getValues("name-one");
+    assertEquals(1, values.length);
+    assertEquals("value1.1", values[0]);
+    values = meta.getValues("name-two");
+    assertEquals(1, values.length);
+    assertEquals("value2.1", values[0]);
+  }
+    
+  /** Test for <code>get(String)</code> method */
+  public void testGet() {
+    Metadata meta = new Metadata();
+    assertNull(meta.get("a-name"));
+    
+    meta.add("a-name", "value-1");
+    assertEquals("value-1", meta.get("a-name"));
+    meta.add("a-name", "value-2");
+    assertEquals("value-1", meta.get("a-name"));
+  }
+    
+  /** Test for <code>isMultiValued()</code> method */
+  public void testIsMultiValued() {
+    Metadata meta = new Metadata();
+    assertFalse(meta.isMultiValued("key"));
+    meta.add("key", "value1");
+    assertFalse(meta.isMultiValued("key"));
+    meta.add("key", "value2");
+    assertTrue(meta.isMultiValued("key"));
+  }
+
+  /** Test for <code>names</code> method */
+  public void testNames() {
+    String[] names = null;
+    Metadata meta = new Metadata();
+    names = meta.names();
+    assertEquals(0, names.length);
+    
+    meta.add("name-one", "value");
+    names = meta.names();
+    assertEquals(1, names.length);
+    assertEquals("name-one", names[0]);
+    meta.add("name-two", "value");
+    names = meta.names();
+    assertEquals(2, names.length);
+  }
+  
+  /** Test for <code>remove(String)</code> method */
+  public void testRemove() {
+    Metadata meta = new Metadata();
+    meta.remove("name-one");
+    assertEquals(0, meta.size());
+    meta.add("name-one", "value-1.1");
+    meta.add("name-one", "value-1.2");
+    meta.add("name-two", "value-2.2");
+    assertEquals(2, meta.size());
+    assertNotNull(meta.get("name-one"));
+    assertNotNull(meta.get("name-two"));
+    meta.remove("name-one");
+    assertEquals(1, meta.size());
+    assertNull(meta.get("name-one"));
+    assertNotNull(meta.get("name-two"));
+    meta.remove("name-two");
+    assertEquals(0, meta.size());
+    assertNull(meta.get("name-one"));
+    assertNull(meta.get("name-two"));
+  }
+
+  /** Test for <code>equals(Object)</code> method */
+  public void testObject() {
+    Metadata meta1 = new Metadata();
+    Metadata meta2 = new Metadata();
+    assertFalse(meta1.equals(null));
+    assertFalse(meta1.equals("String"));
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-one", "value-1.1");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-one", "value-1.1");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-one", "value-1.2");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-one", "value-1.2");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-two", "value-2.1");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-two", "value-2.1");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-two", "value-2.2");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-two", "value-2.x");
+    assertFalse(meta1.equals(meta2));
+  }
+  
+  /** Test for <code>Writable</code> implementation */
+  public void testWritable() {
+    Metadata result = null;
+    Metadata meta = new Metadata();
+    result = writeRead(meta);
+    assertEquals(0, result.size());
+    meta.add("name-one", "value-1.1");
+    result = writeRead(meta);
+    assertEquals(1, result.size());
+    assertEquals(1, result.getValues("name-one").length);
+    assertEquals("value-1.1", result.get("name-one"));
+    meta.add("name-two", "value-2.1");
+    meta.add("name-two", "value-2.2");
+    result = writeRead(meta);
+    assertEquals(2, result.size());
+    assertEquals(1, result.getValues("name-one").length);
+    assertEquals("value-1.1", result.getValues("name-one")[0]);
+    assertEquals(2, result.getValues("name-two").length);
+    assertEquals("value-2.1", result.getValues("name-two")[0]);
+    assertEquals("value-2.2", result.getValues("name-two")[1]);
+  }
+  
+  private Metadata writeRead(Metadata meta) {
+    Metadata readed = new Metadata();
+    try {
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      meta.write(new DataOutputStream(out));
+      readed.readFields(new DataInputStream(new ByteArrayInputStream(out.toByteArray())));
+    } catch (IOException ioe) {
+      fail(ioe.toString());
+    }
+    return readed;
+  }
+    
+}
Index: src/test/org/apache/nutch/metadata/TestSpellCheckedMetadata.java
===================================================================
--- src/test/org/apache/nutch/metadata/TestSpellCheckedMetadata.java	(revision 0)
+++ src/test/org/apache/nutch/metadata/TestSpellCheckedMetadata.java	(revision 0)
@@ -0,0 +1,261 @@
+/**
+ * Copyright 2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nutch.metadata;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+import org.apache.nutch.metadata.SpellCheckedMetadata;
+
+
+/**
+ * JUnit based tests of class {@link org.apache.nutch.metadata.SpellCheckedMetadata}.
+ *
+ * @author Chris Mattmann
+ * @author J&eacute;r&ocirc;me Charron
+ */
+public class TestSpellCheckedMetadata extends TestCase {
+  
+  public TestSpellCheckedMetadata(String testName) {
+    super(testName);
+  }
+  
+  public static Test suite() {
+    return new TestSuite(TestSpellCheckedMetadata.class);
+  }
+  
+  public static void main(String[] args) {
+    TestRunner.run(suite());
+  }
+  
+
+  /** Test for the <code>getNormalizedName(String)</code> method. */
+  public void testGetNormalizedName() {
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("Content-Type"));
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("ContentType"));
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("Content-type"));
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("contenttype"));
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("contentype"));
+    assertEquals("Content-Type", SpellCheckedMetadata.getNormalizedName("contntype"));
+  }
+
+  /** Test for the <code>add(String, String)</code> method. */
+  public void testAdd() {
+    String[] values = null;
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+
+    values = meta.getValues("contentype");
+    assertEquals(0, values.length);
+
+    meta.add("contentype", "value1");
+    values = meta.getValues("contentype");
+    assertEquals(1, values.length);
+    assertEquals("value1", values[0]);
+    
+    meta.add("Content-Type", "value2");
+    values = meta.getValues("contentype");
+    assertEquals(2, values.length);
+    assertEquals("value1", values[0]);
+    assertEquals("value2", values[1]);
+
+    // NOTE : For now, the same value can be added many times.
+    //        Should it be changed?
+    meta.add("ContentType", "value1");
+    values = meta.getValues("Content-Type");
+    assertEquals(3, values.length);
+    assertEquals("value1", values[0]);
+    assertEquals("value2", values[1]);
+    assertEquals("value1", values[2]);
+  }
+
+  /** Test for the <code>set(String, String)</code> method. */
+  public void testSet() {
+    String[] values = null;
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+
+    values = meta.getValues("contentype");
+    assertEquals(0, values.length);
+
+    meta.set("contentype", "value1");
+    values = meta.getValues("contentype");
+    assertEquals(1, values.length);
+    assertEquals("value1", values[0]);
+    
+    meta.set("Content-Type", "value2");
+    values = meta.getValues("contentype");
+    assertEquals(1, values.length);
+    assertEquals("value2", values[0]);
+    
+    meta.set("contenttype", "new value 1");
+    meta.add("contenttype", "new value 2");
+    values = meta.getValues("contentype");
+    assertEquals(2, values.length);
+    assertEquals("new value 1", values[0]);
+    assertEquals("new value 2", values[1]);
+  }
+  
+  /** Test for <code>setAll(Properties)</code> method */
+  public void testSetProperties() {
+    String[] values = null;
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    Properties props = new Properties();
+    
+    meta.setAll(props);
+    assertEquals(0, meta.size());
+    
+    props.setProperty("name-one", "value1.1");
+    meta.setAll(props);
+    assertEquals(1, meta.size());
+    values = meta.getValues("name-one");
+    assertEquals(1, values.length);
+    assertEquals("value1.1", values[0]);
+    
+    props.setProperty("name-two", "value2.1");
+    meta.setAll(props);
+    assertEquals(2, meta.size());
+    values = meta.getValues("name-one");
+    assertEquals(1, values.length);
+    assertEquals("value1.1", values[0]);
+    values = meta.getValues("name-two");
+    assertEquals(1, values.length);
+    assertEquals("value2.1", values[0]);
+  }
+    
+  /** Test for <code>get(String)</code> method */
+  public void testGet() {
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    assertNull(meta.get("a-name"));
+    
+    meta.add("a-name", "value-1");
+    assertEquals("value-1", meta.get("a-name"));
+    meta.add("a-name", "value-2");
+    assertEquals("value-1", meta.get("a-name"));
+  }
+    
+  /** Test for <code>isMultiValued()</code> method */
+  public void testIsMultiValued() {
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    assertFalse(meta.isMultiValued("key"));
+    meta.add("key", "value1");
+    assertFalse(meta.isMultiValued("key"));
+    meta.add("key", "value2");
+    assertTrue(meta.isMultiValued("key"));
+  }
+
+  /** Test for <code>names</code> method */
+  public void testNames() {
+    String[] names = null;
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    names = meta.names();
+    assertEquals(0, names.length);
+    
+    meta.add("name-one", "value");
+    names = meta.names();
+    assertEquals(1, names.length);
+    assertEquals("name-one", names[0]);
+    meta.add("name-two", "value");
+    names = meta.names();
+    assertEquals(2, names.length);
+  }
+  
+  /** Test for <code>remove(String)</code> method */
+  public void testRemove() {
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    meta.remove("name-one");
+    assertEquals(0, meta.size());
+    meta.add("name-one", "value-1.1");
+    meta.add("name-one", "value-1.2");
+    meta.add("name-two", "value-2.2");
+    assertEquals(2, meta.size());
+    assertNotNull(meta.get("name-one"));
+    assertNotNull(meta.get("name-two"));
+    meta.remove("name-one");
+    assertEquals(1, meta.size());
+    assertNull(meta.get("name-one"));
+    assertNotNull(meta.get("name-two"));
+    meta.remove("name-two");
+    assertEquals(0, meta.size());
+    assertNull(meta.get("name-one"));
+    assertNull(meta.get("name-two"));
+  }
+
+  /** Test for <code>equals(Object)</code> method */
+  public void testObject() {
+    SpellCheckedMetadata meta1 = new SpellCheckedMetadata();
+    SpellCheckedMetadata meta2 = new SpellCheckedMetadata();
+    assertFalse(meta1.equals(null));
+    assertFalse(meta1.equals("String"));
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-one", "value-1.1");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-one", "value-1.1");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-one", "value-1.2");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-one", "value-1.2");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-two", "value-2.1");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-two", "value-2.1");
+    assertTrue(meta1.equals(meta2));
+    meta1.add("name-two", "value-2.2");
+    assertFalse(meta1.equals(meta2));
+    meta2.add("name-two", "value-2.x");
+    assertFalse(meta1.equals(meta2));
+  }
+  
+  /** Test for <code>Writable</code> implementation */
+  public void testWritable() {
+    SpellCheckedMetadata result = null;
+    SpellCheckedMetadata meta = new SpellCheckedMetadata();
+    result = writeRead(meta);
+    assertEquals(0, result.size());
+    meta.add("name-one", "value-1.1");
+    result = writeRead(meta);
+    assertEquals(1, result.size());
+    assertEquals(1, result.getValues("name-one").length);
+    assertEquals("value-1.1", result.get("name-one"));
+    meta.add("name-two", "value-2.1");
+    meta.add("name-two", "value-2.2");
+    result = writeRead(meta);
+    assertEquals(2, result.size());
+    assertEquals(1, result.getValues("name-one").length);
+    assertEquals("value-1.1", result.getValues("name-one")[0]);
+    assertEquals(2, result.getValues("name-two").length);
+    assertEquals("value-2.1", result.getValues("name-two")[0]);
+    assertEquals("value-2.2", result.getValues("name-two")[1]);
+  }
+  
+  private SpellCheckedMetadata writeRead(SpellCheckedMetadata meta) {
+    SpellCheckedMetadata readed = new SpellCheckedMetadata();
+    try {
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      meta.write(new DataOutputStream(out));
+      readed.readFields(new DataInputStream(new ByteArrayInputStream(out.toByteArray())));
+    } catch (IOException ioe) {
+      fail(ioe.toString());
+    }
+    return readed;
+  }
+  	
+}
Index: src/test/org/apache/nutch/protocol/TestContent.java
===================================================================
--- src/test/org/apache/nutch/protocol/TestContent.java	(revision 470451)
+++ src/test/org/apache/nutch/protocol/TestContent.java	(working copy)
@@ -17,6 +17,7 @@
 package org.apache.nutch.protocol;
 
 import org.apache.nutch.metadata.Metadata;
+import org.apache.nutch.metadata.SpellCheckedMetadata;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.nutch.util.NutchConfiguration;
 import org.apache.nutch.util.WritableTestUtils;
@@ -38,7 +39,7 @@
 
     String url = "http://www.foo.com/";
 
-    Metadata metaData = new Metadata();
+    SpellCheckedMetadata metaData = new SpellCheckedMetadata();
     metaData.add("Host", "www.foo.com");
     metaData.add("Content-Type", "text/html");
 
Index: src/java/org/apache/nutch/metadata/Metadata.java
===================================================================
--- src/java/org/apache/nutch/metadata/Metadata.java	(revision 470451)
+++ src/java/org/apache/nutch/metadata/Metadata.java	(working copy)
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright 2005 The Apache Software Foundation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,101 +15,47 @@
  */
 package org.apache.nutch.metadata;
 
-// JDK imports
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-
-// Commons Lang imports
-import org.apache.commons.lang.StringUtils;
-
-// Hadoop imports
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.io.Writable;
 
 
 /**
- * A syntax tolerant and multi-valued metadata container.
- *
- * All the static String fields declared by this class are used as reference
- * names for syntax correction on meta-data naming.
- *
+ * A multi-valued metadata container.
+ * 
  * @author Chris Mattmann
  * @author J&eacute;r&ocirc;me Charron
- */
-public class Metadata implements CreativeCommons,
-                                 DublinCore,
-                                 HttpHeaders,
-                                 Nutch,
-                                 Office,
-                                 Writable {
+ *
+ **/
+public class Metadata implements Writable, CreativeCommons,
+DublinCore, HttpHeaders, Nutch, Office {
   
-
-  /** Used to format DC dates for the DATE metadata field */
-  public final static SimpleDateFormat DATE_FORMAT = 
-          new SimpleDateFormat("yyyy-MM-dd");
-  
-    
-  private final static Map NAMES_IDX = new HashMap();
-  private static String[] normalized = null;
-
-  // Uses self introspection to fill the metanames index and the
-  // metanames list.
-  static {
-    Field[] fields = Metadata.class.getFields();
-    for (int i=0; i<fields.length; i++) {
-      int mods = fields[i].getModifiers();
-      if (Modifier.isFinal(mods) &&
-          Modifier.isPublic(mods) &&
-          Modifier.isStatic(mods) &&
-          fields[i].getType().equals(String.class)) {
-        try {
-          String val = (String) fields[i].get(null);
-          NAMES_IDX.put(normalize(val), val);
-        } catch (Exception e) {
-          // Simply ignore...
-        }
-      }
-    }
-    normalized = (String[]) NAMES_IDX.keySet().toArray(new String[NAMES_IDX.size()]);
-  }
-  
-  
   /** A map of all metadata attributes */
-  private Map metadata = null;
+  Map<String, String[]> metadata = null;
 
   
   /** Constructs a new, empty metadata. */
   public Metadata() {
-    metadata = new HashMap();
+    metadata = new HashMap<String, String[]>();
   }
 
   /**
    */
   public boolean isMultiValued(String name) {
-    return getValues(name).length > 1;
+    return metadata.get(name)!=null && metadata.get(name).length>1;
   }
 
   /**
    * Returns an array of the names contained in the metadata.
    */
   public String[] names() {
-    Iterator iter = metadata.keySet().iterator();
-    List names = new ArrayList();
-    while(iter.hasNext()) {
-      names.add(getNormalizedName((String) iter.next()));
-    }
-    return (String[]) names.toArray(new String[names.size()]);
+    return metadata.keySet().toArray(new String[metadata.keySet().size()]);
   }
   
   /**
@@ -121,11 +67,11 @@
    * @return the value associated to the specified metadata name.
    */
   public String get(String name) {
-    Object values = metadata.get(getNormalizedName(name));
-    if ((values != null) && (values instanceof List)) {
-      return (String) ((List) values).get(0);
+    String[] values = metadata.get(name);
+    if(values==null){
+      return null;
     } else {
-      return (String) values;
+      return values[0];
     }
   }
 
@@ -135,16 +81,10 @@
    * @return the values associated to a metadata name.
    */
   public String[] getValues(String name) {
-    Object values = metadata.get(getNormalizedName(name));
-    if (values != null) {
-      if (values instanceof List) {
-        List list = (List) values;
-        return (String[]) list.toArray(new String[list.size()]);
-      } else {
-        return new String[] { (String) values };
-      }
-    }
-    return new String[0];
+    String[] values=metadata.get(name);
+    if(values==null)
+      values=new String[0];
+    return values;
   }
   
   /**
@@ -156,19 +96,14 @@
    * @param value the metadata value.
    */
   public void add(String name, String value) {
-    String normalized = getNormalizedName(name);
-    Object values = metadata.get(normalized);
-    if (values != null) {
-      if (values instanceof String) {
-        List list = new ArrayList();
-        list.add(values);
-        list.add(value);
-        metadata.put(normalized, list);
-      } else if (values instanceof List) {
-        ((List) values).add(value);
-      }
+    String[] values = metadata.get(name);
+    if (values==null){
+      set(name, value);
     } else {
-      metadata.put(normalized, value);
+      String newValues[]=new String[values.length+1];
+      System.arraycopy(values, 0, newValues, 0, values.length);
+      newValues[newValues.length-1]=value;
+      metadata.put(name,newValues);
     }
   }
 
@@ -176,7 +111,7 @@
     Enumeration names = properties.propertyNames();
     while (names.hasMoreElements()) {
       String name = (String) names.nextElement();
-      set(name, properties.getProperty(name));
+      metadata.put(name, new String[]{properties.getProperty(name)});
     }
   }
   
@@ -189,15 +124,14 @@
    * @param value the metadata value.
    */
   public void set(String name, String value) {
-    remove(name);
-    add(name, value);
+    metadata.put(name, new String[]{value});
   }
-
+  
   /**
    * Remove a metadata and all its associated values.
    */
   public void remove(String name) {
-    metadata.remove(getNormalizedName(name));
+    metadata.remove(name);
   }
   
   /**
@@ -207,7 +141,6 @@
     return metadata.size();
   }
   
-  // Inherited Javadoc
   public boolean equals(Object o) {
     
     if (o == null) { return false; }
@@ -237,7 +170,6 @@
     return true;
   }
 
-  // Inherited Javadoc
   public String toString() {
     StringBuffer buf = new StringBuffer();
     String[] names = names();
@@ -253,53 +185,6 @@
     return buf.toString();
   }
   
-  
-  /**
-   * Get the normalized name of metadata attribute name.
-   * This method tries to find a well-known metadata name (one of the
-   * metadata names defined in this class) that matches the specified name.
-   * The matching is error tolerent. For instance,
-   * <ul>
-   *  <li>content-type gives Content-Type</li>
-   *  <li>CoNtEntType  gives Content-Type</li>
-   *  <li>ConTnTtYpe   gives Content-Type</li>
-   * </ul>
-   * If no matching with a well-known metadata name is found, then the original
-   * name is returned.
-   */
-  public static String getNormalizedName(String name) {
-    String searched = normalize(name);
-    String value = (String) NAMES_IDX.get(searched);
-
-    if ((value == null) && (normalized != null)) {
-      int threshold = searched.length() / 3;
-      for (int i=0; i<normalized.length && value == null; i++) {
-        if (StringUtils.getLevenshteinDistance(searched, normalized[i]) < threshold) {
-          value = (String) NAMES_IDX.get(normalized[i]);
-        }
-      }
-    }
-    return (value != null) ? value : name;
-  }
-    
-  private final static String normalize(String str) {
-    char c;
-    StringBuffer buf = new StringBuffer();
-    for (int i=0; i<str.length(); i++) {
-      c = str.charAt(i);
-      if (Character.isLetter(c)) {
-        buf.append(Character.toLowerCase(c));
-      }
-    }
-    return buf.toString();
-  }
-
-  
-  /* ------------------------- *
-   * <implementation:Writable> *
-   * ------------------------- */
-  
-  // Inherited Javadoc
   public final void write(DataOutput out) throws IOException {
     out.writeInt(size());
     String[] values = null;
@@ -314,7 +199,6 @@
     }
   }
 
-  // Inherited Javadoc
   public final void readFields(DataInput in) throws IOException {
     int keySize = in.readInt();
     String key;
@@ -327,8 +211,4 @@
     }
   }
 
-  /* -------------------------- *
-   * </implementation:Writable> *
-   * -------------------------- */
-   
 }
Index: src/java/org/apache/nutch/metadata/SpellCheckedMetadata.java
===================================================================
--- src/java/org/apache/nutch/metadata/SpellCheckedMetadata.java	(revision 0)
+++ src/java/org/apache/nutch/metadata/SpellCheckedMetadata.java	(revision 0)
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nutch.metadata;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * A decorator to Metadata that adds spellchecking capabilities to property
+ * names.
+ * 
+ * All the static String fields declared by this class are used as reference
+ * names for syntax correction on meta-data naming.
+ */
+public class SpellCheckedMetadata extends Metadata {
+  
+  private final static Map<String, String> NAMES_IDX = new HashMap<String, String>();
+  private static String[] normalized = null;
+
+  // Uses self introspection to fill the metanames index and the
+  // metanames list.
+  static {
+    for (Field field: SpellCheckedMetadata.class.getFields()) {
+      int mods = field.getModifiers();
+      if (Modifier.isFinal(mods) &&
+          Modifier.isPublic(mods) &&
+          Modifier.isStatic(mods) &&
+          field.getType().equals(String.class)) {
+        try {
+          String val = (String) field.get(null);
+          NAMES_IDX.put(normalize(val), val);
+        } catch (Exception e) {
+          // Simply ignore...
+        }
+      }
+    }
+    normalized = NAMES_IDX.keySet().toArray(new String[NAMES_IDX.size()]);
+  }
+  
+  private final static String normalize(String str) {
+    char c;
+    StringBuffer buf = new StringBuffer();
+    for (int i=0; i<str.length(); i++) {
+      c = str.charAt(i);
+      if (Character.isLetter(c)) {
+        buf.append(Character.toLowerCase(c));
+      }
+    }
+    return buf.toString();
+  }
+
+  
+  
+  /**
+   * Get the normalized name of metadata attribute name.
+   * This method tries to find a well-known metadata name (one of the
+   * metadata names defined in this class) that matches the specified name.
+   * The matching is error tolerent. For instance,
+   * <ul>
+   *  <li>content-type gives Content-Type</li>
+   *  <li>CoNtEntType  gives Content-Type</li>
+   *  <li>ConTnTtYpe   gives Content-Type</li>
+   * </ul>
+   * If no matching with a well-known metadata name is found, then the original
+   * name is returned.
+   */
+  public static String getNormalizedName(String name) {
+    String searched = normalize(name);
+    String value = NAMES_IDX.get(searched);
+
+    if ((value == null) && (normalized != null)) {
+      int threshold = searched.length() / 3;
+      for (int i=0; i<normalized.length && value == null; i++) {
+        if (StringUtils.getLevenshteinDistance(searched, normalized[i]) < threshold) {
+          value = NAMES_IDX.get(normalized[i]);
+        }
+      }
+    }
+    return (value != null) ? value : name;
+  }
+  
+  public void remove(String name) {
+    super.remove(getNormalizedName(name));
+  }
+  
+  public void add(String name, String value) {
+    String normalized = getNormalizedName(name);
+    super.add(normalized, value);
+  }
+  
+  public String[] getValues(String name) {
+    return super.getValues(getNormalizedName(name));
+  }
+
+  public String get(String name) {
+    return super.get(getNormalizedName(name));
+  }
+
+  public void set(String name, String value) {
+    super.set(getNormalizedName(name), value);
+  }
+  
+}
Index: src/java/org/apache/nutch/protocol/Content.java
===================================================================
--- src/java/org/apache/nutch/protocol/Content.java	(revision 470451)
+++ src/java/org/apache/nutch/protocol/Content.java	(working copy)
@@ -23,6 +23,7 @@
 import org.apache.hadoop.fs.*;
 import org.apache.hadoop.conf.*;
 import org.apache.nutch.metadata.Metadata;
+import org.apache.nutch.metadata.SpellCheckedMetadata;
 import org.apache.nutch.util.mime.MimeType;
 import org.apache.nutch.util.mime.MimeTypes;
 import org.apache.nutch.util.mime.MimeTypeException;
@@ -64,7 +65,7 @@
 
   protected final void readFieldsCompressed(DataInput in) throws IOException {
     version = in.readByte();
-    metadata = new Metadata();
+    metadata = new SpellCheckedMetadata();
     switch (version) {
     case 0:
     case 1:
