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        private boolean stop;
026
027        class PeriodicRunnable implements Runnable {
028                long millis;
029
030                @Override
031                public void run() {
032                        while (!stop) {
033                                try {
034                                        Thread.sleep(millis);
035                                } catch (InterruptedException e) {
036                                        logger.error("Something has interrupted this periodic runner!");
037                                        stop = true; // ends runner
038                                }
039                                if (checker == null || checker.check()) {
040                                        fireDataListeners();
041                                }
042                        }
043                }
044        }
045
046        private transient PeriodicRunnable runner = new PeriodicRunnable();
047
048        private Thread checkingThread;
049
050        public LazyDynamicDataset(String name, int dtype, int elements, int[] shape, int[] maxShape, ILazyLoader loader) {
051                super(name, dtype, elements, shape, loader);
052                if (maxShape == null) {
053                        this.maxShape = shape.clone();
054                        // check there are no unlimited dimensions in shape
055                        int rank = shape.length;
056                        boolean isUnlimited = false;
057                        for (int i = 0; i < rank; i++) {
058                                if (shape[i] == ILazyWriteableDataset.UNLIMITED) {
059                                        isUnlimited = true;
060                                        break;
061                                }
062                        }
063                        if (isUnlimited) { // set all zeros
064                                for (int i = 0; i < rank; i++) {
065                                        this.shape[i] = 0;
066                                        this.oShape[i] = 0;
067                                }
068                        }
069                } else {
070                        this.maxShape = maxShape.clone();
071                }
072                this.eventDelegate = new DataListenerDelegate();
073        }
074
075        @Override
076        public ILazyDataset getDataset() {
077                return this;
078        }
079
080        @Override
081        public void addDataListener(IDataListener l) {
082                eventDelegate.addDataListener(l);
083        }
084
085        @Override
086        public void removeDataListener(IDataListener l) {
087                eventDelegate.removeDataListener(l);
088        }
089
090        @Override
091        public void fireDataListeners() {
092                synchronized (eventDelegate) {
093                        eventDelegate.fire(new DataEvent(name, shape));
094                }
095        }
096
097        @Override
098        public boolean refreshShape() {
099                if (loader instanceof ILazyDynamicLoader) {
100                        return resize(((ILazyDynamicLoader)loader).refreshShape());
101                }
102                return false;
103        }
104        
105        @Override
106        public boolean resize(int... newShape) {
107                if (base != null) {
108                        throw new UnsupportedOperationException("Changing the shape of a view is not allowed");
109                }
110                int rank = shape.length;
111                if (newShape.length != rank) {
112                        throw new IllegalArgumentException("Rank of new shape must match current shape");
113                }
114
115                if (Arrays.equals(shape, newShape)) {
116                        return false;
117                }
118
119                if (maxShape != null) {
120                        for (int i = 0; i < rank; i++) {
121                                int m = maxShape[i];
122                                if (m != -1 && newShape[i] > m) {
123                                        throw new IllegalArgumentException("A dimension of new shape must not exceed maximum shape");
124                                }
125                        }
126                }
127                this.shape = newShape.clone();
128                this.oShape = this.shape;
129                try {
130                        size = ShapeUtils.calcLongSize(shape);
131                } catch (IllegalArgumentException e) {
132                        size = Long.MAX_VALUE; // this indicates that the entire dataset cannot be read in! 
133                }
134
135                eventDelegate.fire(new DataEvent(name, shape));
136                return true;
137        }
138
139        @Override
140        public int[] getMaxShape() {
141                return maxShape;
142        }
143
144        @Override
145        public void setMaxShape(int... maxShape) {
146                this.maxShape = maxShape == null ? shape.clone() : maxShape.clone();
147
148                if (this.maxShape.length > oShape.length) {
149                        oShape = prependShapeWithOnes(this.maxShape.length, oShape);
150                }
151                if (this.maxShape.length > shape.length) {
152                        shape = prependShapeWithOnes(this.maxShape.length, shape); // TODO this does not update any metadata
153//                      setShapeInternal(prependShapeWithOnes(this.maxShape.length, shape));
154                }
155        }
156
157        private final static int[] prependShapeWithOnes(int rank, int[] shape) {
158                int[] nShape = new int[rank];
159                int excess = rank - shape.length;
160                for (int i = 0; i < excess; i++) {
161                        nShape[i] = 1;
162                }
163                for (int i = excess; i < nShape.length; i++) {
164                        nShape[i] = shape[i - excess];
165                }
166                return nShape;
167        }
168
169        @Override
170        public LazyDynamicDataset clone() {
171                LazyDynamicDataset ret = new LazyDynamicDataset(new String(name), getDType(), getElementsPerItem(), 
172                                oShape, maxShape, loader);
173                ret.shape = shape;
174                ret.size = size;
175                ret.prepShape = prepShape;
176                ret.postShape = postShape;
177                ret.begSlice = begSlice;
178                ret.delSlice = delSlice;
179                ret.map = map;
180                ret.base = base;
181                ret.metadata = copyMetadata();
182                ret.oMetadata = oMetadata;
183                ret.eventDelegate = eventDelegate;
184                return ret;
185        }
186
187        @Override
188        public synchronized void startUpdateChecker(int milliseconds, IDatasetChangeChecker checker) {
189                // stop any current checking threads
190                if (checkingThread != null) {
191                        stop = true;
192                        if (checkingThread != null) {
193                                checkingThread.interrupt();
194                        }
195                }
196                this.checker = checker;
197                if (checker != null) {
198                        checker.setDataset(this);
199                }
200                if (milliseconds <= 0) {  
201                        return;
202                }
203
204                runner.millis = milliseconds;
205                checkingThread = new Thread(runner);
206                checkingThread.setDaemon(true);
207                checkingThread.setName("Checking thread with period " + milliseconds + "ms");
208                checkingThread.start();
209        }
210}