001    /*
002     * Copyright (c) Andrey Kuznetsov. All Rights Reserved.
003     *
004     * http://uio.imagero.com
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions are met:
008     *
009     *  o Redistributions of source code must retain the above copyright notice,
010     *    this list of conditions and the following disclaimer.
011     *
012     *  o Redistributions in binary form must reproduce the above copyright notice,
013     *    this list of conditions and the following disclaimer in the documentation
014     *    and/or other materials provided with the distribution.
015     *
016     *  o Neither the name of Andrey Kuznetsov nor the names of
017     *    its contributors may be used to endorse or promote products derived
018     *    from this software without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
022     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
023     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
027     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
028     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
029     * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
030     * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031     */
032    
033    package com.imagero.uio;
034    
035    import com.imagero.uio.bio.BIOFactory;
036    import com.imagero.uio.bio.BufferedRandomAccessIO;
037    import com.imagero.uio.bio.ByteArrayRandomAccessIO;
038    import com.imagero.uio.bio.IOController;
039    import com.imagero.uio.bio.content.ByteArrayContent;
040    import com.imagero.uio.bio.content.CharArrayContent;
041    import com.imagero.uio.bio.content.Content;
042    import com.imagero.uio.bio.content.DoubleArrayContent;
043    import com.imagero.uio.bio.content.FloatArrayContent;
044    import com.imagero.uio.bio.content.IntArrayContent;
045    import com.imagero.uio.bio.content.LongArrayContent;
046    import com.imagero.uio.bio.content.RandomAccessFileContent;
047    import com.imagero.uio.bio.content.ShortArrayContent;
048    import com.imagero.uio.impl.RandomAccessFileWrapper;
049    import com.imagero.uio.impl.RandomAccessFileX;
050    
051    import java.io.ByteArrayInputStream;
052    import java.io.File;
053    import java.io.IOException;
054    import java.io.InputStream;
055    import java.io.OutputStream;
056    import java.io.RandomAccessFile;
057    import java.net.URL;
058    
059    /**
060     * <pre>
061     * UIOStreamBuilder is a builder pattern implementation and replacement for RandomAccessFactory.
062     * Usual process looks like
063     * File f = ...;
064     * RandomAccessIO ro = new UIOStreamBuilder(f).setByteOrder(RandomAccessIO.LITTLE_ENDIAN).setBuffered(true).create();
065     * or
066     * RandomAccessIO ra = (RandomAccessIO)new UIOStreamBuilder(f).setMode(UIOStreamBuilder.READ_WRITE).create();
067     *
068     * Defaul values are:
069     * mode - UIOStreamBuilder.READ_ONLY
070     * byte order - RandomAccessIO.BIG_ENDIAN
071     * buffered - false (however some streams are always buffered)
072     *
073     * </pre>
074     * @see #create
075     * @see #setBuffered
076     * @see #setByteOrder
077     * @see #setBufferSize
078     * @see #setCacheFile
079     * @see #setMaxBufferCount
080     * @see #setMode
081     * @see #setStart
082     * @see #setLength
083     *
084     * @author Andrey Kuznetsov
085     */
086    public class UIOStreamBuilder {
087    
088        public static final String READ_ONLY = "r";
089        public static final String READ_WRITE = "rw";
090    
091        String mode = READ_ONLY;
092    
093        int byteOrder = ISeekable.BIG_ENDIAN;
094    
095        Long start;
096        Long length;
097    
098        private boolean buffered = true;
099    
100        private Integer maxBufferCount;
101        private Integer bufferSize;
102    
103        Creator creator;
104    
105        File cache;
106    
107        public static int DEFAULT_CHUNK_SIZE = 256*1024;
108        public static int DEFAULT_CHUNK_COUNT = 8;
109    
110        public boolean isReadOnly() {
111            if (READ_WRITE.equals(mode)) {
112                return false;
113            }
114            return true;
115        }
116    
117        public UIOStreamBuilder(String filename) {
118            this(new File(filename));
119        }
120    
121        public UIOStreamBuilder(File file) {
122            this.creator = new FileCreator(file);
123        }
124    
125        public UIOStreamBuilder(RandomAccessFile rafSource) {
126            this.creator = new RAFCreator(rafSource);
127        }
128    
129        public UIOStreamBuilder(RandomAccessIO ra) {
130            this.creator = new RAIOCreator(ra);
131        }
132    
133        public UIOStreamBuilder(RandomAccessInput ro) {
134            this.creator = new RAICreator(ro);
135        }
136    
137        public UIOStreamBuilder(byte[] byteSource) {
138            this.creator = new ByteCreator(byteSource);
139        }
140    
141        public UIOStreamBuilder(byte[][] byteSource) {
142            this.creator = new Byte2DCreator(byteSource);
143        }
144    
145        public UIOStreamBuilder(short[] shortSource) {
146            creator = new ShortCreator(shortSource);
147        }
148    
149        public UIOStreamBuilder(short[][] shortSource) {
150            creator = new ShortCreator(shortSource);
151        }
152    
153        public UIOStreamBuilder(char[] charSource) {
154            creator = new CharCreator(charSource);
155        }
156    
157        public UIOStreamBuilder(char[][] charSource) {
158            creator = new CharCreator(charSource);
159        }
160    
161        public UIOStreamBuilder(int[] intSource) {
162            creator = new IntCreator(intSource);
163        }
164    
165        public UIOStreamBuilder(int[][] intSource) {
166            creator = new IntCreator(intSource);
167        }
168    
169        public UIOStreamBuilder(long[] longSource) {
170            creator = new LongCreator(longSource);
171        }
172    
173        public UIOStreamBuilder(long[][] longSource) {
174            creator = new LongCreator(longSource);
175        }
176    
177        public UIOStreamBuilder(float[] floatSource) {
178            creator = new FloatCreator(floatSource);
179        }
180    
181        public UIOStreamBuilder(float[][] floatSource) {
182            creator = new FloatCreator(floatSource);
183        }
184    
185        public UIOStreamBuilder(double[] doubleSource) {
186            creator = new DoubleCreator(doubleSource);
187        }
188    
189        public UIOStreamBuilder(double[][] doubleSource) {
190            creator = new DoubleCreator(doubleSource);
191        }
192    
193        static private File getTmpDir() {
194            String name = System.getProperty("uio.temp.dir");
195            if(name != null && name.length() > 0) {
196                File f = new File(name);
197                if(!f.exists()) {
198                    f.mkdirs();
199                }
200                if(f.isDirectory()) {
201                    return f;
202                }
203            }
204            return null;
205        }
206    
207        static File createTempFile(String prefix) {
208            File dir = getTmpDir();
209            if(dir != null) {
210                return new File(dir, prefix + Integer.toHexString(dir.hashCode()));
211            }
212            return null;
213        }
214    
215        /**
216         * always buffered
217         * @param url
218         */
219        public UIOStreamBuilder(URL url) {
220            creator = new URLCreator(url);
221        }
222    
223        /**
224         * always buffered
225         * @param in
226         */
227        public UIOStreamBuilder(InputStream in) {
228            creator = new ISCreator(in);
229        }
230    
231        /**
232         * Always buffered.
233         * Two things are very important:
234         * 1. closing RandomAccessOutput created by this method does not close OutputStream
235         * 2. To write data to OutputStream RandomAccessOutput must be closed or flushed.
236         *
237         * @param out OutputStream
238         */
239        public UIOStreamBuilder(OutputStream out) {
240            creator = new OSCreator(out);
241        }
242    
243        /**
244         * set mode (writeable or read only)
245         * @param mode READ_ONLY or READ_WRITE
246         * @return UIOStreamBuilder
247         */
248        public UIOStreamBuilder setMode(String mode) {
249            if (READ_ONLY.equals(mode) || READ_WRITE.equals(mode)) {
250                this.mode = mode;
251                return this;
252            } else {
253                throw new IllegalArgumentException(mode);
254            }
255        }
256    
257        /**
258         * set byte order (big endian or little endian)
259         * @param byteOrder LITTLE_ENDIAN or BIG_ENDIAN (default value - BIG_ENDIAN)
260         * @return UIOStreamBuilder
261         */
262        public UIOStreamBuilder setByteOrder(int byteOrder) {
263            switch (byteOrder) {
264                case RandomAccessIO.LITTLE_ENDIAN:
265                case RandomAccessIO.BIG_ENDIAN:
266                    this.byteOrder = byteOrder;
267                    return this;
268                default:
269                    throw new IllegalArgumentException("" + byteOrder);
270            }
271        }
272    
273        /**
274         * set start offset
275         * @param start start offset of stream (default value - 0L)
276         * @return UIOStreamBuilder
277         */
278        public UIOStreamBuilder setStart(long start) {
279            if (start < 0) {
280                throw new IllegalArgumentException(" " + start);
281            }
282            this.start = new Long(start);
283            return this;
284        }
285    
286        /**
287         * set stream length
288         * @param length stream length
289         * @return UIOStreamBuilder
290         */
291        public UIOStreamBuilder setLength(long length) {
292            if (length < 0) {
293                throw new IllegalArgumentException(" " + length);
294            }
295            this.length = new Long(length);
296            if (start == null) {
297                start = new Long(0L);
298            }
299            return this;
300        }
301    
302        /**
303         * set if stream should be buffered or not (rather a hint because some streams are always buffered)
304         * @param buffered true or false (default value - false)
305         * @return UIOStreamBuilder
306         */
307        public UIOStreamBuilder setBuffered(boolean buffered) {
308            this.buffered = buffered;
309            return this;
310        }
311    
312        /**
313         * set maxBufferCount for MemoryAccessManager - for unbuffered streams this parameter is ignored.
314         * @param max
315         * @return UIOStreamBuilder
316         */
317        public UIOStreamBuilder setMaxBufferCount(int max) {
318            this.maxBufferCount = new Integer(max);
319            return this;
320        }
321    
322        /**
323         * set size for memory chunks used by MemoryAccessManager - for unbuffered streams this parameter is ignored.
324         * @param bufferSize
325         * @return UIOStreamBuilder
326         */
327        public UIOStreamBuilder setBufferSize(int bufferSize) {
328            this.bufferSize = new Integer(bufferSize);
329            return this;
330        }
331    
332        /**
333         * Set file which can be used to cache data (only for Streams)
334         * @param f File
335         * @return UIOStreamBuilder
336         */
337        public UIOStreamBuilder setCacheFile(File f) {
338            this.cache = f;
339            return this;
340        }
341    
342        /**
343         * finally create desired stream
344         * @return RandomAccessIinput
345         * @throws java.io.IOException
346         */
347        public RandomAccessInput create() throws IOException {
348            if (length != null && length.longValue() == 0) {
349                Sys.err.println("Warning: stream length is 0");
350            }
351            if (buffered) {
352                return creator.createBuffered();
353            } else {
354                return creator.create();
355            }
356        }
357    
358        abstract class Creator {
359            abstract RandomAccessInput create() throws IOException;
360    
361            abstract RandomAccessInput createBuffered() throws IOException;
362    
363            protected int getByteOrder() {
364                return byteOrder != 0 ? byteOrder : RandomAccessIO.BIG_ENDIAN;
365            }
366    
367            protected int getBufferCount() {
368                if (maxBufferCount != null) {
369                    return maxBufferCount.intValue();
370                }
371                return DEFAULT_CHUNK_COUNT;
372            }
373    
374            protected int getBufferSize() {
375                if (bufferSize != null) {
376                    return bufferSize.intValue();
377                }
378                return DEFAULT_CHUNK_SIZE;
379            }
380        }
381    
382        class FileCreator extends Creator {
383    
384            File fileSource;
385    
386            public FileCreator(File fileSource) {
387                this.fileSource = fileSource;
388            }
389    
390            public RandomAccessInput create() throws IOException {
391                final RandomAccessFileX rafx = new RandomAccessFileX(fileSource, mode);
392                if (start == null && length == null) {
393                    return new RandomAccessFileWrapper(rafx, getByteOrder());
394                } else if (length == null) {
395                    return new RandomAccessFileWrapper(rafx, start.longValue(), getByteOrder());
396                } else {
397                    return new RandomAccessFileWrapper(rafx, start.longValue(), length.longValue(), getByteOrder());
398                }
399            }
400    
401            RandomAccessInput createBuffered() throws IOException {
402                Content bc = new RandomAccessFileContent(fileSource, mode);
403                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
404                BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
405                bio.setByteOrder(byteOrder);
406                return bio;
407            }
408        }
409    
410        class RAFCreator extends Creator {
411            RandomAccessFile rafSource;
412    
413            public RAFCreator(RandomAccessFile rafSource) {
414                this.rafSource = rafSource;
415            }
416    
417            public RandomAccessInput create() throws IOException {
418                if (start == null && length == null) {
419                    return new RandomAccessFileWrapper(rafSource, getByteOrder());
420                } else if (length == null) {
421                    return new RandomAccessFileWrapper(rafSource, start.longValue(), getByteOrder());
422                } else {
423                    return new RandomAccessFileWrapper(rafSource, start.longValue(), length.longValue(), getByteOrder());
424                }
425            }
426    
427            RandomAccessInput createBuffered() throws IOException {
428                Content bc = new RandomAccessFileContent(rafSource);
429                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, bc);
430                return new BufferedRandomAccessIO(controller);
431            }
432        }
433    
434        class RAICreator extends Creator {
435            RandomAccessInput roSource;
436    
437            public RAICreator(RandomAccessInput roSource) {
438                this.roSource = roSource;
439            }
440    
441            protected int getByteOrder() {
442                return byteOrder != 0 ? byteOrder : roSource.getByteOrder();
443            }
444    
445            public RandomAccessInput create() throws IOException {
446                return roSource.createInputChild(start != null ? start.longValue() : 0L, byteOrder, true);
447            }
448    
449            RandomAccessInput createBuffered() throws IOException {
450                return create();
451            }
452        }
453    
454        class RAIOCreator extends Creator {
455            RandomAccessIO raSource;
456    
457            public RAIOCreator(RandomAccessIO raSource) {
458                this.raSource = raSource;
459            }
460    
461            protected int getByteOrder() {
462                return byteOrder != 0 ? byteOrder : raSource.getByteOrder();
463            }
464    
465            public RandomAccessInput create() throws IOException {
466                return raSource.createIOChild(start != null ? start.longValue() : 0L, byteOrder, true);
467            }
468    
469            RandomAccessInput createBuffered() throws IOException {
470                return create();
471            }
472        }
473    
474        class Byte2DCreator extends Creator {
475    
476            byte[][] byteSource;
477    
478            public Byte2DCreator(byte[][] byteSource) {
479                this.byteSource = byteSource;
480            }
481    
482            RandomAccessInput create() throws IOException {
483                return createBuffered();
484            }
485    
486            RandomAccessInput createBuffered() throws IOException {
487                Content content = new ByteArrayContent(byteSource);
488                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
489                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
490            }
491        }
492    
493        class ByteCreator extends Creator {
494    
495            byte[] byteSource;
496    
497            public ByteCreator(byte[] byteSource) {
498                this.byteSource = byteSource;
499            }
500    
501            RandomAccessInput create() throws IOException {
502                return new ByteArrayRandomAccessIO(byteSource);
503            }
504    
505            RandomAccessInput createBuffered() throws IOException {
506                final byte[][] bytes = new byte[][]{byteSource};
507                Content content = new ByteArrayContent(bytes);
508                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
509                return new BufferedRandomAccessIO(controller);
510            }
511        }
512    
513        class ShortCreator extends Creator {
514            short[][] shortSource;
515    
516            public ShortCreator(short[] shortSource) {
517                this(new short[][]{shortSource});
518            }
519    
520            public ShortCreator(short[][] shortSource) {
521                this.shortSource = shortSource;
522            }
523    
524            RandomAccessInput create() throws IOException {
525                Content content = new ShortArrayContent(shortSource);
526                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
527                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
528            }
529    
530            RandomAccessInput createBuffered() throws IOException {
531                return create();
532            }
533        }
534    
535        class CharCreator extends Creator {
536            char[][] charSource;
537    
538            public CharCreator(char[] charSource) {
539                this(new char[][]{charSource});
540            }
541    
542            public CharCreator(char[][] charSource) {
543                this.charSource = charSource;
544            }
545    
546            RandomAccessInput create() throws IOException {
547                Content content = new CharArrayContent(charSource);
548                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
549                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
550            }
551    
552            RandomAccessInput createBuffered() throws IOException {
553                return create();
554            }
555        }
556    
557        class IntCreator extends Creator {
558            int[][] intSource;
559    
560            public IntCreator(int[] intSource) {
561                this(new int[][]{intSource});
562            }
563    
564            public IntCreator(int[][] intSource) {
565                this.intSource = intSource;
566            }
567    
568            RandomAccessInput create() throws IOException {
569                Content content = new IntArrayContent(intSource);
570                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
571                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
572            }
573    
574            RandomAccessInput createBuffered() throws IOException {
575                return create();
576            }
577        }
578    
579        class LongCreator extends Creator {
580            long[][] longSource;
581    
582            public LongCreator(long[] longSource) {
583                this(new long[][]{longSource});
584            }
585    
586            public LongCreator(long[][] longSource) {
587                this.longSource = longSource;
588            }
589    
590            RandomAccessInput create() throws IOException {
591                Content content = new LongArrayContent(longSource);
592                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
593                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
594            }
595    
596            RandomAccessInput createBuffered() throws IOException {
597                return create();
598            }
599        }
600    
601        class FloatCreator extends Creator {
602            float[][] floatSource;
603    
604            public FloatCreator(float[] floatSource) {
605                this(new float[][]{floatSource});
606            }
607    
608            public FloatCreator(float[][] floatSource) {
609                this.floatSource = floatSource;
610            }
611    
612            RandomAccessInput create() throws IOException {
613                Content content = new FloatArrayContent(floatSource);
614                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
615                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
616            }
617    
618            RandomAccessInput createBuffered() throws IOException {
619                return create();
620            }
621        }
622    
623        class DoubleCreator extends Creator {
624            double[][] doubleSource;
625    
626            public DoubleCreator(double[] doubleSource) {
627                this(new double[][]{doubleSource});
628            }
629    
630            public DoubleCreator(double[][] doubleSource) {
631                this.doubleSource = doubleSource;
632            }
633    
634            RandomAccessInput create() throws IOException {
635                Content content = new DoubleArrayContent(doubleSource);
636                IOController controller = new IOController(DEFAULT_CHUNK_SIZE, content);
637                return new BufferedRandomAccessIO(controller, start != null ? start.longValue() : 0L);
638            }
639    
640            RandomAccessInput createBuffered() throws IOException {
641                return create();
642            }
643        }
644    
645        class URLCreator extends Creator {
646            URL url;
647            FileCreator fileCreator;
648    
649            public URLCreator(URL url) {
650                this.url = url;
651                final String protocol = url.getProtocol();
652                if ("file".equalsIgnoreCase(protocol)) {
653                    File f = new File(url.getFile());
654                    fileCreator = new FileCreator(f);
655                }
656            }
657    
658            RandomAccessInput create() throws IOException {
659                if (fileCreator == null) {
660                    return create0();
661                } else {
662                    return fileCreator.create();
663                }
664            }
665    
666            RandomAccessInput createBuffered() throws IOException {
667                if (fileCreator == null) {
668                    return create0();
669                } else {
670                    return fileCreator.createBuffered();
671                }
672            }
673    
674            private RandomAccessInput create0() {
675                if(cache == null) {
676                    cache = createTempFile("urc");
677                }
678                IOController controller = BIOFactory.createIOController(url, cache, getBufferSize());
679                BufferedRandomAccessIO rio = new BufferedRandomAccessIO(controller);
680                rio.setByteOrder(byteOrder);
681                return rio;
682            }
683        }
684    
685        class ISCreator extends Creator {
686            InputStream inputStreamSource;
687    
688            public ISCreator(InputStream inputStreamSource) {
689                this.inputStreamSource = inputStreamSource;
690            }
691    
692            RandomAccessInput create() throws IOException {
693                if (inputStreamSource instanceof ByteArrayInputStream) {
694                    return new BaisWrapper((ByteArrayInputStream) inputStreamSource);
695                } else {
696                    if (cache == null) {
697                        cache = createTempFile("isc");
698                    }
699                    IOController controller = BIOFactory.createIOController(inputStreamSource, cache, getBufferSize());
700                    BufferedRandomAccessIO bio = new BufferedRandomAccessIO(controller);
701                    bio.setByteOrder(byteOrder);
702                    return bio;
703                }
704            }
705    
706            RandomAccessInput createBuffered() throws IOException {
707                return create();
708            }
709        }
710    
711        class OSCreator extends Creator {
712            OutputStream outputStreamSource;
713    
714            public OSCreator(OutputStream outputStreamSource) {
715                this.outputStreamSource = outputStreamSource;
716                setMode(READ_WRITE);
717            }
718    
719            RandomAccessInput create() throws IOException {
720                final BufferedRandomAccessIO bio = BIOFactory.create(outputStreamSource);
721                bio.setByteOrder(byteOrder);
722                return bio;
723            }
724    
725            RandomAccessInput createBuffered() throws IOException {
726                return create();
727            }
728        }
729    }