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;
033    
034    import com.imagero.util.OpenVector;
035    import com.imagero.uio.bio.content.SynchronizedContent;
036    import com.imagero.uio.bio.content.Content;
037    import com.imagero.uio.UIOStreamBuilder;
038    
039    import java.io.DataOutput;
040    import java.io.IOException;
041    import java.io.OutputStream;
042    import java.util.Enumeration;
043    import java.util.NoSuchElementException;
044    
045    /**
046     * Buffer controller.
047     * IOController loads data from Content and maintains buffers (FixedSizeBuffer).
048     * @author Andrey Kuznetsov
049     */
050    public class IOController {
051    
052        OpenVector bufs = new OpenVector(100);
053    
054        int bufferSize = UIOStreamBuilder.DEFAULT_CHUNK_SIZE;
055        int arrayLength = 1000;
056    
057        Content content;
058    
059        RingSpeicher rs;
060        int maxBufferCount = UIOStreamBuilder.DEFAULT_CHUNK_COUNT;
061    
062        long explicitLength;
063    
064        public IOController(int bufferSize, Content content) {
065            this.bufferSize = bufferSize;
066            this.content = content;
067            this.rs = new RingSpeicher(maxBufferCount);
068        }
069    
070        final void setLength(long newLength) throws IOException {
071            explicitLength = newLength;
072        }
073    
074        private Enumeration buffers(final boolean allowNullValues) {
075            return new Enumeration() {
076                final FixedSizeByteBuffer empty = new FixedSizeByteBuffer(new byte[bufferSize]);
077                final long length = length();
078                final BufferIndex max = getBufferIndex(length);
079                final BufferIndex bi = new BufferIndex(0, 0);
080    
081                public boolean hasMoreElements() {
082                    boolean b = bi.arrayIndex < max.arrayIndex;
083                    boolean b2 = bi.index <= max.index;
084                    return b || b2;
085                }
086    
087                public Object nextElement() {
088                    if (hasMoreElements()) {
089                        if (!(bi.index < arrayLength)) {
090                            bi.index = 0;
091                            bi.arrayIndex++;
092                        }
093                        FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
094                        return fb != null || allowNullValues ? fb : empty;
095                    } else {
096                        throw new NoSuchElementException();
097                    }
098                }
099            };
100        }
101    
102        private Enumeration buffers(final long maxPos, final boolean allowNullValues) {
103            return new Enumeration() {
104                final FixedSizeByteBuffer empty = new FixedSizeByteBuffer(new byte[bufferSize]);
105                final long length = Math.min(maxPos, length());
106                final BufferIndex max = getBufferIndex(length);
107                final BufferIndex bi = new BufferIndex(0, 0);
108    
109                public boolean hasMoreElements() {
110                    boolean b = bi.arrayIndex < max.arrayIndex;
111                    boolean b2 = bi.index <= max.index;
112                    return b || b2;
113                }
114    
115                public Object nextElement() {
116                    if (hasMoreElements()) {
117                        if (!(bi.index < arrayLength)) {
118                            bi.index = 0;
119                            bi.arrayIndex++;
120                        }
121                        FixedSizeByteBuffer fb = getBuffer(bi.arrayIndex, bi.index++);
122                        return fb != null || allowNullValues ? fb : empty;
123                    } else {
124                        throw new NoSuchElementException();
125                    }
126                }
127            };
128        }
129    
130    
131        long length() {
132            if (explicitLength > 0) {
133                return explicitLength;
134            }
135    
136            long contentLength = 0;
137            try {
138                contentLength = content.length();
139            } catch (IOException ex) {
140                ex.printStackTrace();
141            }
142    
143            Object[] elements = bufs.getElements();
144            int maxI = elements.length - 1;
145            for (int i = maxI; i >= 0; i--) {
146                BufferArray ba = (BufferArray) elements[i];
147                if (ba != null) {
148                    FixedSizeByteBuffer[] buffers = ba.buffers;
149                    int maxJ = buffers.length - 1;
150                    for (int j = maxJ; j >= 0; j--) {
151                        FixedSizeByteBuffer buffer = buffers[j];
152                        if (buffer != null) {
153                            int count = buffer.getCount();
154                            long startOffset = getStartOffset(buffer.index, bufferSize);
155                            if (count > 0) {
156                                return Math.max(contentLength, startOffset + count);
157                            }
158                        }
159                    }
160                }
161            }
162            return contentLength;
163        }
164    
165        void writeTo(OutputStream out) throws IOException {
166            Enumeration enum = buffers(false);
167            while (enum.hasMoreElements()) {
168                FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enum.nextElement();
169                buffer.writeBuffer(out, enum.hasMoreElements());
170            }
171        }
172    
173        void writeTo(DataOutput out) throws IOException {
174            Enumeration enum = buffers(false);
175            while (enum.hasMoreElements()) {
176                FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enum.nextElement();
177                buffer.writeBuffer(out, enum.hasMoreElements());
178            }
179        }
180    
181        private class BufferArray {
182            FixedSizeByteBuffer[] buffers;
183    
184            public BufferArray() {
185                buffers = new FixedSizeByteBuffer[arrayLength];
186            }
187        }
188    
189        private FixedSizeByteBuffer getBuffer(BufferIndex bi) {
190            return getBuffer(bi.arrayIndex, bi.index);
191        }
192    
193        private FixedSizeByteBuffer getBuffer(int aIndex, int index) {
194            Object[] objects = bufs.checkSize(aIndex);
195            BufferArray ba = (BufferArray) objects[aIndex];
196            if (ba == null) {
197                ba = new BufferArray();
198                objects[aIndex] = ba;
199            }
200            return ba.buffers[index];
201        }
202    
203        protected void setBuffer(BufferIndex index, FixedSizeByteBuffer buffer) {
204            Object[] objects = bufs.checkSize(index.arrayIndex);
205            BufferArray ba = (BufferArray) objects[index.arrayIndex];
206            ba.buffers[index.index] = buffer;
207        }
208    
209        public long flushBefore(long pos) {
210            pos = (pos / bufferSize) * bufferSize;
211            Enumeration enum = buffers(pos, true);
212            while (enum.hasMoreElements()) {
213                FixedSizeByteBuffer o = (FixedSizeByteBuffer) enum.nextElement();
214                if (o != null) {
215                    o.buf = null;
216                }
217            }
218            return pos;
219        }
220    
221        public BufferIndex getBufferIndex(long pos) {
222            long count = pos / bufferSize;
223            long aIndex = count / arrayLength;
224            int index = (int) (count % arrayLength);
225            if (aIndex > Integer.MAX_VALUE) {
226                throw new IndexOutOfBoundsException("Please increase buffer size");
227            }
228            BufferIndex bi = new BufferIndex((int) aIndex, index);
229            return bi;
230        }
231    
232        public FixedSizeByteBuffer getBuffer(long pos) {
233            return getBuffer(getBufferIndex(pos));
234        }
235    
236        FixedSizeByteBuffer getBuffer(long pos, boolean load) throws IOException {
237            BufferIndex bi = getBufferIndex(pos);
238            long startOffset = getStartOffset(bi, bufferSize);
239            FixedSizeByteBuffer sb = getBuffer(bi);
240            if (sb == null) {
241                sb = new FixedSizeByteBuffer(new byte[bufferSize]);
242                setBuffer(bi, sb);
243                sb.index = bi;
244                if (load) {
245                    long max = content.length();
246                    if (pos > max) {
247                        return null;
248                    }
249    
250                    int size = content.load(startOffset, sb.buf);
251                    sb.count = size;
252                }
253                if (content.canReload()) {
254                    checkBuffers(sb);
255                }
256            } else {
257                if (sb.buf == null) {
258                    long max = content.length();
259                    if (pos > max) {
260                        return null;
261                    }
262                    sb.buf = new byte[bufferSize];
263                    int size = content.load(startOffset, sb.buf);
264                    sb.count = size;
265                }
266            }
267            return sb;
268        }
269    
270        private void checkBuffers(FixedSizeByteBuffer buffer0) {
271            FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) rs.add(buffer0);
272            if (buffer != null && content.writable()) {
273                if (buffer.changed) {
274                    try {
275                        long offset = getStartOffset(buffer.index, bufferSize);
276                        content.save(offset, 0, buffer.buf, buffer.getCount());
277                        buffer.changed = false;
278                    } catch (IOException ex) {
279                        ex.printStackTrace();
280                    }
281                }
282                buffer.buf = null;
283            }
284        }
285    
286        void sync() throws IOException {
287            boolean canWrite = content.writable();
288            if (!canWrite) {
289                return;
290            }
291            /*long length = */content.length(); //may be already closed
292    
293            Enumeration enum = buffers(true);
294            while (enum.hasMoreElements()) {
295                FixedSizeByteBuffer buffer = (FixedSizeByteBuffer) enum.nextElement();
296                if (buffer != null && buffer.changed) {
297                    long offset = getStartOffset(buffer.index, bufferSize);
298                    content.save(offset, 0, buffer.buf, buffer.getCount());
299                    buffer.changed = false;
300                }
301            }
302        }
303    
304        public long getStartOffset(BufferIndex bufferIndex, int bufferSize) {
305            long res = bufferIndex.arrayIndex * arrayLength * bufferSize;
306            res += bufferIndex.index * bufferSize;
307            return res;
308        }
309    
310        /**
311         * determine if access to stream content is synchronized
312         */
313        public boolean isSynchronizedContent() {
314            return content instanceof SynchronizedContent;
315        }
316    
317        /**
318         * define if access to content should be syncronized.
319         */
320        public void setSynchronizedContent(boolean b) {
321            if(b) {
322                if(!isSynchronizedContent()) {
323                    content = new SynchronizedContent(content);
324                }
325            }
326            else {
327                if(isSynchronizedContent()) {
328                    SynchronizedContent synchronizedContent = (SynchronizedContent) content;
329                    content = synchronizedContent.getContent();
330                }
331            }
332        }
333    
334        protected void finalize() throws Throwable {
335            super.finalize();
336            content = null;
337            rs = null;
338            bufs = null;
339        }
340    }