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
022        protected transient DataListenerDelegate eventDelegate; // this does not need to be serialised!
023
024        protected IDatasetChangeChecker checker;
025
026        class PeriodicRunnable implements Runnable {
027                long millis;
028
029                @Override
030                public void run() {
031                        while (true) {
032                                try {
033                                        Thread.sleep(millis);
034                                } catch (InterruptedException e) {
035                                        break;
036                                }
037                                if (checker == null || checker.check()) {
038                                        fireDataListeners();
039                                }
040                        }
041                }
042        }
043
044        private transient PeriodicRunnable runner = new PeriodicRunnable();
045
046        private Thread checkingThread;
047
048        /**
049         * Create a dynamic lazy dataset
050         * @param name of dataset
051         * @param dtype dataset type
052         * @param elements item size
053         * @param shape dataset shape
054         * @param maxShape maximum shape
055         * @param loader lazy loader
056         * @deprecated Use {@link #LazyDynamicDataset(ILazyLoader, String, int, Class, int[], int[])}
057         */
058        @Deprecated
059        public LazyDynamicDataset(String name, int dtype, int elements, int[] shape, int[] maxShape, ILazyLoader loader) {
060                this(name, elements, DTypeUtils.getInterface(dtype), shape, maxShape, loader);
061        }
062
063        /**
064         * Create a dynamic lazy dataset
065         * @param name of dataset
066         * @param elements item size
067         * @param clazz dataset sub-interface
068         * @param shape dataset shape
069         * @param maxShape maximum shape
070         * @param loader lazy loader
071         * @since 2.3
072         * @deprecated Use {@link #LazyDynamicDataset(ILazyLoader, String, int, Class, int[], int[])}
073         */
074        @Deprecated
075        public LazyDynamicDataset(String name, int elements, Class<? extends Dataset> clazz, int[] shape, int[] maxShape, ILazyLoader loader) {
076                this(loader, name, elements, clazz, shape, maxShape);
077        }
078
079        /**
080         * Create a dynamic lazy dataset
081         * @param loader lazy loader
082         * @param name of dataset
083         * @param elements item size
084         * @param clazz dataset sub-interface
085         * @param shape dataset shape
086         * @param maxShape maximum shape
087         * @since 2.3
088         */
089        public LazyDynamicDataset(ILazyLoader loader, String name, int elements, Class<? extends Dataset> clazz, int[] shape, int[] maxShape) {
090                super(loader, name, elements, clazz, shape);
091                if (maxShape == null) {
092                        this.maxShape = shape.clone();
093                        // check there are no unlimited dimensions in shape
094                        int rank = shape.length;
095                        boolean isUnlimited = false;
096                        for (int i = 0; i < rank; i++) {
097                                if (shape[i] == ILazyWriteableDataset.UNLIMITED) {
098                                        isUnlimited = true;
099                                        break;
100                                }
101                        }
102                        if (isUnlimited) { // set all zeros
103                                for (int i = 0; i < rank; i++) {
104                                        this.shape[i] = 0;
105                                        this.oShape[i] = 0;
106                                }
107                        }
108                } else {
109                        this.maxShape = maxShape.clone();
110                }
111                this.eventDelegate = new DataListenerDelegate();
112        }
113
114        /**
115         * @param other dataset to clone
116         * @since 2.2
117         */
118        protected LazyDynamicDataset(LazyDynamicDataset other) {
119                super(other);
120
121                maxShape = other.maxShape;
122                eventDelegate = other.eventDelegate;
123                checker = other.checker;
124                runner = other.runner;
125        }
126
127        @Override
128        public int hashCode() {
129                final int prime = 31;
130                int result = super.hashCode();
131                result = prime * result + ((checker == null) ? 0 : checker.hashCode());
132                result = prime * result + ((checkingThread == null) ? 0 : checkingThread.hashCode());
133                result = prime * result + Arrays.hashCode(maxShape);
134                return result;
135        }
136
137        @Override
138        public boolean equals(Object obj) {
139                if (this == obj) {
140                        return true;
141                }
142                if (!super.equals(obj)) {
143                        return false;
144                }
145
146                LazyDynamicDataset other = (LazyDynamicDataset) obj;
147                if (!Arrays.equals(maxShape, other.maxShape)) {
148                        return false;
149                }
150
151                if (checker == null) {
152                        if (other.checker != null) {
153                                return false;
154                        }
155                } else if (!checker.equals(other.checker)) {
156                        return false;
157                }
158                if (checkingThread == null) {
159                        if (other.checkingThread != null) {
160                                return false;
161                        }
162                } else if (!checkingThread.equals(other.checkingThread)) {
163                        return false;
164                }
165                return true;
166        }
167
168        @Override
169        public ILazyDataset getDataset() {
170                return this;
171        }
172
173        @Override
174        public void addDataListener(IDataListener l) {
175                eventDelegate.addDataListener(l);
176        }
177
178        @Override
179        public void removeDataListener(IDataListener l) {
180                eventDelegate.removeDataListener(l);
181        }
182
183        @Override
184        public void fireDataListeners() {
185                synchronized (eventDelegate) {
186                        eventDelegate.fire(new DataEvent(name, shape));
187                }
188        }
189
190        @Override
191        public boolean refreshShape() {
192                if (loader instanceof ILazyDynamicLoader) {
193                        return resize(((ILazyDynamicLoader)loader).refreshShape());
194                }
195                return false;
196        }
197
198        @Override
199        public boolean resize(int... newShape) {
200                int rank = shape.length;
201                if (newShape.length != rank) {
202                        throw new IllegalArgumentException("Rank of new shape must match current shape");
203                }
204
205                if (Arrays.equals(shape, newShape)) {
206                        return false;
207                }
208
209                if (maxShape != null) {
210                        for (int i = 0; i < rank; i++) {
211                                int m = maxShape[i];
212                                if (m != -1 && newShape[i] > m) {
213                                        throw new IllegalArgumentException("A dimension of new shape must not exceed maximum shape");
214                                }
215                        }
216                }
217                this.shape = newShape.clone();
218                this.oShape = this.shape;
219                try {
220                        size = ShapeUtils.calcLongSize(shape);
221                } catch (IllegalArgumentException e) {
222                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
223                }
224
225                eventDelegate.fire(new DataEvent(name, shape));
226                return true;
227        }
228
229        @Override
230        public int[] getMaxShape() {
231                return maxShape;
232        }
233
234        @Override
235        public void setMaxShape(int... maxShape) {
236                this.maxShape = maxShape == null ? shape.clone() : maxShape.clone();
237
238                if (this.maxShape.length > oShape.length) {
239                        oShape = prependShapeWithOnes(this.maxShape.length, oShape);
240                }
241                if (this.maxShape.length > shape.length) {
242                        shape = prependShapeWithOnes(this.maxShape.length, shape); // TODO this does not update any metadata
243//                      setShapeInternal(prependShapeWithOnes(this.maxShape.length, shape));
244                }
245        }
246
247        private final static int[] prependShapeWithOnes(int rank, int[] shape) {
248                int[] nShape = new int[rank];
249                int excess = rank - shape.length;
250                for (int i = 0; i < excess; i++) {
251                        nShape[i] = 1;
252                }
253                for (int i = excess; i < nShape.length; i++) {
254                        nShape[i] = shape[i - excess];
255                }
256                return nShape;
257        }
258
259        @Override
260        public LazyDynamicDataset clone() {
261                return new LazyDynamicDataset(this);
262        }
263
264        @Override
265        public synchronized void startUpdateChecker(int milliseconds, IDatasetChangeChecker checker) {
266                // stop any current checking threads
267                if (checkingThread != null) {
268                        checkingThread.interrupt();
269                }
270                this.checker = checker;
271                if (checker != null) {
272                        checker.setDataset(this);
273                }
274                if (milliseconds <= 0) {  
275                        return;
276                }
277
278                runner.millis = milliseconds;
279                checkingThread = new Thread(runner);
280                checkingThread.setDaemon(true);
281                checkingThread.setName("Checking thread with period " + milliseconds + "ms");
282                checkingThread.start();
283        }
284}