package org.jaudiotagger.tag.datatype;
import org.jaudiotagger.tag.InvalidDataTypeException;
import org.jaudiotagger.tag.id3.AbstractTagFrameBody;
import org.jaudiotagger.utils.EqualsUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
/**
* Represents a data type that allow multiple Strings but they should be paired as key values, i.e should be 2,4,6..
* But keys are not unique so we don't store as a map, so could have same key pointing to two different values
* such as two ENGINEER keys
* <p/>
*/
public class PairedTextEncodedStringNullTerminated extends AbstractDataType {
public PairedTextEncodedStringNullTerminated(String identifier, AbstractTagFrameBody frameBody) {
super(identifier, frameBody);
value = new PairedTextEncodedStringNullTerminated.ValuePairs();
}
public PairedTextEncodedStringNullTerminated(TextEncodedStringSizeTerminated object) {
super(object);
value = new PairedTextEncodedStringNullTerminated.ValuePairs();
}
public PairedTextEncodedStringNullTerminated(PairedTextEncodedStringNullTerminated object) {
super(object);
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PairedTextEncodedStringNullTerminated)) {
return false;
}
PairedTextEncodedStringNullTerminated that = (PairedTextEncodedStringNullTerminated) obj;
return EqualsUtil.areEqual(value, that.value);
}
/**
* Returns the size in bytes of this dataType when written to file
*
* @return size of this dataType
*/
public int getSize() {
return size;
}
/**
* Check the value can be encoded with the specified encoding
*
* @return
*/
public boolean canBeEncoded() {
for (Pair entry : ((ValuePairs) value).mapping) {
TextEncodedStringNullTerminated next = new TextEncodedStringNullTerminated(identifier, frameBody, entry.getValue());
if (!next.canBeEncoded()) {
return false;
}
}
return true;
}
/**
* Read Null Terminated Strings from the array starting at offset, continue until unable to find any null terminated
* Strings or until reached the end of the array. The offset should be set to byte after the last null terminated
* String found.
*
* @param arr to read the Strings from
* @param offset in the array to start reading from
* @throws InvalidDataTypeException if unable to find any null terminated Strings
*/
public void readByteArray(byte[] arr, int offset) throws InvalidDataTypeException {
// logger.finer("Reading PairTextEncodedStringNullTerminated from array from offset:" + offset);
//Continue until unable to read a null terminated String
while (true) {
try {
//Read Key
TextEncodedStringNullTerminated key = new TextEncodedStringNullTerminated(identifier, frameBody);
key.readByteArray(arr, offset);
size += key.getSize();
offset += key.getSize();
if (key.getSize() == 0) {
break;
}
//Read Value
TextEncodedStringNullTerminated result = new TextEncodedStringNullTerminated(identifier, frameBody);
result.readByteArray(arr, offset);
size += result.getSize();
offset += result.getSize();
if (result.getSize() == 0) {
break;
}
//Add to value
((ValuePairs) value).add((String) key.getValue(), (String) result.getValue());
} catch (InvalidDataTypeException idte) {
break;
}
if (size == 0) {
//logger.warning("No null terminated Strings found");
throw new InvalidDataTypeException("No null terminated Strings found");
}
}
// logger.finer("Read PairTextEncodedStringNullTerminated:" + value + " size:" + size);
}
/**
* For every String write to byteBuffer
*
* @return byteBuffer that should be written to file to persist this dataType.
*/
public byte[] writeByteArray() {
// logger.finer("Writing PairTextEncodedStringNullTerminated");
int localSize = 0;
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
for (Pair pair : ((ValuePairs) value).mapping) {
{
TextEncodedStringNullTerminated next = new TextEncodedStringNullTerminated(identifier, frameBody, pair.getKey());
buffer.write(next.writeByteArray());
localSize += next.getSize();
}
{
TextEncodedStringNullTerminated next = new TextEncodedStringNullTerminated(identifier, frameBody, pair.getValue());
buffer.write(next.writeByteArray());
localSize += next.getSize();
}
}
} catch (IOException ioe) {
//This should never happen because the write is internal with the JVM it is not to a file
logger.log(Level.SEVERE, "IOException in MultipleTextEncodedStringNullTerminated when writing byte array", ioe);
throw new RuntimeException(ioe);
}
//Update size member variable
size = localSize;
//
//logger.finer("Written PairTextEncodedStringNullTerminated");
return buffer.toByteArray();
}
/**
* This holds the values held by this PairedTextEncodedDataType, always held as pairs of values
*/
public static class ValuePairs {
private List<Pair> mapping = new ArrayList<Pair>();
public ValuePairs() {
super();
}
/**
* Add String Data type to the value list
*
* @param value to add to the list
*/
public void add(String key, String value) {
mapping.add(new Pair(key, value));
}
/**
* Return the list of values
*
* @return the list of values
*/
public List<Pair> getMapping() {
return mapping;
}
/**
* @return no of values
*/
public int getNumberOfValues() {
return mapping.size();
}
/**
* Return the list of values as a single string separated by a colon,comma
*
* @return a string representation of the value
*/
public String toString() {
StringBuffer sb = new StringBuffer();
for (Pair next : mapping) {
sb.append(next.getKey() + ':' + next.getValue() + ',');
}
return sb.toString();
}
/**
* @return no of values
*/
public int getNumberOfPairs() {
return mapping.size();
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ValuePairs)) {
return false;
}
ValuePairs that = (ValuePairs) obj;
return EqualsUtil.areEqual(getNumberOfValues(), that.getNumberOfValues());
}
}
public ValuePairs getValue() {
return (ValuePairs) value;
}
}