1/*
2 * Copyright 2008 CoreMedia AG, Hamburg
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.googlecode.mp4parser;
18
19import com.coremedia.iso.BoxParser;
20import com.coremedia.iso.boxes.Box;
21import com.coremedia.iso.boxes.ContainerBox;
22import com.googlecode.mp4parser.util.ByteBufferByteChannel;
23
24import java.io.IOException;
25import java.nio.ByteBuffer;
26import java.nio.channels.ReadableByteChannel;
27import java.nio.channels.WritableByteChannel;
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.LinkedList;
31import java.util.List;
32import java.util.logging.Logger;
33
34
35/**
36 * Abstract base class suitable for most boxes acting purely as container for other boxes.
37 */
38public abstract class AbstractContainerBox extends AbstractBox implements ContainerBox {
39    private static Logger LOG = Logger.getLogger(AbstractContainerBox.class.getName());
40
41    protected List<Box> boxes = new LinkedList<Box>();
42    protected BoxParser boxParser;
43
44    @Override
45    protected long getContentSize() {
46        long contentSize = 0;
47        for (Box boxe : boxes) {
48            contentSize += boxe.getSize();
49        }
50        return contentSize;
51    }
52
53    public AbstractContainerBox(String type) {
54        super(type);
55    }
56
57    public List<Box> getBoxes() {
58        return Collections.unmodifiableList(boxes);
59    }
60
61    public void setBoxes(List<Box> boxes) {
62        this.boxes = new LinkedList<Box>(boxes);
63    }
64
65    @SuppressWarnings("unchecked")
66    public <T extends Box> List<T> getBoxes(Class<T> clazz) {
67        return getBoxes(clazz, false);
68    }
69
70    @SuppressWarnings("unchecked")
71    public <T extends Box> List<T> getBoxes(Class<T> clazz, boolean recursive) {
72        List<T> boxesToBeReturned = new ArrayList<T>(2);
73        for (Box boxe : boxes) {
74            //clazz.isInstance(boxe) / clazz == boxe.getClass()?
75            // I hereby finally decide to use isInstance
76
77            if (clazz.isInstance(boxe)) {
78                boxesToBeReturned.add((T) boxe);
79            }
80
81            if (recursive && boxe instanceof ContainerBox) {
82                boxesToBeReturned.addAll(((ContainerBox) boxe).getBoxes(clazz, recursive));
83            }
84        }
85        return boxesToBeReturned;
86    }
87
88    /**
89     * Add <code>b</code> to the container and sets the parent correctly.
90     *
91     * @param b will be added to the container
92     */
93    public void addBox(Box b) {
94        b.setParent(this);
95        boxes.add(b);
96    }
97
98    public void removeBox(Box b) {
99        b.setParent(this);
100        boxes.remove(b);
101    }
102
103    @Override
104    public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
105        this.boxParser = boxParser;
106        super.parse(readableByteChannel, header, contentSize, boxParser);
107    }
108
109    @Override
110    public void _parseDetails(ByteBuffer content) {
111        parseChildBoxes(content);
112    }
113
114
115    public String toString() {
116        StringBuilder buffer = new StringBuilder();
117
118        buffer.append(this.getClass().getSimpleName()).append("[");
119        for (int i = 0; i < boxes.size(); i++) {
120            if (i > 0) {
121                buffer.append(";");
122            }
123            buffer.append(boxes.get(i).toString());
124        }
125        buffer.append("]");
126        return buffer.toString();
127    }
128
129    /**
130     * The number of bytes from box start (first length byte) to the
131     * first length byte of the first child box
132     *
133     * @return offset to first child box
134     */
135    public long getNumOfBytesToFirstChild() {
136        return 8;
137    }
138
139    @Override
140    protected void getContent(ByteBuffer byteBuffer) {
141        writeChildBoxes(byteBuffer);
142    }
143
144    protected final void parseChildBoxes(ByteBuffer content) {
145        try {
146            while (content.remaining() >= 8) { //  8 is the minimal size for a sane box
147                boxes.add(boxParser.parseBox(new ByteBufferByteChannel(content), this));
148            }
149
150            if (content.remaining() != 0) {
151                setDeadBytes(content.slice());
152                LOG.warning("Something's wrong with the sizes. There are dead bytes in a container box.");
153            }
154        } catch (IOException e) {
155            throw new RuntimeException(e);
156        }
157    }
158
159    protected final void writeChildBoxes(ByteBuffer bb) {
160        WritableByteChannel wbc = new ByteBufferByteChannel(bb);
161        for (Box box : boxes) {
162            try {
163                box.getBox(wbc);
164            } catch (IOException e) {
165                // My WritableByteChannel won't throw any excpetion
166                throw new RuntimeException("Cannot happen to me", e);
167            }
168        }
169    }
170
171}
172