org.netflux.core.RecordMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.netflux.core.RecordMetadata.java

Source

/* 
 * netflux-core - Copyright (C) 2005 OPEN input - http://www.openinput.com/
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published by the 
 * Free Software Foundation; either version 2 of the License, or (at your 
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to 
 *   the Free Software Foundation, Inc., 
 *   59 Temple Place, Suite 330, 
 *   Boston, MA 02111-1307 USA
 *   
 * $Id$
 */
package org.netflux.core;

import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.ResourceBundle;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A description of the data that may be held in a {@link Record}. This can be conceptually seen as a list of {@link FieldMetadata}
 * instances, each of them describing the data that may be held in each of the fields of a record.
 * 
 * @author OPEN input - <a href="http://www.openinput.com/">http://www.openinput.com/</a>
 */
public class RecordMetadata implements Serializable, Cloneable {
    private static final long serialVersionUID = -1433565348464531285L;
    private static Log log = LogFactory.getLog(RecordMetadata.class);
    private static ResourceBundle messages = ResourceBundle.getBundle(RecordMetadata.class.getName());

    private ArrayList<FieldMetadata> fieldMetadata;
    private LinkedHashMap<String, Integer> fieldIndexes;

    /**
     * Creates a metadata describing a record that can't hold any field.
     */
    public RecordMetadata() {
        this(new ArrayList<FieldMetadata>());
    }

    /**
     * Creates a metadata describing a record that may hold the fields described by the field metadata contained in the supplied list. No
     * null values or duplicated field names are allowed (See {@link RecordMetadata#setFieldMetadata(List)} for more information).
     * 
     * @param fieldMetadata The list of field metadata this record metadata will contain.
     * @throws NullPointerException if <code>fieldMetadata</code> is <code>null</code> or contains a <code>null</code> item.
     * @throws IllegalArgumentException if the list of field metadata contains duplicated field names.
     */
    public RecordMetadata(List<FieldMetadata> fieldMetadata) {
        this.setFieldMetadata(fieldMetadata);
    }

    /**
     * Returns the list of field metadata this metadata contains. The returned list is unmodifiable, so if you want to mutate this
     * metadata instance you must use some mutator method instead of trying to directly manipulate the list of field metadata.
     * 
     * @return the list of field metadata this metadata contains.
     */
    public List<FieldMetadata> getFieldMetadata() {
        return Collections.unmodifiableList(this.fieldMetadata);
    }

    /**
     * <p>
     * Sets the list of field metadata that this record metadata will contain. This means that this metadata will describe a record that
     * may contain a list of fields, each of them described by the corresponding field metadata, and the fields will be arranged in the
     * order specified by the list.
     * </p>
     * <p>
     * Passing a <code>null</code> value or including a null field metadata in the list will cause the method to throw an exception. If
     * you want to have a record metadata describing a record that can't hold any field, either use the
     * {@linkplain RecordMetadata#RecordMetadata() default constructor} or pass an empty list to this method.
     * </p>
     * <p>
     * It isn't allowed to have two or more fields in the same record with the same name, so this method will check for duplicated names,
     * and throw an exception in that case.
     * </p>
     * 
     * @param fieldMetadata The list of field metadata this record metadata will contain.
     * @throws NullPointerException if <code>fieldMetadata</code> is <code>null</code> or contains a <code>null</code> item.
     * @throws IllegalArgumentException if the list of field metadata contains duplicated field names.
     */
    public void setFieldMetadata(List<FieldMetadata> fieldMetadata) {
        LinkedHashMap<String, Integer> fieldIndexes = new LinkedHashMap<String, Integer>();
        int index = 0;
        for (FieldMetadata currentFieldMetadata : fieldMetadata) {
            fieldIndexes.put(currentFieldMetadata.getName(), index);
            index++;
        }

        if (fieldMetadata.size() == fieldIndexes.size()) {
            this.fieldMetadata = new ArrayList<FieldMetadata>(fieldMetadata);
            this.fieldIndexes = fieldIndexes;
        } else {
            // There are duplicated field names
            String errorMessage = RecordMetadata.messages.getString("message.duplicated.names");
            if (RecordMetadata.log.isInfoEnabled()) {
                RecordMetadata.log
                        .info(RecordMetadata.messages.getString("exception.duplicated.names") + errorMessage);
            }
            throw new IllegalArgumentException(errorMessage);
        }
    }

    /**
     * Returns the number of fields the record described by this metadata may contain.
     * 
     * @return the number of fields in this metadata.
     */
    public int getFieldCount() {
        return this.fieldMetadata.size();
    }

    /**
     * Returns the <code>0</code> based position of the field with the supplied <code>name</code> in this metadata. If no field with
     * that <code>name</code> may be found, <code>-1</code> is returned.
     * 
     * @param fieldName the name of the field to be located.
     * @return the position of the field (<code>0</code> based), <code>-1</code> if not found.
     */
    public int getFieldIndex(String fieldName) {
        Integer index = this.fieldIndexes.get(fieldName);
        return (index != null) ? index : -1;
    }

    /**
     * Returns a list of the names of the fields described by this metadata. The returned list is not backed by the current list of field
     * metadata, so a change in this metadata won't be reflected in a previously retrieved list.
     * 
     * @return a list of names of the fields described by this metadata.
     */
    public List<String> getFieldNames() {
        return new ArrayList<String>(this.fieldIndexes.keySet());
    }

    /**
     * Returns the metadata associated with the field with the supplied name. If there is no field metadata with the supplied name, an
     * exception is thrown.
     * 
     * @param fieldName the name of the field which metadata we want to get.
     * @return the metadata of the field with the supplied name.
     * @throws NoSuchFieldNameException if no field metadata can be found with the specified name.
     */
    public FieldMetadata getFieldMetadata(String fieldName) {
        Integer index = this.fieldIndexes.get(fieldName);
        if (index != null) {
            return this.fieldMetadata.get(index);
        } else {
            String errorMessage = MessageFormat.format(RecordMetadata.messages.getString("message.invalid.name"),
                    fieldName);
            if (RecordMetadata.log.isInfoEnabled()) {
                RecordMetadata.log.info(RecordMetadata.messages.getString("exception.invalid.name") + errorMessage);
            }
            throw new NoSuchFieldNameException(errorMessage);
        }
    }

    /**
     * Removes from this metadata all the field metadata with names included in the supplied collection.
     * 
     * @param fieldNames the names of the field metadata to remove.
     * @throws NullPointerException if the specified collection is <code>null</code>.
     */
    public void remove(Collection<String> fieldNames) {
        LinkedHashMap<String, Integer> fieldsToRemove = (LinkedHashMap<String, Integer>) this.fieldIndexes.clone();
        fieldsToRemove.keySet().retainAll(fieldNames);

        List<FieldMetadata> newFieldMetadata = (List<FieldMetadata>) this.fieldMetadata.clone();

        ListIterator<Integer> fieldIndexIterator = new ArrayList<Integer>(fieldsToRemove.values())
                .listIterator(fieldsToRemove.size());
        while (fieldIndexIterator.hasPrevious()) {
            newFieldMetadata.remove(fieldIndexIterator.previous());
        }

        this.setFieldMetadata(newFieldMetadata);
    }

    /**
     * Retains all the field metadata with names included in the suppled collection. In other words, removes from this metadata all the
     * field metadata with names not included in the supplied collection.
     * 
     * @param fieldNames the names of the field metadata to keep.
     * @throws NullPointerException if the specified collection is <code>null</code>.
     */
    public void retain(Collection<String> fieldNames) {
        LinkedHashMap<String, Integer> fieldsToRemove = (LinkedHashMap<String, Integer>) this.fieldIndexes.clone();
        fieldsToRemove.keySet().removeAll(fieldNames);
        this.remove(fieldsToRemove.keySet());
    }

    /**
     * Appends the field metadata contained in the supplied record metadata to the end of the field metadata contained in this metadata.
     * 
     * @param recordMetadata the metadata which field metadata will be appended to this metadata.
     * @throws NullPointerException if <code>recordMetadata</code> is <code>null</code>.
     * @throws IllegalArgumentException if the the supplied record metadata contains some field metadata with the same name that some
     *           field metadata in this metadata.
     */
    public void add(RecordMetadata recordMetadata) {
        List<FieldMetadata> newFieldMetadata = new ArrayList<FieldMetadata>(this.getFieldMetadata());
        newFieldMetadata.addAll(recordMetadata.getFieldMetadata());
        this.setFieldMetadata(newFieldMetadata);
    }

    /**
     * Returns a metadata containing all the field metadata of this record metadata with names not included in the supplied collection.
     * 
     * @param fieldNames the names of the field metadata to remove.
     * @return a metadata with the same field metadata that this metadata, supressing the specified field metadata.
     * @throws NullPointerException if the specified collection is <code>null</code>.
     */
    public RecordMetadata supress(Collection<String> fieldNames) {
        List<String> fieldNamesToExtract = new LinkedList<String>(this.fieldIndexes.keySet());
        fieldNamesToExtract.removeAll(fieldNames);
        return this.extract(fieldNamesToExtract);
    }

    /**
     * Returns a metadata containing all the field metadata of this record metadata with names included in the supplied list. The order
     * given in the supplied list is preserved in the resulting metadata.
     * 
     * @param fieldNames the names of the field metadata to extract.
     * @return a metadata containing all the field metadata in this metadata which field name is included in the supplied list.
     * @throws NullPointerException if the specified collection is <code>null</code>.
     * @throws IllegalArgumentException if the supplied list contains duplicated names.
     */
    public RecordMetadata extract(List<String> fieldNames) {
        List<FieldMetadata> fieldMetadata = new LinkedList<FieldMetadata>();
        for (String fieldName : fieldNames) {
            if (this.getFieldIndex(fieldName) != -1) {
                fieldMetadata.add(this.getFieldMetadata(fieldName));
            }
        }

        return new RecordMetadata(fieldMetadata);
    }

    /**
     * Returns a metadata containing the field metadata of this metadata concatenated with the field metadata contained in the supplied
     * record metadata.
     * 
     * @param recordMetadata the metadata which field metadata will be appended to this metadata.
     * @return a metadata containing the concatenated field metadata of this metadata and the supplied metadata.
     * @throws NullPointerException if <code>recordMetadata</code> is <code>null</code>.
     * @throws IllegalArgumentException if the the supplied record metadata contains some field metadata with the same name that some
     *           field metadata in this metadata.
     */
    public RecordMetadata concatenate(RecordMetadata recordMetadata) {
        List<FieldMetadata> newFieldMetadata = new ArrayList<FieldMetadata>(this.getFieldMetadata());
        newFieldMetadata.addAll(recordMetadata.getFieldMetadata());
        return new RecordMetadata(newFieldMetadata);
    }

    /**
     * Compares the specified object with this metadata for equality. Returns <code>true</code> if and only if the specified object is
     * also a record metadata, and the list of field metadata are equal.
     * 
     * @param object The object to be compared for equality with this metadata.
     * @return <code>true</code> if the specified object is equal to this metadata.
     */
    @Override
    public boolean equals(Object object) {
        return this == object || (object instanceof RecordMetadata
                && this.fieldMetadata.equals(((RecordMetadata) object).fieldMetadata));
    }

    /**
     * Returns the hash code value for this metadata.
     * 
     * @return the hash code value for this metadata.
     */
    @Override
    public int hashCode() {
        return this.fieldMetadata.hashCode();
    }

    /**
     * Returns a copy of this metadata. A shallow copy of the private instance variables is done, so changes in the cloned metadata
     * doesn't affect the original instance.
     * 
     * @return a clone of this <code>RecordMetadata</code> instance
     */
    @Override
    public RecordMetadata clone() {
        try {
            RecordMetadata clonedRecordMetadata = (RecordMetadata) super.clone();
            clonedRecordMetadata.fieldMetadata = (ArrayList<FieldMetadata>) this.fieldMetadata.clone();
            clonedRecordMetadata.fieldIndexes = (LinkedHashMap<String, Integer>) this.fieldIndexes.clone();
            return clonedRecordMetadata;
        } catch (CloneNotSupportedException exc) {
            RecordMetadata.log.fatal(RecordMetadata.messages.getString("error.clone.not.supported"), exc);
            throw new InternalError();
        }
    }

    /**
     * Returns a string representation of this record metadata. The string representation is a comma separated list of values surrounded
     * by square brackets.
     * 
     * @return a string representation of this record metadata.
     */
    @Override
    public String toString() {
        StringBuffer metadataString = new StringBuffer("[");
        for (FieldMetadata fieldMetadata : this.fieldMetadata) {
            metadataString.append(fieldMetadata.toString());
            metadataString.append(',');
        }
        if (!this.fieldMetadata.isEmpty()) {
            metadataString.deleteCharAt(metadataString.length() - 1);
        }
        metadataString.append(']');
        return metadataString.toString();
    }
}