1/*
2 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.media.sound;
27
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.IOException;
31import java.io.OutputStream;
32import java.io.RandomAccessFile;
33
34/**
35 * Resource Interchange File Format (RIFF) stream encoder.
36 *
37 * @author Karl Helgason
38 */
39public final class RIFFWriter extends OutputStream {
40
41    private interface RandomAccessWriter {
42
43        void seek(long chunksizepointer) throws IOException;
44
45        long getPointer() throws IOException;
46
47        void close() throws IOException;
48
49        void write(int b) throws IOException;
50
51        void write(byte[] b, int off, int len) throws IOException;
52
53        void write(byte[] bytes) throws IOException;
54
55        long length() throws IOException;
56
57        void setLength(long i) throws IOException;
58    }
59
60    private static class RandomAccessFileWriter implements RandomAccessWriter {
61
62        RandomAccessFile raf;
63
64        RandomAccessFileWriter(File file) throws FileNotFoundException {
65            this.raf = new RandomAccessFile(file, "rw");
66        }
67
68        RandomAccessFileWriter(String name) throws FileNotFoundException {
69            this.raf = new RandomAccessFile(name, "rw");
70        }
71
72        @Override
73        public void seek(long chunksizepointer) throws IOException {
74            raf.seek(chunksizepointer);
75        }
76
77        @Override
78        public long getPointer() throws IOException {
79            return raf.getFilePointer();
80        }
81
82        @Override
83        public void close() throws IOException {
84            raf.close();
85        }
86
87        @Override
88        public void write(int b) throws IOException {
89            raf.write(b);
90        }
91
92        @Override
93        public void write(byte[] b, int off, int len) throws IOException {
94            raf.write(b, off, len);
95        }
96
97        @Override
98        public void write(byte[] bytes) throws IOException {
99            raf.write(bytes);
100        }
101
102        @Override
103        public long length() throws IOException {
104            return raf.length();
105        }
106
107        @Override
108        public void setLength(long i) throws IOException {
109            raf.setLength(i);
110        }
111    }
112
113    private static class RandomAccessByteWriter implements RandomAccessWriter {
114
115        byte[] buff = new byte[32];
116        int length = 0;
117        int pos = 0;
118        byte[] s;
119        final OutputStream stream;
120
121        RandomAccessByteWriter(OutputStream stream) {
122            this.stream = stream;
123        }
124
125        @Override
126        public void seek(long chunksizepointer) throws IOException {
127            pos = (int) chunksizepointer;
128        }
129
130        @Override
131        public long getPointer() throws IOException {
132            return pos;
133        }
134
135        @Override
136        public void close() throws IOException {
137            stream.write(buff, 0, length);
138            stream.close();
139        }
140
141        @Override
142        public void write(int b) throws IOException {
143            if (s == null)
144                s = new byte[1];
145            s[0] = (byte)b;
146            write(s, 0, 1);
147        }
148
149        @Override
150        public void write(byte[] b, int off, int len) throws IOException {
151            int newsize = pos + len;
152            if (newsize > length)
153                setLength(newsize);
154            int end = off + len;
155            for (int i = off; i < end; i++) {
156                buff[pos++] = b[i];
157            }
158        }
159
160        @Override
161        public void write(byte[] bytes) throws IOException {
162            write(bytes, 0, bytes.length);
163        }
164
165        @Override
166        public long length() throws IOException {
167            return length;
168        }
169
170        @Override
171        public void setLength(long i) throws IOException {
172            length = (int) i;
173            if (length > buff.length) {
174                int newlen = Math.max(buff.length << 1, length);
175                byte[] newbuff = new byte[newlen];
176                System.arraycopy(buff, 0, newbuff, 0, buff.length);
177                buff = newbuff;
178            }
179        }
180    }
181    private int chunktype = 0; // 0=RIFF, 1=LIST; 2=CHUNK
182    private RandomAccessWriter raf;
183    private final long chunksizepointer;
184    private final long startpointer;
185    private RIFFWriter childchunk = null;
186    private boolean open = true;
187    private boolean writeoverride = false;
188
189    public RIFFWriter(String name, String format) throws IOException {
190        this(new RandomAccessFileWriter(name), format, 0);
191    }
192
193    public RIFFWriter(File file, String format) throws IOException {
194        this(new RandomAccessFileWriter(file), format, 0);
195    }
196
197    public RIFFWriter(OutputStream stream, String format) throws IOException {
198        this(new RandomAccessByteWriter(stream), format, 0);
199    }
200
201    private RIFFWriter(RandomAccessWriter raf, String format, int chunktype)
202            throws IOException {
203        if (chunktype == 0)
204            if (raf.length() != 0)
205                raf.setLength(0);
206        this.raf = raf;
207        if (raf.getPointer() % 2 != 0)
208            raf.write(0);
209
210        if (chunktype == 0)
211            raf.write("RIFF".getBytes("ascii"));
212        else if (chunktype == 1)
213            raf.write("LIST".getBytes("ascii"));
214        else
215            raf.write((format + "    ").substring(0, 4).getBytes("ascii"));
216
217        chunksizepointer = raf.getPointer();
218        this.chunktype = 2;
219        writeUnsignedInt(0);
220        this.chunktype = chunktype;
221        startpointer = raf.getPointer();
222        if (chunktype != 2)
223            raf.write((format + "    ").substring(0, 4).getBytes("ascii"));
224
225    }
226
227    public void seek(long pos) throws IOException {
228        raf.seek(pos);
229    }
230
231    public long getFilePointer() throws IOException {
232        return raf.getPointer();
233    }
234
235    public void setWriteOverride(boolean writeoverride) {
236        this.writeoverride = writeoverride;
237    }
238
239    public boolean getWriteOverride() {
240        return writeoverride;
241    }
242
243    @Override
244    public void close() throws IOException {
245        if (!open)
246            return;
247        if (childchunk != null) {
248            childchunk.close();
249            childchunk = null;
250        }
251
252        int bakchunktype = chunktype;
253        long fpointer = raf.getPointer();
254        raf.seek(chunksizepointer);
255        chunktype = 2;
256        writeUnsignedInt(fpointer - startpointer);
257
258        if (bakchunktype == 0)
259            raf.close();
260        else
261            raf.seek(fpointer);
262        open = false;
263        raf = null;
264    }
265
266    @Override
267    public void write(int b) throws IOException {
268        if (!writeoverride) {
269            if (chunktype != 2) {
270                throw new IllegalArgumentException(
271                        "Only chunks can write bytes!");
272            }
273            if (childchunk != null) {
274                childchunk.close();
275                childchunk = null;
276            }
277        }
278        raf.write(b);
279    }
280
281    @Override
282    public void write(byte b[], int off, int len) throws IOException {
283        if (!writeoverride) {
284            if (chunktype != 2) {
285                throw new IllegalArgumentException(
286                        "Only chunks can write bytes!");
287            }
288            if (childchunk != null) {
289                childchunk.close();
290                childchunk = null;
291            }
292        }
293        raf.write(b, off, len);
294    }
295
296    public RIFFWriter writeList(String format) throws IOException {
297        if (chunktype == 2) {
298            throw new IllegalArgumentException(
299                    "Only LIST and RIFF can write lists!");
300        }
301        if (childchunk != null) {
302            childchunk.close();
303            childchunk = null;
304        }
305        childchunk = new RIFFWriter(this.raf, format, 1);
306        return childchunk;
307    }
308
309    public RIFFWriter writeChunk(String format) throws IOException {
310        if (chunktype == 2) {
311            throw new IllegalArgumentException(
312                    "Only LIST and RIFF can write chunks!");
313        }
314        if (childchunk != null) {
315            childchunk.close();
316            childchunk = null;
317        }
318        childchunk = new RIFFWriter(this.raf, format, 2);
319        return childchunk;
320    }
321
322    // Write ASCII chars to stream
323    public void writeString(String string) throws IOException {
324        byte[] buff = string.getBytes();
325        write(buff);
326    }
327
328    // Write ASCII chars to stream
329    public void writeString(String string, int len) throws IOException {
330        byte[] buff = string.getBytes();
331        if (buff.length > len)
332            write(buff, 0, len);
333        else {
334            write(buff);
335            for (int i = buff.length; i < len; i++)
336                write(0);
337        }
338    }
339
340    // Write 8 bit signed integer to stream
341    public void writeByte(int b) throws IOException {
342        write(b);
343    }
344
345    // Write 16 bit signed integer to stream
346    public void writeShort(short b) throws IOException {
347        write((b >>> 0) & 0xFF);
348        write((b >>> 8) & 0xFF);
349    }
350
351    // Write 32 bit signed integer to stream
352    public void writeInt(int b) throws IOException {
353        write((b >>> 0) & 0xFF);
354        write((b >>> 8) & 0xFF);
355        write((b >>> 16) & 0xFF);
356        write((b >>> 24) & 0xFF);
357    }
358
359    // Write 64 bit signed integer to stream
360    public void writeLong(long b) throws IOException {
361        write((int) (b >>> 0) & 0xFF);
362        write((int) (b >>> 8) & 0xFF);
363        write((int) (b >>> 16) & 0xFF);
364        write((int) (b >>> 24) & 0xFF);
365        write((int) (b >>> 32) & 0xFF);
366        write((int) (b >>> 40) & 0xFF);
367        write((int) (b >>> 48) & 0xFF);
368        write((int) (b >>> 56) & 0xFF);
369    }
370
371    // Write 8 bit unsigned integer to stream
372    public void writeUnsignedByte(int b) throws IOException {
373        writeByte((byte) b);
374    }
375
376    // Write 16 bit unsigned integer to stream
377    public void writeUnsignedShort(int b) throws IOException {
378        writeShort((short) b);
379    }
380
381    // Write 32 bit unsigned integer to stream
382    public void writeUnsignedInt(long b) throws IOException {
383        writeInt((int) b);
384    }
385}
386