code: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.nutch.metadata;
  
  import java.io.DataInput;
  import java.io.DataOutput;
  import java.io.IOException;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Properties;
  
  import org.apache.hadoop.io.Text;
  import org.apache.hadoop.io.Writable;
  
  
  /**
   * A multi-valued metadata container.
   *
   * @author Chris Mattmann
   * @author J&eacute;r&ocirc;me Charron
   *
   */
  public class Metadata implements Writable {
  
    /**
     * A map of all metadata attributes.
     */
    private Map<String, String[]> metadata = null;
  
  
    /**
     * Constructs a new, empty metadata.
     */
    public Metadata() {
      metadata = new HashMap<String, String[]>();
    }
  
    /**
     * Returns true if named value is multivalued.
     * @param name name of metadata
     * @return true is named value is multivalued, false if single
     * value or null
     */
    public boolean isMultiValued(final String name) {
      return metadata.get(name) != null && metadata.get(name).length > 1;
    }
  
    /**
     * Returns an array of the names contained in the metadata.
     * @return Metadata names
     */
    public String[] names() {
      return metadata.keySet().toArray(new String[metadata.keySet().size()]);
    }
  
    /**
     * Get the value associated to a metadata name.
     * If many values are assiociated to the specified name, then the first
     * one is returned.
     *
     * @param name of the metadata.
     * @return the value associated to the specified metadata name.
     */
    public String get(final String name) {
      String[] values = metadata.get(name);
      if (values == null) {
        return null;
      } else {
        return values[0];
      }
    }
  
    /**
     * Get the values associated to a metadata name.
     * @param name of the metadata.
     * @return the values associated to a metadata name.
     */
    public String[] getValues(final String name) {
      return _getValues(name);
    }
    
    private String[] _getValues(final String name) {
      String[] values = metadata.get(name);
      if (values == null) {
        values = new String[0];
      }
      return values;
    }
  
    /**
     * Add a metadata name/value mapping.
     * Add the specified value to the list of values associated to the
     * specified metadata name.
     *
     * @param name the metadata name.
     * @param value the metadata value.
     */
    public void add(final String name, final String value) {
      String[] values = metadata.get(name);
      if (values == null) {
        set(name, value);
      } else {
        String[] newValues = new String[values.length + 1];
        System.arraycopy(values, 0, newValues, 0, values.length);
        newValues[newValues.length - 1] = value;
        metadata.put(name, newValues);
      }
    }
  
    /**
     * Copy All key-value pairs from properties.
     * @param properties properties to copy from
     */
    public void setAll(Properties properties) {
      @SuppressWarnings("rawtypes")
  	Enumeration names = properties.propertyNames();
      while (names.hasMoreElements()) {
        String name = (String) names.nextElement();
        metadata.put(name, new String[]{properties.getProperty(name)});
      }
    }
  
    /**
     * Set metadata name/value.
     * Associate the specified value to the specified metadata name. If some
     * previous values were associated to this name, they are removed.
     *
     * @param name the metadata name.
     * @param value the metadata value.
     */
    public void set(String name, String value) {
      metadata.put(name, new String[]{value});
    }
  
    /**
     * Remove a metadata and all its associated values.
     * @param name metadata name to remove
     */
    public void remove(String name) {
      metadata.remove(name);
    }
  
    /**
     * Returns the number of metadata names in this metadata.
     * @return number of metadata names
     */
    public int size() {
      return metadata.size();
    }
    
    /** Remove all mappings from metadata. */
    public void clear() {
      metadata.clear();
    }
  
    public boolean equals(Object o) {
  
      if (o == null) { return false; }
  
      Metadata other = null;
      try {
        other = (Metadata) o;
      } catch (ClassCastException cce) {
        return false;
      }
  
      if (other.size() != size()) { return false; }
  
      String[] names = names();
      for (int i = 0; i < names.length; i++) {
        String[] otherValues = other._getValues(names[i]);
        String[] thisValues = _getValues(names[i]);
        if (otherValues.length != thisValues.length) {
          return false;
        }
        for (int j = 0; j < otherValues.length; j++) {
          if (!otherValues[j].equals(thisValues[j])) {
            return false;
          }
        }
      }
      return true;
    }
  
    public String toString() {
      StringBuffer buf = new StringBuffer();
      String[] names = names();
      for (int i = 0; i < names.length; i++) {
        String[] values = _getValues(names[i]);
        for (int j = 0; j < values.length; j++) {
          buf.append(names[i])
             .append("=")
             .append(values[j])
             .append(" ");
        }
      }
      return buf.toString();
    }
  
    public final void write(DataOutput out) throws IOException {
      out.writeInt(size());
      String[] values = null;
      String[] names = names();
      for (int i = 0; i < names.length; i++) {
        Text.writeString(out, names[i]);
        values = _getValues(names[i]);
        int cnt = 0;
        for (int j = 0; j < values.length; j++) {
          if (values[j] != null)
            cnt++;
        }
        out.writeInt(cnt);
        for (int j = 0; j < values.length; j++) {
          if (values[j] != null) {
            Text.writeString(out, values[j]);
          }
        }
      }
    }
  
    public final void readFields(DataInput in) throws IOException {
      int keySize = in.readInt();
      String key;
      for (int i = 0; i < keySize; i++) {
        key = Text.readString(in);
        int valueSize = in.readInt();
        for (int j = 0; j < valueSize; j++) {
          add(key, Text.readString(in));
        }
      }
    }
  
  }
  