StructData.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.PrintStream;
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.LabelNotUniqueException;
import net.jarre_de_the.griffin.exception.NoFieldFoundException;
import net.jarre_de_the.griffin.file.Gff;
import net.jarre_de_the.griffin.types.field.*;
import net.jarre_de_the.griffin.types.field.AbstractField.FieldType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of STRUCT. The GFF STRUCT physically contains an ID, a pointer
* to where the contained elements are stored and the element count. The STRUCT
* data type contains a number of fields including other STRUCTs.
* <p>
* @author charly4711
*/
public class StructData
extends AbstractData
implements Container, Cloneable {
/**
* The size of a STRUCT in number of bytes for the STRUCT element itself,
* not including the fields contained (only the pointer).
*/
public static final int STRUCT_LENGTH = 12;
private static final Logger LOGGER = LoggerFactory.getLogger(StructData.class);
private DWordData id;
private List<AbstractField> fields;
/*
*
* constructors
*
*/
public StructData() {
id = new DWordData(-1);
}
/**
* Creates a new instance of {@code StructData} and sets the specified
* values for id and fields contained.
* <p>
* @param id The field ID to set.
* @param fields The fields to be contained in the new {@code StructData}.
*/
public StructData(DWordData id,
List<AbstractField> fields) {
setId(id);
this.fields = fields;
}
/*
*
* read from file
*
*/
/**
* Creates a new instance of {@code StructData} based on the data read from
* the file passed. Note that the contents are read from whatever position
* the file pointer is at.
* <p>
* @param in An open {@code RandomAccessFile}.
* @return
* @throws java.io.IOException If there are any I/O related problems with
* file access to in.
*/
public static StructData read(RandomAccessFile in,
Gff file)
throws IOException {
StructData ret = new StructData();
LOGGER.debug("Reading StructData from file.");
// read ID first
ret.setId(DWordData.read(in));
LOGGER.trace("Read this id: " + ret.getId().getValueAsNumber());
// offset is next
long offset = DWordData.read(in).getValueAsNumber();
LOGGER.trace("Read this offset: " + offset);
// field count next
long fcount = DWordData.read(in).getValueAsNumber();
LOGGER.trace("Have to read " + fcount + " fields.");
// go and fetch the fields from the known offset and to the known count
ret.setFieldValue(readFields(in,
(int) offset, (int) fcount, file));
LOGGER.debug("Done reading StructData.");
return ret;
}
/**
* Reads the fields contained in this {@code StructData} from a GFF file.
* This is called by {@link #read(java.io.RandomAccessFile, net.jarre_de_the.griffin.object.File)
* }
* and gets the correct offset into the field or field index array and the
* correct field count from there.
* <p>
* @param in File to read from.
* @param offset Offset into the field array or field index array depending
* on the {@code count}.
* @param count Number of fields contained in this instance.
* @param file File object with the required information about the various
* GFF file arrays.
* @return A list of fields contained in this instance.
* @throws java.io.IOException If there are any I/O related problems with
* file access to in.
*/
private static List<AbstractField> readFields(RandomAccessFile in,
int offset,
int count,
Gff file)
throws IOException {
List<AbstractField> lFields = new ArrayList<AbstractField>();
// return a null list, if there are no children
if (count == 0) {
LOGGER.trace("Was asked to read a list of fields, where "
+ "the number of fields to read was zero.");
return null;
}
// if there are more than one field in this struct ...
if (count > 1) {
LOGGER.trace("Was asked to read a list of fields.");
// we need to look for pointers to the fields themselves off the
// field index ... what we retrieved as offset should be a byte
// offset to the field index
long fOff = file.getFieldIdxOff() + file.getEmbeddedOffset();
fOff += offset;
LOGGER.trace("Absolute field index offset: " + fOff);
// go there
in.seek(fOff);
for (int i = 0; i < count; i++) {
// then start reading one DWord after another
long fPos = DWordData.read(in).getValueAsNumber();
fPos = (fPos * AbstractField.FIELD_LENGTH) + file.getFieldOff() + file.getEmbeddedOffset();
LOGGER.trace("Absolute field offset: " + fPos);
// store the position in the field index (which is that of the
// next element to read
long fIdxPos = in.getFilePointer();
in.seek(fPos);
lFields.add(AbstractField.readField(in, file));
in.seek(fIdxPos);
}
} else {
LOGGER.trace("Was asked to read a single field.");
long fOff = file.getFieldOff() + file.getEmbeddedOffset();
fOff += (1L * offset * AbstractField.FIELD_LENGTH);
LOGGER.trace("Absolute field offset: " + fOff);
in.seek(fOff);
lFields.add(AbstractField.readField(in, file));
}
LOGGER.trace("Returning " + lFields.size() + " fields.");
return lFields;
}
/*
*
* setter
*
*/
private void setId(DWordData id) {
if (null != id) {
try {
this.id = id.clone();
} catch (CloneNotSupportedException ex) {
LOGGER.error(null, ex);
}
} else {
this.id = null;
}
}
private void setFieldValue(List<AbstractField> fields) {
this.fields = fields;
}
/*
*
* getter
*
*/
public DWordData getId() {
DWordData result = null;
if (null != id) {
try {
result = id.clone();
} catch (CloneNotSupportedException ex) {
LOGGER.error(null, ex);
}
}
return result;
}
/**
* Retrieves the list of fields contained in this instance.
* <p>
* @return This instance's fields.
*/
public List<AbstractField> getValueAsList() {
return fields;
}
/**
* Retrieves a specific, numbered element of the fields contained in this
* instance.
* <p>
* @param i Index of the element to return from this instance's list of
* contained fields.
* @return A single field contained in this instance.
*/
public AbstractField getValueElement(int i) {
if (fields != null && fields.size() > i) {
return fields.get(i);
} else {
return null;
}
}
/*
*
* 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, 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, null);
}
@Override
public <T extends AbstractField> List<FoundField<T>>
findField(Class<T> clazz, byte[] label, String regex) {
return findField(clazz, label, null, regex, null);
}
protected <T extends AbstractField, M extends AbstractData> List<FoundField<T>>
findField(Class<T> clazz, byte[] label,
M value, String regex, int[] path) {
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 (this.fields != null) {
if (this.fields.size() > 0) {
LOGGER.trace("Need to examine " + this.fields.size()
+ " fields.");
for (int i = 0; i < fields.size(); i++) {
T ef = (T) fields.get(i);
int[] localPath;
if (null != path) {
localPath = new int[1 + path.length];
System.arraycopy(path, 0, localPath, 0, path.length);
localPath[localPath.length - 1] = i;
} else {
localPath = new int[]{i};
}
examineField(ret, ef, clazz, label, value, regex, localPath);
}
} else {
LOGGER.trace("No fields to examine.");
}
} else {
LOGGER.trace("No fields to examine.");
}
LOGGER.debug(ret.size() + " elements found.");
return ret;
}
private <T extends AbstractField, M extends AbstractData> void examineField(
List<FoundField<T>> found,
T examine,
Class<T> clazz,
byte[] label,
M value,
String regex,
int[] path) {
List<FoundField<T>> f;
if (null != examine) {
if (clazz == null
|| examine.getType().getFieldClassOfType().equals(clazz)) {
if (label == null || Arrays.equals(examine.getLabel(),
label)) {
boolean match = false;
if (null != value) {
if (value.equals(examine.getData())) {
match = true;
}
} else if (null != regex) {
if (null != examine.getData()
&& examine.getData().toString().matches(regex)) {
match = true;
}
} else {
match = true;
}
if (match) {
LOGGER.trace("Match found.");
found.add(new FoundField<T>(examine, path));
}
}
}
if (examine.getType().equals(AbstractField.FieldType.Struct)) {
LOGGER.trace("StructField found, recursing.");
StructField s = (StructField) examine;
StructData sd = s.getValue();
if (sd != null) {
f = sd.findField(clazz, label, value, regex, path);
if (f != null) {
found.addAll(f);
}
}
} else if (examine.getValue() != null && examine.getValue() instanceof Container) {
LOGGER.trace(examine.getType() + " found, recursing.");
Container c = (Container) examine.getValue();
if (null != value) {
f = c.findField(clazz, label, value);
} else if (null != regex) {
f = c.findField(clazz, label, regex);
} else {
f = c.findField(clazz, label);
}
if (f != null) {
for (FoundField<T> foundField : f) {
int[] localPath;
if (null != path) {
localPath = new int[foundField.getPath().length + path.length];
System.arraycopy(path, 0, localPath, 0, path.length);
System.arraycopy(foundField.getPath(), 0, localPath, path.length, foundField.getPath().length);
} else {
// this cannot really happen
// path can only be null if findFields is called on
// the struct itself and then we cannot be in a ListField
localPath = foundField.getPath();
}
found.add(new FoundField<T>(foundField.getField(), localPath));
}
}
}
}
}
/**
* Implements Container
* <p>
* @param clazz
* @param label
* @return
*/
@Override
public <T extends AbstractField> T findLocalField(Class<T> clazz, byte[] label) {
LOGGER.debug("Trying to find local field "
+ Util.getNullTerminatedString(label)
+ " of type: " + (clazz == null ? "null" : clazz.getSimpleName()));
T result = null;
if (null != fields && null != label) {
if (fields.size() > 0) {
LOGGER.trace("Need to examine " + fields.size()
+ " fields.");
for (AbstractField field : fields) {
T f = (T) field;
if ((null == clazz || f.getType().getFieldClassOfType().equals(clazz))
&& Arrays.equals(f.getLabel(), label)) {
LOGGER.trace("Match found.");
if (result != null) {
FieldType ft = null;
for (FieldType type : FieldType.values()) {
if (type.getFieldClassOfType().equals(clazz)) {
ft = type;
break;
}
}
throw new LabelNotUniqueException(this, ft, label);
}
result = f;
}
}
} else {
LOGGER.trace("No fields to examine.");
}
} else {
LOGGER.trace("No fields to examine or no label specified.");
}
if (result == null) {
FieldType ft = null;
for (FieldType f : FieldType.values()) {
if (f.getFieldClassOfType().equals(clazz)) {
ft = f;
break;
}
}
throw new NoFieldFoundException(this, ft, label);
} else {
return result;
}
}
@Override
public AbstractField findFieldByPath(int[] path) {
if (path.length > 0) {
int here = path[0];
AbstractField tmp = this.getValueElement(here);
if (path.length == 1) {
return tmp;
} else if (tmp != null && tmp.getValue() != null
&& tmp.getValue() instanceof Container) {
return ((Container) tmp.getValue()).findFieldByPath(Arrays.copyOfRange(path, 1, path.length));
}
}
throw new NoFieldFoundException(this, path);
}
@Override
public StructData clone() throws CloneNotSupportedException {
LOGGER.debug("Cloning object");
StructData clone = (StructData) super.clone();
clone.setId(id);
List<AbstractField> fl = new ArrayList<AbstractField>();
if (fields != null) {
for (AbstractField f : fields) {
fl.add(f.clone());
}
clone.setFieldValue(fl);
}
return clone;
}
@Override
public boolean equals(Object compare) {
if (compare == this) {
return true;
}
if (!(compare instanceof StructData)) {
return false;
}
StructData fl = (StructData) compare;
if (fl.getValueAsList() == null && fields == null) {
return true && id.equals(fl.getId());
} else if ((fl.getValueAsList() == null && fields != null) || (fl.getValueAsList() != null && fields == null)) {
return false;
}
if (fl.getValueAsList().size() != fields.size()) {
return false;
}
boolean equals = true;
for (int i = 0; i < fl.getValueAsList().size(); i++) {
if (!fl.getValueElement(i).equals(fields.get(i))) {
equals = false;
break;
}
}
return equals == true && id.equals(fl.getId());
}
@Override
public int hashCode() {
int i = id.getValue();
if (fields != null) {
i |= fields.size();
if (fields.size() > 0) {
i ^= Arrays.hashCode(fields.toArray());
}
}
return i;
}
// private void dump(AbstractField field, StringBuilder sb, int indent) {
// String space = " ";
//
// for (int i = 0; i < indent; i++) {
// sb.append(space);
// }
// sb.append(field.getType().name()).append(": ").append(field.getLabelString()).append("\n");
//
// if (FieldType.Struct.equals(field.getType())) {
// StructData sd = ((StructField) field).getValue();
// for (int i = 0; i < indent; i++) {
// sb.append(space);
// }
// sb.append(space).append("STRUCT: ").append(sd.getId()).append("\n");
// if (null != sd.getValueAsList()) {
// for (int i = 0; i < sd.getValueAsList().size(); i++) {
// sb.append(i);
// dump(sd.getValueElement(i), sb, indent + 2);
// }
// }
// } else if (FieldType.List.equals(field.getType())) {
// ListData ld = ((ListField) field).getValue();
// for (int l = 0; l < ld.getValueAsList().size(); l++) {
// StructData listItem = ld.getValueElement(l);
// sb.append(l);
// for (int i = 0; i < indent; i++) {
// sb.append(space);
// }
// sb.append(space).append("STRUCT: ").append(listItem.getId()).append("\n");
//
// for (int x = 0; x < listItem.getValueAsList().size(); x++) {
//// for (int y = 0; y < listItem.getValueAsList().size(); y++) {
// sb.append(l).append("-").append(x);
// dump(listItem.getValueElement(x), sb, indent + 2);
//// }
// }
// }
// }
// }
public void dump(PrintStream out) {
int indent = 0;
out.println("STRUCT: " + this.getId());
for (AbstractField f : this.getValueAsList()) {
f.printString(out, indent + 1);
}
}
}