1package com.coremedia.iso.boxes.apple;
2
3import com.coremedia.iso.IsoTypeReader;
4import com.coremedia.iso.IsoTypeWriter;
5import com.coremedia.iso.Utf8;
6import com.googlecode.mp4parser.AbstractBox;
7import com.coremedia.iso.boxes.Box;
8import com.coremedia.iso.boxes.ContainerBox;
9import com.googlecode.mp4parser.util.ByteBufferByteChannel;
10
11import java.io.IOException;
12import java.math.BigInteger;
13import java.nio.ByteBuffer;
14import java.util.Collections;
15import java.util.List;
16import java.util.logging.Logger;
17
18/**
19 *
20 */
21public abstract class AbstractAppleMetaDataBox extends AbstractBox implements ContainerBox {
22    private static Logger LOG = Logger.getLogger(AbstractAppleMetaDataBox.class.getName());
23    AppleDataBox appleDataBox = new AppleDataBox();
24
25    public List<Box> getBoxes() {
26        return Collections.singletonList((Box) appleDataBox);
27    }
28
29    public void setBoxes(List<Box> boxes) {
30        if (boxes.size() == 1 && boxes.get(0) instanceof AppleDataBox) {
31            appleDataBox = (AppleDataBox) boxes.get(0);
32        } else {
33            throw new IllegalArgumentException("This box only accepts one AppleDataBox child");
34        }
35    }
36
37    public <T extends Box> List<T> getBoxes(Class<T> clazz) {
38        return getBoxes(clazz, false);
39    }
40
41    public <T extends Box> List<T> getBoxes(Class<T> clazz, boolean recursive) {
42        //todo recursive?
43        if (clazz.isAssignableFrom(appleDataBox.getClass())) {
44            return (List<T>) Collections.singletonList(appleDataBox);
45        }
46        return null;
47    }
48
49    public AbstractAppleMetaDataBox(String type) {
50        super(type);
51    }
52
53    @Override
54    public void _parseDetails(ByteBuffer content) {
55        long dataBoxSize = IsoTypeReader.readUInt32(content);
56        String thisShouldBeData = IsoTypeReader.read4cc(content);
57        assert "data".equals(thisShouldBeData);
58        appleDataBox = new AppleDataBox();
59        try {
60            appleDataBox.parse(new ByteBufferByteChannel(content), null, content.remaining(), null);
61        } catch (IOException e) {
62            throw new RuntimeException(e);
63        }
64        appleDataBox.setParent(this);
65    }
66
67
68    protected long getContentSize() {
69        return appleDataBox.getSize();
70    }
71
72    protected void getContent(ByteBuffer byteBuffer) {
73        try {
74            appleDataBox.getBox(new ByteBufferByteChannel(byteBuffer));
75        } catch (IOException e) {
76            throw new RuntimeException("The Channel is based on a ByteBuffer and therefore it shouldn't throw any exception");
77        }
78    }
79
80    public long getNumOfBytesToFirstChild() {
81        return getSize() - appleDataBox.getSize();
82    }
83
84    @Override
85    public String toString() {
86        return this.getClass().getSimpleName() + "{" +
87                "appleDataBox=" + getValue() +
88                '}';
89    }
90
91    static long toLong(byte b) {
92        return b < 0 ? b + 256 : b;
93    }
94
95    public void setValue(String value) {
96        if (appleDataBox.getFlags() == 1) {
97            appleDataBox = new AppleDataBox();
98            appleDataBox.setVersion(0);
99            appleDataBox.setFlags(1);
100            appleDataBox.setFourBytes(new byte[4]);
101            appleDataBox.setData(Utf8.convert(value));
102        } else if (appleDataBox.getFlags() == 21) {
103            byte[] content = appleDataBox.getData();
104            appleDataBox = new AppleDataBox();
105            appleDataBox.setVersion(0);
106            appleDataBox.setFlags(21);
107            appleDataBox.setFourBytes(new byte[4]);
108
109            ByteBuffer bb = ByteBuffer.allocate(content.length);
110            if (content.length == 1) {
111                IsoTypeWriter.writeUInt8(bb, (Byte.parseByte(value) & 0xFF));
112            } else if (content.length == 2) {
113                IsoTypeWriter.writeUInt16(bb, Integer.parseInt(value));
114            } else if (content.length == 4) {
115                IsoTypeWriter.writeUInt32(bb, Long.parseLong(value));
116            } else if (content.length == 8) {
117                IsoTypeWriter.writeUInt64(bb, Long.parseLong(value));
118            } else {
119                throw new Error("The content length within the appleDataBox is neither 1, 2, 4 or 8. I can't handle that!");
120            }
121            appleDataBox.setData(bb.array());
122        } else if (appleDataBox.getFlags() == 0) {
123            appleDataBox = new AppleDataBox();
124            appleDataBox.setVersion(0);
125            appleDataBox.setFlags(0);
126            appleDataBox.setFourBytes(new byte[4]);
127            appleDataBox.setData(hexStringToByteArray(value));
128
129        } else {
130            LOG.warning("Don't know how to handle appleDataBox with flag=" + appleDataBox.getFlags());
131        }
132    }
133
134    public String getValue() {
135        if (appleDataBox.getFlags() == 1) {
136            return Utf8.convert(appleDataBox.getData());
137        } else if (appleDataBox.getFlags() == 21) {
138            byte[] content = appleDataBox.getData();
139            long l = 0;
140            int current = 1;
141            int length = content.length;
142            for (byte b : content) {
143                l += toLong(b) << (8 * (length - current++));
144            }
145            return "" + l;
146        } else if (appleDataBox.getFlags() == 0) {
147            return String.format("%x", new BigInteger(appleDataBox.getData()));
148        } else {
149            return "unknown";
150        }
151    }
152
153    public static byte[] hexStringToByteArray(String s) {
154        int len = s.length();
155        byte[] data = new byte[len / 2];
156        for (int i = 0; i < len; i += 2) {
157            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
158                    + Character.digit(s.charAt(i + 1), 16));
159        }
160        return data;
161    }
162
163
164}
165