    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}