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