ListData.java

// <editor-fold defaultstate="collapsed" desc="license">
/*
 * Copyright (c) 2014, Karl H. Beckers
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 **/
// </editor-fold>
package net.jarre_de_the.griffin.types.data;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.jarre_de_the.griffin.Util;
import net.jarre_de_the.griffin.exception.NoFieldFoundException;
import net.jarre_de_the.griffin.exception.NoFieldInListException;
import net.jarre_de_the.griffin.file.Gff;
import net.jarre_de_the.griffin.types.field.AbstractField;
import net.jarre_de_the.griffin.types.field.AbstractField.FieldType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of the LIST data type.
 *
 * @author charly4711
 * @param <T> StructData or one of its child classes
 */
public class ListData<T extends StructData> extends AbstractData
        implements Container, Cloneable {

    private static final Logger LOGGER = LoggerFactory.getLogger(ListData.class);
    private List<T> elements;

    /*
     *
     * constructors
     *
     */
    public ListData() {
    }

    public ListData(List<T> elements) {
        setValue(elements);
    }

    /*
     *
     * read from file
     *
     */
    public static ListData read(RandomAccessFile in,
                                Gff file) throws IOException {
        LOGGER.debug("Reading ListData from file.");
        // we're just hoping we'll never encounter a GFF file where a
        // single buffer is > MAXINT
        int size = DWordData.read(in).getValueAsNumber().intValue();
        LOGGER.trace("Need to read " + size + " elements.");

        List<StructData> readElements = new ArrayList<StructData>();
        for (int i = 0; i < size; i++) {
            long lOff = DWordData.read(in).getValueAsNumber();
            LOGGER.trace("Reading element " + i + " from offset: " + lOff);
            lOff *= StructData.STRUCT_LENGTH;
            long sPos = in.getFilePointer();

            in.seek(lOff + file.getStructOff() + file.getEmbeddedOffset());
            readElements.add(StructData.read(in, file));
            in.seek(sPos);
        }
        return new ListData(readElements);
    }

    /*
     *
     * setter
     *
     */
    private void setValue(List<T> elements) {
        this.elements = elements;
    }

    /*
     *
     * getter
     *
     */
    public List<T> getValueAsList() {
        return elements;
    }

    public T getValueElement(int i) {
        if (elements != null && elements.size() > i) {
            return elements.get(i);
        } else {
            return null;
        }
    }

    public int getSize() {
        if (elements == null) {
            return 0;
        }
        return elements.size();
    }

    /*
     *
     * utility
     *
     */
    /**
     * Implements Container
     * <p>
     * @param clazz
     * @param label
     * @return
     */
    @Override
    public <T extends AbstractField> List<FoundField<T>>
            findField(Class<T> clazz, byte[] label) {
        return findField(clazz, label, null, null);
    }

    @Override
    public <T extends AbstractField, M extends AbstractData> List<FoundField<T>>
            findField(Class<T> clazz, byte[] label, M value) {
        return findField(clazz, label, value, null);
    }

    @Override
    public <T extends AbstractField> List<FoundField<T>>
            findField(Class<T> clazz, byte[] label, String regex) {
        return findField(clazz, label, null, regex);
    }

    private <T extends AbstractField, M extends AbstractData> List<FoundField<T>>
            findField(Class<T> clazz, byte[] label, M value, String regex) {
        List<FoundField<T>> ret = new ArrayList<FoundField<T>>();
        LOGGER.debug("Trying to find field "
                + Util.getNullTerminatedString(label)
                + " of type: " + (clazz == null ? "null" : clazz.getSimpleName()));

        if (elements != null) {
            if (elements.size() > 0) {
                LOGGER.trace("Need to examine " + elements.size()
                        + " fields.");
                for (int i = 0; i < elements.size(); i++) {
                    StructData f = elements.get(i);

                    List<FoundField<T>> lf;
                    if (null != value) {
                        lf = f.findField(clazz, label, value);
                    } else if (null != regex) {
                        lf = f.findField(clazz, label, regex);
                    } else {
                        lf = f.findField(clazz, label);
                    }

                    if (lf != null) {
                        for (FoundField<T> foundField : lf) {
                            int[] localPath = new int[foundField.getPath().length + 1];
                            localPath[0] = i;
                            System.arraycopy(foundField.getPath(), 0, localPath, 1,
                                             foundField.getPath().length);

                            ret.add(new FoundField<T>(foundField.getField(), localPath));
                        }
                    }
                }
            } else {
                LOGGER.trace("No fields to examine.");
            }
        } else {
            LOGGER.trace("No fields to examine.");
        }
        LOGGER.debug(ret.size() + " elements found.");
        return ret;
    }

    /**
     * Implements Container as a no-op because ListData cannot contain a Field
     * directly.
     * <p>
     * @param clazz
     * @param label
     * @return
     */
    @Override
    public <T extends AbstractField> T findLocalField(Class<T> clazz, byte[] label) {
        FieldType ft = null;
        for (FieldType f : FieldType.values()) {
            if (f.getFieldClassOfType().equals(clazz)) {
                ft = f;
                break;
            }
        }
        throw new NoFieldFoundException(this, ft, label);
    }

    @Override
    public AbstractField findFieldByPath(int[] path) {
        if (path.length == 1) {
            throw new NoFieldInListException(this, path);
        }
        if (path.length > 1) {
            int here = path[0];
            StructData sd = this.getValueElement(here);
            if (sd != null) {
                AbstractField tmp = sd.getValueElement(path[1]);
                if (tmp != null && path.length == 2) {
                    return tmp;
                } else if (tmp != null && (tmp.getValue() instanceof Container)) {
                    return ((Container) tmp.getValue()).findFieldByPath(Arrays.copyOfRange(path, 2, path.length));
                }
            }
        }
        throw new NoFieldFoundException(this, path);
    }

    @Override
    public ListData clone() throws CloneNotSupportedException {
        LOGGER.debug("Cloning object");
        ListData clone = (ListData) super.clone();
        if (null != elements) {
            List<StructData> elem = new ArrayList<StructData>();
            for (StructData s : elements) {
                elem.add(s.clone());
            }
            clone.setValue(elem);
        } else {
            clone.setValue(null);
        }

        return clone;
    }

    @Override
    public boolean equals(Object compare) {
        if (compare == this) {
            return true;
        }

        if (!(compare instanceof ListData)) {
            return false;
        }

        ListData ld = (ListData) compare;
        if (ld.getValueAsList() == null && elements == null) {
            return true;
        } else if ((ld.getValueAsList() == null && elements != null) || (ld.getValueAsList() != null && elements == null)) {
            return false;
        }
        if (ld.getValueAsList().size() != elements.size()) {
            return false;
        }

        boolean equals = true;
        for (int i = 0; i < ld.getSize(); i++) {
            if (!ld.getValueElement(i).equals(this.getValueElement(i))) {
                equals = false;
                break;
            }
        }

        if (equals == true) {
            return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        int result = this.getSize();
        if (result > 0) {
            result ^= Arrays.hashCode(elements.toArray());
        }
        return result;
    }
}