001/*-
002 * Copyright 2015, 2016 Diamond Light Source Ltd.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.january.dataset;
011
012import java.util.Arrays;
013
014import org.eclipse.january.io.ILazyDynamicLoader;
015import org.eclipse.january.io.ILazyLoader;
016
017public class LazyDynamicDataset extends LazyDataset implements IDynamicDataset {
018        private static final long serialVersionUID = -6296506563932840938L;
019
020        protected int[] maxShape;
021        protected int[] chunks;
022
023        protected transient DataListenerDelegate eventDelegate; // this does not need to be serialised!
024
025        protected IDatasetChangeChecker checker;
026
027        class PeriodicRunnable implements Runnable {
028                long millis;
029
030                @Override
031                public void run() {
032                        while (true) {
033                                try {
034                                        Thread.sleep(millis);
035                                } catch (InterruptedException e) {
036                                        break;
037                                }
038                                if (checker == null || checker.check()) {
039                                        fireDataListeners();
040                                }
041                        }
042                }
043        }
044
045        private transient PeriodicRunnable runner = new PeriodicRunnable();
046
047        private Thread checkingThread;
048
049        /**
050         * Create a dynamic lazy dataset
051         * @param name of dataset
052         * @param dtype dataset type
053         * @param elements item size
054         * @param shape dataset shape
055         * @param maxShape maximum shape
056         * @param loader lazy loader
057         * @deprecated Use {@link #LazyDynamicDataset(ILazyLoader, String, int, Class, int[], int[])}
058         */
059        @Deprecated
060        public LazyDynamicDataset(String name, int dtype, int elements, int[] shape, int[] maxShape, ILazyLoader loader) {
061                this(name, elements, DTypeUtils.getInterface(dtype), shape, maxShape, loader);
062        }
063
064        /**
065         * Create a dynamic lazy dataset
066         * @param name of dataset
067         * @param elements item size
068         * @param clazz dataset sub-interface
069         * @param shape dataset shape
070         * @param maxShape maximum shape
071         * @param loader lazy loader
072         * @since 2.3
073         * @deprecated Use {@link #LazyDynamicDataset(ILazyLoader, String, int, Class, int[], int[])}
074         */
075        @Deprecated
076        public LazyDynamicDataset(String name, int elements, Class<? extends Dataset> clazz, int[] shape, int[] maxShape, ILazyLoader loader) {
077                this(loader, name, elements, clazz, shape, maxShape);
078        }
079
080        /**
081         * Create a dynamic lazy dataset
082         * @param loader lazy loader
083         * @param name of dataset
084         * @param elements item size
085         * @param clazz dataset sub-interface
086         * @param shape dataset shape
087         * @param maxShape maximum shape
088         * @since 2.3
089         */
090        public LazyDynamicDataset(ILazyLoader loader, String name, int elements, Class<? extends Dataset> clazz, int[] shape, int[] maxShape) {
091                this(loader, name, elements, clazz, shape, maxShape, null);
092        }
093
094        /**
095         * Create a dynamic lazy dataset
096         * @param loader lazy loader
097         * @param name of dataset
098         * @param elements item size
099         * @param clazz dataset sub-interface
100         * @param shape dataset shape
101         * @param maxShape maximum shape
102         * @param chunks chunk shape
103         * @since 2.3
104         */
105        public LazyDynamicDataset(ILazyLoader loader, String name, int elements, Class<? extends Dataset> clazz, int[] shape, int[] maxShape, int[] chunks) {
106                super(loader, name, elements, clazz, shape);
107                if (maxShape == null) {
108                        this.maxShape = shape.clone();
109                        // check there are no unlimited dimensions in shape
110                        int rank = shape.length;
111                        boolean isUnlimited = false;
112                        for (int i = 0; i < rank; i++) {
113                                if (shape[i] == ILazyWriteableDataset.UNLIMITED) {
114                                        isUnlimited = true;
115                                        break;
116                                }
117                        }
118                        if (isUnlimited) { // set all zeros
119                                for (int i = 0; i < rank; i++) {
120                                        this.shape[i] = 0;
121                                        this.oShape[i] = 0;
122                                }
123                        }
124                } else {
125                        this.maxShape = maxShape.clone();
126                }
127                this.chunks = chunks == null ? null : chunks.clone();
128
129                this.eventDelegate = new DataListenerDelegate();
130        }
131
132        /**
133         * @param other dataset to clone
134         * @since 2.2
135         */
136        protected LazyDynamicDataset(LazyDynamicDataset other) {
137                super(other);
138
139                maxShape = other.maxShape;
140                chunks = other.chunks;
141                eventDelegate = other.eventDelegate;
142                checker = other.checker;
143                runner = other.runner;
144        }
145
146        @Override
147        public int hashCode() {
148                final int prime = 31;
149                int result = super.hashCode();
150                result = prime * result + ((checker == null) ? 0 : checker.hashCode());
151                result = prime * result + ((checkingThread == null) ? 0 : checkingThread.hashCode());
152                result = prime * result + Arrays.hashCode(maxShape);
153                result = prime * result + Arrays.hashCode(chunks);
154                return result;
155        }
156
157        @Override
158        public boolean equals(Object obj) {
159                if (this == obj) {
160                        return true;
161                }
162                if (!super.equals(obj)) {
163                        return false;
164                }
165
166                LazyDynamicDataset other = (LazyDynamicDataset) obj;
167                if (!Arrays.equals(maxShape, other.maxShape)) {
168                        return false;
169                }
170                if (!Arrays.equals(chunks, other.chunks)) {
171                        return false;
172                }
173
174                if (checker == null) {
175                        if (other.checker != null) {
176                                return false;
177                        }
178                } else if (!checker.equals(other.checker)) {
179                        return false;
180                }
181                if (checkingThread == null) {
182                        if (other.checkingThread != null) {
183                                return false;
184                        }
185                } else if (!checkingThread.equals(other.checkingThread)) {
186                        return false;
187                }
188                return true;
189        }
190
191        @Override
192        public ILazyDataset getDataset() {
193                return this;
194        }
195
196        @Override
197        public void addDataListener(IDataListener l) {
198                eventDelegate.addDataListener(l);
199        }
200
201        @Override
202        public void removeDataListener(IDataListener l) {
203                eventDelegate.removeDataListener(l);
204        }
205
206        @Override
207        public void fireDataListeners() {
208                synchronized (eventDelegate) {
209                        eventDelegate.fire(new DataEvent(name, shape));
210                }
211        }
212
213        @Override
214        public boolean refreshShape() {
215                if (loader instanceof ILazyDynamicLoader) {
216                        return resize(((ILazyDynamicLoader)loader).refreshShape());
217                }
218                return false;
219        }
220
221        @Override
222        public boolean resize(int... newShape) {
223                int rank = shape.length;
224                if (newShape.length != rank) {
225                        throw new IllegalArgumentException("Rank of new shape must match current shape");
226                }
227
228                if (Arrays.equals(shape, newShape)) {
229                        return false;
230                }
231
232                if (maxShape != null) {
233                        for (int i = 0; i < rank; i++) {
234                                int m = maxShape[i];
235                                if (m != -1 && newShape[i] > m) {
236                                        throw new IllegalArgumentException("A dimension of new shape must not exceed maximum shape");
237                                }
238                        }
239                }
240                this.shape = newShape.clone();
241                this.oShape = this.shape;
242                try {
243                        size = ShapeUtils.calcLongSize(shape);
244                } catch (IllegalArgumentException e) {
245                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
246                }
247
248                eventDelegate.fire(new DataEvent(name, shape));
249                return true;
250        }
251
252        @Override
253        public int[] getMaxShape() {
254                return maxShape;
255        }
256
257        @Override
258        public void setMaxShape(int... maxShape) {
259                this.maxShape = maxShape == null ? shape.clone() : maxShape.clone();
260
261                if (this.maxShape.length > oShape.length) {
262                        oShape = prependShapeWithOnes(this.maxShape.length, oShape);
263                }
264                if (this.maxShape.length > shape.length) {
265                        shape = prependShapeWithOnes(this.maxShape.length, shape); // TODO this does not update any metadata
266//                      setShapeInternal(prependShapeWithOnes(this.maxShape.length, shape));
267                }
268        }
269
270        @Override
271        public int[] getChunking() {
272                return chunks;
273        }
274
275        @Override
276        public void setChunking(int... chunks) {
277                this.chunks = chunks == null ? null : chunks.clone();
278        }
279
280        private final static int[] prependShapeWithOnes(int rank, int[] shape) {
281                int[] nShape = new int[rank];
282                int excess = rank - shape.length;
283                for (int i = 0; i < excess; i++) {
284                        nShape[i] = 1;
285                }
286                for (int i = excess; i < nShape.length; i++) {
287                        nShape[i] = shape[i - excess];
288                }
289                return nShape;
290        }
291
292        @Override
293        protected void checkSliceND(SliceND slice) {
294                slice.checkShapes(shape, maxShape);
295        }
296
297        @Override
298        protected SliceND createSlice(int[] nstart, int[] nstop, int[] nstep) {
299                return SliceND.createSlice(oShape, maxShape, nstart, nstop, nstep);
300        }
301
302        @Override
303        public LazyDynamicDataset clone() {
304                return new LazyDynamicDataset(this);
305        }
306
307        @Override
308        public synchronized void startUpdateChecker(int milliseconds, IDatasetChangeChecker checker) {
309                // stop any current checking threads
310                if (checkingThread != null) {
311                        checkingThread.interrupt();
312                }
313                this.checker = checker;
314                if (checker != null) {
315                        checker.setDataset(this);
316                }
317                if (milliseconds <= 0) {  
318                        return;
319                }
320
321                runner.millis = milliseconds;
322                checkingThread = new Thread(runner);
323                checkingThread.setDaemon(true);
324                checkingThread.setName("Checking thread with period " + milliseconds + "ms");
325                checkingThread.start();
326        }
327}