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 (this == obj) {
098                        return true;
099                }
100                if (!super.equals(obj)) {
101                        return false;
102                }
103
104                LazyDynamicDataset other = (LazyDynamicDataset) obj;
105                if (!Arrays.equals(maxShape, other.maxShape)) {
106                        return false;
107                }
108
109                if (checker == null) {
110                        if (other.checker != null) {
111                                return false;
112                        }
113                } else if (!checker.equals(other.checker)) {
114                        return false;
115                }
116                if (checkingThread == null) {
117                        if (other.checkingThread != null) {
118                                return false;
119                        }
120                } else if (!checkingThread.equals(other.checkingThread)) {
121                        return false;
122                }
123                return true;
124        }
125
126        @Override
127        public ILazyDataset getDataset() {
128                return this;
129        }
130
131        @Override
132        public void addDataListener(IDataListener l) {
133                eventDelegate.addDataListener(l);
134        }
135
136        @Override
137        public void removeDataListener(IDataListener l) {
138                eventDelegate.removeDataListener(l);
139        }
140
141        @Override
142        public void fireDataListeners() {
143                synchronized (eventDelegate) {
144                        eventDelegate.fire(new DataEvent(name, shape));
145                }
146        }
147
148        @Override
149        public boolean refreshShape() {
150                if (loader instanceof ILazyDynamicLoader) {
151                        return resize(((ILazyDynamicLoader)loader).refreshShape());
152                }
153                return false;
154        }
155
156        @Override
157        public boolean resize(int... newShape) {
158                int rank = shape.length;
159                if (newShape.length != rank) {
160                        throw new IllegalArgumentException("Rank of new shape must match current shape");
161                }
162
163                if (Arrays.equals(shape, newShape)) {
164                        return false;
165                }
166
167                if (maxShape != null) {
168                        for (int i = 0; i < rank; i++) {
169                                int m = maxShape[i];
170                                if (m != -1 && newShape[i] > m) {
171                                        throw new IllegalArgumentException("A dimension of new shape must not exceed maximum shape");
172                                }
173                        }
174                }
175                this.shape = newShape.clone();
176                this.oShape = this.shape;
177                try {
178                        size = ShapeUtils.calcLongSize(shape);
179                } catch (IllegalArgumentException e) {
180                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
181                }
182
183                eventDelegate.fire(new DataEvent(name, shape));
184                return true;
185        }
186
187        @Override
188        public int[] getMaxShape() {
189                return maxShape;
190        }
191
192        @Override
193        public void setMaxShape(int... maxShape) {
194                this.maxShape = maxShape == null ? shape.clone() : maxShape.clone();
195
196                if (this.maxShape.length > oShape.length) {
197                        oShape = prependShapeWithOnes(this.maxShape.length, oShape);
198                }
199                if (this.maxShape.length > shape.length) {
200                        shape = prependShapeWithOnes(this.maxShape.length, shape); // TODO this does not update any metadata
201//                      setShapeInternal(prependShapeWithOnes(this.maxShape.length, shape));
202                }
203        }
204
205        private final static int[] prependShapeWithOnes(int rank, int[] shape) {
206                int[] nShape = new int[rank];
207                int excess = rank - shape.length;
208                for (int i = 0; i < excess; i++) {
209                        nShape[i] = 1;
210                }
211                for (int i = excess; i < nShape.length; i++) {
212                        nShape[i] = shape[i - excess];
213                }
214                return nShape;
215        }
216
217        @Override
218        public LazyDynamicDataset clone() {
219                return new LazyDynamicDataset(this);
220        }
221
222        @Override
223        public synchronized void startUpdateChecker(int milliseconds, IDatasetChangeChecker checker) {
224                // stop any current checking threads
225                if (checkingThread != null) {
226                        checkingThread.interrupt();
227                }
228                this.checker = checker;
229                if (checker != null) {
230                        checker.setDataset(this);
231                }
232                if (milliseconds <= 0) {  
233                        return;
234                }
235
236                runner.millis = milliseconds;
237                checkingThread = new Thread(runner);
238                checkingThread.setDaemon(true);
239                checkingThread.setName("Checking thread with period " + milliseconds + "ms");
240                checkingThread.start();
241        }
242}