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        public LazyDynamicDataset(String name, int dtype, int elements, int[] shape, int[] maxShape, ILazyLoader loader) {
049                super(name, dtype, elements, shape, loader);
050                if (maxShape == null) {
051                        this.maxShape = shape.clone();
052                        // check there are no unlimited dimensions in shape
053                        int rank = shape.length;
054                        boolean isUnlimited = false;
055                        for (int i = 0; i < rank; i++) {
056                                if (shape[i] == ILazyWriteableDataset.UNLIMITED) {
057                                        isUnlimited = true;
058                                        break;
059                                }
060                        }
061                        if (isUnlimited) { // set all zeros
062                                for (int i = 0; i < rank; i++) {
063                                        this.shape[i] = 0;
064                                        this.oShape[i] = 0;
065                                }
066                        }
067                } else {
068                        this.maxShape = maxShape.clone();
069                }
070                this.eventDelegate = new DataListenerDelegate();
071        }
072
073        /**
074         * @since 2.2
075         */
076        protected LazyDynamicDataset(LazyDynamicDataset other) {
077                super(other);
078
079                maxShape = other.maxShape;
080                eventDelegate = other.eventDelegate;
081                checker = other.checker;
082                runner = other.runner;
083        }
084
085        @Override
086        public int hashCode() {
087                final int prime = 31;
088                int result = super.hashCode();
089                result = prime * result + ((checker == null) ? 0 : checker.hashCode());
090                result = prime * result + ((checkingThread == null) ? 0 : checkingThread.hashCode());
091                result = prime * result + Arrays.hashCode(maxShape);
092                return result;
093        }
094
095        @Override
096        public boolean equals(Object obj) {
097                if (!super.equals(obj)) {
098                        return false;
099                }
100
101                LazyDynamicDataset other = (LazyDynamicDataset) obj;
102                if (!Arrays.equals(maxShape, other.maxShape)) {
103                        return false;
104                }
105
106                if (checker == null) {
107                        if (other.checker != null) {
108                                return false;
109                        }
110                } else if (!checker.equals(other.checker)) {
111                        return false;
112                }
113                if (checkingThread == null) {
114                        if (other.checkingThread != null) {
115                                return false;
116                        }
117                } else if (!checkingThread.equals(other.checkingThread)) {
118                        return false;
119                }
120                return true;
121        }
122
123        @Override
124        public ILazyDataset getDataset() {
125                return this;
126        }
127
128        @Override
129        public void addDataListener(IDataListener l) {
130                eventDelegate.addDataListener(l);
131        }
132
133        @Override
134        public void removeDataListener(IDataListener l) {
135                eventDelegate.removeDataListener(l);
136        }
137
138        @Override
139        public void fireDataListeners() {
140                synchronized (eventDelegate) {
141                        eventDelegate.fire(new DataEvent(name, shape));
142                }
143        }
144
145        @Override
146        public boolean refreshShape() {
147                if (loader instanceof ILazyDynamicLoader) {
148                        return resize(((ILazyDynamicLoader)loader).refreshShape());
149                }
150                return false;
151        }
152
153        @Override
154        public boolean resize(int... newShape) {
155                int rank = shape.length;
156                if (newShape.length != rank) {
157                        throw new IllegalArgumentException("Rank of new shape must match current shape");
158                }
159
160                if (Arrays.equals(shape, newShape)) {
161                        return false;
162                }
163
164                if (maxShape != null) {
165                        for (int i = 0; i < rank; i++) {
166                                int m = maxShape[i];
167                                if (m != -1 && newShape[i] > m) {
168                                        throw new IllegalArgumentException("A dimension of new shape must not exceed maximum shape");
169                                }
170                        }
171                }
172                this.shape = newShape.clone();
173                this.oShape = this.shape;
174                try {
175                        size = ShapeUtils.calcLongSize(shape);
176                } catch (IllegalArgumentException e) {
177                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
178                }
179
180                eventDelegate.fire(new DataEvent(name, shape));
181                return true;
182        }
183
184        @Override
185        public int[] getMaxShape() {
186                return maxShape;
187        }
188
189        @Override
190        public void setMaxShape(int... maxShape) {
191                this.maxShape = maxShape == null ? shape.clone() : maxShape.clone();
192
193                if (this.maxShape.length > oShape.length) {
194                        oShape = prependShapeWithOnes(this.maxShape.length, oShape);
195                }
196                if (this.maxShape.length > shape.length) {
197                        shape = prependShapeWithOnes(this.maxShape.length, shape); // TODO this does not update any metadata
198//                      setShapeInternal(prependShapeWithOnes(this.maxShape.length, shape));
199                }
200        }
201
202        private final static int[] prependShapeWithOnes(int rank, int[] shape) {
203                int[] nShape = new int[rank];
204                int excess = rank - shape.length;
205                for (int i = 0; i < excess; i++) {
206                        nShape[i] = 1;
207                }
208                for (int i = excess; i < nShape.length; i++) {
209                        nShape[i] = shape[i - excess];
210                }
211                return nShape;
212        }
213
214        @Override
215        public LazyDynamicDataset clone() {
216                return new LazyDynamicDataset(this);
217        }
218
219        @Override
220        public synchronized void startUpdateChecker(int milliseconds, IDatasetChangeChecker checker) {
221                // stop any current checking threads
222                if (checkingThread != null) {
223                        checkingThread.interrupt();
224                }
225                this.checker = checker;
226                if (checker != null) {
227                        checker.setDataset(this);
228                }
229                if (milliseconds <= 0) {  
230                        return;
231                }
232
233                runner.millis = milliseconds;
234                checkingThread = new Thread(runner);
235                checkingThread.setDaemon(true);
236                checkingThread.setName("Checking thread with period " + milliseconds + "ms");
237                checkingThread.start();
238        }
239}