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    package com.imagero.uio.bio.content;
033    
034    import com.imagero.uio.io.IOutils;
035    import com.imagero.uio.io.UnexpectedEOFException;
036    
037    import java.io.InputStream;
038    import java.io.IOException;
039    import java.io.EOFException;
040    import java.util.Hashtable;
041    
042    /**
043     * Date: 05.01.2008
044     *
045     * @author Andrey Kuznetsov
046     */
047    public class MemoryCachedInputStreamContent extends Content {
048        InputStream in;
049        int chunkSize;
050        boolean finished;
051    
052        int overflow = 5;
053    
054        Hashtable ht = new Hashtable();
055    
056        int lastChunkSize;
057    
058        public MemoryCachedInputStreamContent(InputStream in, int chunkSize) {
059            this.in = in;
060            this.chunkSize = chunkSize;
061        }
062    
063        public int load(long offset, int destOffset, byte[] dest) throws IOException {
064            long index = offset / chunkSize;
065            if (finished && index >= readCount) {
066                throw new EOFException();
067            }
068            if (!finished) {
069                try {
070                    for (long i = readCount; i <= index; i++) {
071                        byte[] b = new byte[chunkSize];
072                        Chunk chunk = addChunk(b, i);   //first add then read
073                        int length = b.length;
074                        lastChunkSize = chunkSize;
075                        try {
076                            IOutils.readFully(in, b);
077                        } catch (UnexpectedEOFException ex) {
078                            length = ex.getCount();
079                        }
080                        if (chunk.src.length != length) {
081                            if (length > 0) {
082                                b = new byte[length];
083                                System.arraycopy(chunk.src, 0, b, 0, length);
084                                chunk.src = b;
085                                lastChunkSize = length;
086                            } else {
087                                ht.remove(new Long(i));
088                            }
089                            finished = true;
090                            break;
091                        }
092                    }
093                } catch (IOException ex) {
094                    finished = true;
095                }
096            }
097            if (index <= readCount) {
098                return copyData(dest, destOffset, offset);
099            }
100            return 0;
101        }
102    
103        protected void prepare() {
104            try {
105                for (long i = readCount; !finished; i++) {
106                    byte[] b = new byte[chunkSize];
107                    Chunk chunk = addChunk(b, i);   //first add then read
108                    int length = b.length;
109                    lastChunkSize = chunkSize;
110                    try {
111                        IOutils.readFully(in, b);
112                    } catch (UnexpectedEOFException ex) {
113                        length = ex.getCount();
114                    }
115                    if (chunk.src.length != length) {
116                        if (length > 0) {
117                            b = new byte[length];
118                            System.arraycopy(chunk.src, 0, b, 0, length);
119                            chunk.src = b;
120                            lastChunkSize = length;
121                        } else {
122                            ht.remove(new Long(i));
123                        }
124                        finished = true;
125                        break;
126                    }
127                }
128            } catch (IOException ex) {
129                finished = true;
130            }
131        }
132    
133        private int copyData(byte[] dest, int destOffset, long streamOffset) {
134            long index = streamOffset / chunkSize;
135            Chunk chunk = (Chunk) ht.get(new Long(index));
136            if (chunk != null) {
137                return chunk.copyInterval(dest, destOffset, streamOffset);
138            }
139            return 0;
140        }
141    
142        public void close() {
143        }
144    
145        long readCount;
146    
147        private Chunk addChunk(byte[] buf, long index) {
148            long start = index * chunkSize;
149            Chunk helper = new Chunk(buf, index, start);
150            readCount++;
151            ht.put(new Long(index), helper);
152            return helper;
153        }
154    
155        class Chunk {
156            byte[] src;
157            long index;
158    
159            long start;
160    
161            private Chunk parent;
162    
163            private Chunk left;
164            private Chunk right;
165    
166            public Chunk(byte[] buf, long index, long start) {
167                this.src = buf;
168                this.index = index;
169                this.start = start;
170            }
171    
172            /**
173             *
174             * @param dest destination array
175             * @param destOffset start offset in destination array
176             * @param absOffset absolute offset in stream
177             * @return how much bytes was copied
178             */
179            int copyInterval(byte[] dest, int destOffset, long absOffset) {
180                if (src != null) {
181                    if ((start > absOffset) || (absOffset > start + src.length)) {
182                        throw new IndexOutOfBoundsException("Given offset is out of chunk bounds");
183                    }
184                    if (destOffset < 0 || destOffset > dest.length) {
185                        throw new IndexOutOfBoundsException("Illegal destination offset: " + destOffset);
186                    }
187                    int srcOffset = (int) (absOffset - start);
188                    int length = Math.min(dest.length - destOffset, src.length - srcOffset);
189                    System.arraycopy(src, srcOffset, dest, destOffset, length);
190                    if (srcOffset == 0 && length == src.length) {
191                        free();
192                    } else {
193                        if (srcOffset == 0) {
194                            //right part leftover
195                            byte[] buf = new byte[src.length - length];
196                            System.arraycopy(src, length, buf, 0, buf.length);
197                            src = buf;
198                        } else if (srcOffset + length == src.length) {
199                            //left part leftover
200                            byte[] buf = new byte[src.length - length];
201                            System.arraycopy(src, 0, buf, 0, buf.length);
202                            src = buf;
203                        } else {
204                            byte[] leftBuf = new byte[srcOffset];
205                            System.arraycopy(src, 0, leftBuf, 0, leftBuf.length);
206                            left = new Chunk(leftBuf, -1, start);
207                            left.parent = this;
208    
209                            byte[] rightBuf = new byte[src.length - (srcOffset + length)];
210                            System.arraycopy(src, srcOffset + length, rightBuf, 0, rightBuf.length);
211                            right = new Chunk(rightBuf, -1, start + srcOffset + length);
212                            right.parent = this;
213    
214                            src = null;
215                        }
216                    }
217                    return length;
218                } else {
219                    Chunk chunk = getChild(absOffset);
220                    if (chunk != null) {
221                        return chunk.copyInterval(dest, destOffset, absOffset);
222                    }
223                }
224                return 0;
225            }
226    
227            private Chunk getChild(long absOffset) {
228                if (right.start <= absOffset) {
229                    if (right.src != null) {
230                        return right;
231                    } else {
232                        return right.getChild(absOffset);
233                    }
234                } else if (left.start <= absOffset) {
235                    if (left.src != null) {
236                        return left;
237                    } else {
238                        return left.getChild(absOffset);
239                    }
240                }
241                return null;
242            }
243    
244            private void free() {
245                if (parent != null) {
246                    parent.removeChild(this);
247                } else {
248                    ht.remove(new Long(index));
249                }
250            }
251    
252            private void removeChild(Chunk c) {
253                if (c == null && c.parent != this) {
254                    return;
255                }
256                if (c == left) {
257                    left = null;
258                } else if (c == right) {
259                    right = null;
260                } else {
261                    return;
262                }
263    
264                if (left == null && right == null) {
265                    free();
266                } else {
267                    if (left != null) {
268                        connectChild(left);
269                    } else if (right != null) {
270                        connectChild(right);
271                    }
272                }
273            }
274    
275            private void connectChild(Chunk c) {
276                src = c.src;
277                left = c.left;
278                right = c.right;
279            }
280        }
281    
282    
283        public boolean canReload() {
284            return false;
285        }
286    
287        public void save(long offset, int bpos, byte[] buffer, int length) throws IOException {
288        }
289    
290        public long length() throws IOException {
291            if (!finished) {
292                prepare();
293            }
294            return (readCount - 1) * chunkSize + lastChunkSize;
295        }
296    
297        public boolean writable() {
298            return false;
299        }
300    }