001/*-
002 *******************************************************************************
003 * Copyright (c) 2011, 2016 Diamond Light Source Ltd.
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 * Contributors:
010 *    Peter Chang - initial API and implementation and/or initial documentation
011 *******************************************************************************/
012
013package org.eclipse.january.dataset;
014
015import java.io.Serializable;
016
017/**
018 * Class to represent a slice through a single dimension of a multi-dimensional dataset. A slice
019 * comprises a starting position, a stopping position (not included) and a stepping size.
020 */
021public class Slice implements Cloneable, Serializable {
022
023        /**
024         * 
025         */
026        private static final long serialVersionUID = 3714928852236201310L;
027        private Integer start;
028        private Integer stop;
029        private int step;
030
031        private int length; // max length of dimension
032
033        public Slice() {
034                this(null, null, 1);
035        }
036
037        /**
038         * Default to starting at 0 with steps of 1
039         * @param stop if null, then default to whatever shape is when converted
040         */
041        public Slice(final Integer stop) {
042                this(null, stop, 1);
043        }
044
045        /**
046         * Default to steps of 1
047         * @param start if null, then default to 0
048         * @param stop if null, then default to whatever shape is when converted
049         */
050        public Slice(final Integer start, final Integer stop) {
051                this(start, stop, 1);
052        }
053
054        /**
055         * 
056         * @param start if null, then default to bound
057         * @param stop if null, then default to bound
058         * @param step if null, then default to 1
059         */
060        public Slice(final Integer start, final Integer stop, final Integer step) {
061                this.start = start;
062                this.stop = stop;
063                this.step = step == null ? 1 : step;
064                length = -1;
065        }
066
067        /**
068         * Copy another slice
069         * @param other
070         */
071        private Slice(final Slice other) {
072                start  = other.start;
073                stop   = other.stop;
074                step   = other.step;
075                length = other.length;
076        }
077
078        @Override
079        public Slice clone() {
080                return new Slice(this);
081        }
082
083        /**
084         * Set maximum value of slice
085         * @param length
086         * @return this slice
087         */
088        public Slice setLength(int length) {
089                if (stop != null && step > 0 && length < stop) {
090                        throw new IllegalArgumentException("Length must be greater than or equal to stop");
091                }
092                if (start != null && step < 0 && length < start) {
093                        throw new IllegalArgumentException("Length must be greater than or equal to start");
094                }
095                this.length = length;
096                return this;
097        }
098
099        /**
100         * @return true if slice represents complete dimension
101         */
102        public boolean isSliceComplete() {
103                if (start == null && stop == null && (step == 1 || step == -1))
104                        return true;
105                if (length > 0) {
106                        return getNumSteps() == length;
107                }
108
109                return true;
110        }
111
112        /**
113         * @return maximum value of slice
114         */
115        public int getLength() {
116                return length;
117        }
118
119        /**
120         * @return starting position of slice
121         */
122        public Integer getStart() {
123                return start;
124        }
125
126        /**
127         * @return stopping position of slice
128         */
129        public Integer getStop() {
130                return stop;
131        }
132
133        /**
134         * @return step size of slice
135         */
136        public int getStep() {
137                return step;
138        }
139
140        /**
141         * Set starting position of slice
142         * @param start (can be null)
143         */
144        public void setStart(Integer start) {
145                if (start != null && length > 0) {
146                        if (step > 0) {
147                                int end = stop == null ? length : stop;
148                                if (start >= end) {
149                                        throw new IllegalArgumentException("Non-null start must be less than end");
150                                }
151                        } else {
152                                int end = stop == null ? -1 : stop;
153                                if (start < end) {
154                                        throw new IllegalArgumentException("Non-null start must be greater than end for negative step");
155                                }
156                        }
157                }
158                this.start = start;
159        }
160
161        /**
162         * Set stopping position of slice
163         * @param stop (can be null)
164         */
165        public void setStop(Integer stop) {
166                if (stop != null && length > 0) {
167                        if (step > 0) {
168                                int beg = start == null ? 0 : start;
169                                if (stop < beg) {
170                                        throw new IllegalArgumentException("Non-null stop must be greater than or equal to beginning");
171                                }
172                        } else {
173                                int beg = start == null ? length - 1 : start;
174                                if (stop >= beg) {
175                                        throw new IllegalArgumentException("Non-null stop must be less than beginning for negative step");
176                                }
177                        }
178                        if (stop > length)
179                                stop = length;
180                }
181                this.stop = stop;
182        }
183
184        /**
185         * Set start and end from implied number of steps. I.e. shift start to position given by
186         * parameter whilst keeping size of slice fixed
187         * @param beg
188         * @return true if end reached
189         */
190        public boolean setPosition(int beg) {
191                boolean end = false;
192                int len = getNumSteps();
193                int max = getNumSteps(beg, length, step);
194                if (len > max) {
195                        len = max;
196                        end = true;
197                }
198                start = beg;
199                stop = start + (len-1) * step + 1;
200                return end;
201        }
202
203        /**
204         * Get position of n-th step in slice
205         * @param n
206         * @return position
207         */
208        public int getPosition(int n) {
209                if (n < 0)
210                        throw new IllegalArgumentException("Given n-th step should be non-negative");
211                if (n >= getNumSteps())
212                        throw new IllegalArgumentException("N-th step exceeds extent of slice");
213                int beg;
214                if (start == null) {
215                        if (step < 0) {
216                                if (length < 0) {
217                                        if (stop == null) {
218                                                throw new IllegalStateException("Length or stop should be set");
219                                        }
220                                        beg = stop - 1;
221                                } else {
222                                        beg = length - 1;
223                                }
224                        } else {
225                                beg = 0;
226                        }
227                } else {
228                        beg = start;
229                }
230                return beg + step*n;
231        }
232
233        /**
234         * Set step size of slice
235         * @param step
236         */
237        public void setStep(int step) {
238                if (step == 0) {
239                        throw new IllegalArgumentException("Step must not be zero");
240                }
241                this.step = step;
242        }
243
244        /**
245         * Append string representation of slice
246         * @param s
247         * @param len
248         * @param beg
249         * @param end
250         * @param del
251         */
252        public static void appendSliceToString(final StringBuilder s, final int len, final int beg, final int end, final int del) {
253                int o = s.length();
254                if (del > 0) {
255                        if (beg != 0)
256                                s.append(beg);
257                } else {
258                        if (beg != len-1)
259                                s.append(beg);
260                }
261
262                int n = getNumSteps(beg, end, del);
263                if (n == 1) {
264                        if (s.length() == o) {
265                                s.append(beg);
266                        }
267                        return;
268                }
269
270                s.append(':');
271
272                if (del > 0) {
273                        if (end != len)
274                                s.append(end);
275                } else {
276                        if (end != -1)
277                                s.append(end);
278                }
279
280                if (del != 1) {
281                        s.append(':');
282                        s.append(del);
283                }
284        }
285
286        @Override
287        public String toString() {
288                StringBuilder s = new StringBuilder();
289                appendSliceToString(s, length, start != null ? start : (step > 0 ? 0 : length - 1), stop != null ? stop : (step > 0 ? length : -1), step);
290                return s.toString();
291        }
292
293        /**
294         * @return number of steps in slice
295         */
296        public int getNumSteps() {
297                if (length < 0) {
298                        if (stop == null)
299                                throw new IllegalStateException("Slice is underspecified - stop is null and length is negative");
300                        int beg = start == null ? (step > 0 ? 0: stop-1) : start;
301                        if (step > 0 && stop <= beg)
302                                return 0;
303                        if (step < 0 && stop > beg)
304                                return 0;
305                        return getNumSteps(beg, stop, step);
306                }
307                int beg = start == null ? (step > 0 ? 0: length-1) : start;
308                int end = stop == null ? (step > 0 ? length : -1) : stop;
309                return getNumSteps(beg, end, step);
310        }
311
312        /**
313         * @param beg
314         * @param end (exclusive)
315         * @return number of steps between limits
316         */
317        public int getNumSteps(int beg, int end) {
318                return getNumSteps(beg, end, step);
319        }
320
321        private static int getNumSteps(int beg, int end, int step) {
322                int del = step > 0 ? 1 : -1;
323                return Math.max(0, (end - beg - del) / step + 1);
324        }
325
326        /**
327         * @return last value in slice (< stop if step > 0, > stop if step < 0)
328         */
329        public int getEnd() {
330                int n = getNumSteps() - 1;
331                if (n < 0)
332                        throw new IllegalStateException("End is not defined");
333
334                return getPosition(n);
335        }
336
337        /**
338         * Flip slice direction so slice begins at previous end point, steps
339         * in the opposite direction, and finishes at the previous start point  .
340         * <p>
341         * Note the stop value may not be preserved across two flips
342         */
343        public Slice flip() {
344                if (length < 0) {
345                        Integer tmp = start == null ? null : start - step;
346                        start = stop == null ? null : getEnd();
347                        stop = tmp;
348                } else {
349                        Integer tstart = start;
350                        start = stop == null ? null : getEnd();
351                        stop = tstart == null ? null : tstart - step;
352                }
353                step = -step;
354
355                return this;
356        }
357
358        /**
359         * Populate given start, stop, step arrays from given slice array 
360         * @param shape
361         * @param start
362         * @param stop
363         * @param step
364         * @param slice
365         */
366        public static void convertFromSlice(final Slice[] slice, final int[] shape, final int[] start, final int[] stop, final int[] step) {
367                final int rank = shape.length;
368                final int length = slice == null ? 0 : slice.length;
369        
370                int i = 0;
371                for (; i < length; i++) {
372                        if (length > rank)
373                                break;
374
375                        Slice s = slice[i];
376                        if (s == null) {
377                                start[i] = 0;
378                                stop[i] = shape[i];
379                                step[i] = 1;
380                                continue;
381                        }
382                        int n;
383                        if (s.start == null) {
384                                start[i] = s.step > 0 ? 0 : shape[i] - 1;
385                        } else {
386                                n = s.start;
387                                if (n < 0)
388                                        n += shape[i];
389                                if (n < 0 || n >= shape[i]) {
390                                        throw new IllegalArgumentException(String.format("Start is out of bounds: %d is not in [%d,%d)",
391                                                        n, s.start, shape[i]));
392                                }
393                                start[i] = n;
394                        }
395
396                        if (s.stop == null) {
397                                stop[i] = s.step > 0 ? shape[i] : -1;
398                        } else {
399                                n = s.stop;
400                                if (n < 0)
401                                        n += shape[i];
402                                if (n < 0 || n > shape[i]) {
403                                        throw new IllegalArgumentException(String.format("Stop is out of bounds: %d is not in [%d,%d)",
404                                                        n, s.stop, shape[i]));
405                                }
406                                stop[i] = n;
407                        }
408
409                        n = s.step;
410                        if (n == 0) {
411                                throw new IllegalArgumentException("Step cannot be zero");
412                        }
413                        if (n > 0) {
414                                if (start[i] > stop[i])
415                                        throw new IllegalArgumentException("Start must be less than stop for positive steps");
416                        } else {
417                                if (start[i] < stop[i])
418                                        throw new IllegalArgumentException("Start must be greater than stop for negative steps");                               
419                        }
420                        step[i] = n;
421                }
422                for (; i < rank; i++) {
423                        start[i] = 0;
424                        stop[i] = shape[i];
425                        step[i] = 1;
426                }
427        }
428
429        /**
430         * Convert from a set of integer arrays to a slice array
431         * @param start
432         * @param stop
433         * @param step
434         * @return a slice array
435         */
436        public static Slice[] convertToSlice(final int[] start, final int[] stop, final int[] step) {
437                int orank = start.length;
438
439                if (stop.length != orank || step.length != orank) {
440                        throw new IllegalArgumentException("All arrays must be same length");
441                }
442
443                Slice[] slice = new Slice[orank];
444
445                for (int j = 0; j < orank; j++) {
446                        slice[j] = new Slice(start[j], stop[j], step[j]);
447                }
448
449                return slice;
450        }
451
452        /**
453         * Convert a string to a slice array
454         * 
455         * @param sliceString
456         * @return a slice array
457         */
458        public static Slice[] convertFromString(String sliceString) {
459
460                String clean = sliceString.replace("[", "");
461                clean = clean.replace("]", "");
462
463                String[] sub = clean.split(",");
464
465                Slice[] slices = new Slice[sub.length];
466
467                for (int i = 0; i < sub.length; i++) {
468                        String s = sub[i];
469
470                        Slice slice = new Slice(); 
471                        slices[i] = slice;
472
473                        int idx0 = s.indexOf(":");
474
475                        int n = 0;
476                        if (idx0 == -1) {
477                                n = Integer.parseInt(s);
478                                slice.setStart(n);
479                                slice.setStop(n + 1);
480                                continue;
481                        } else if (idx0 != 0) {
482                                n = Integer.parseInt(s.substring(0, idx0));
483                        }
484                        slice.setStart(n);
485
486                        idx0++;
487                        int idx1 = s.indexOf(":", idx0);
488                        if (idx1 == -1) {
489                                String t = s.substring(idx0).trim(); 
490                                if (t.length() == 0)
491                                        continue;
492
493                                slice.setStop(Integer.parseInt(t));
494                                continue;
495                        } else if (idx1 != idx0) {
496                                slice.setStop(Integer.parseInt(s.substring(idx0, idx1)));
497                        }
498
499                        String t = s.substring(idx1 + 1).trim(); 
500                        if (t.length() > 0)
501                                slice.setStep(Integer.parseInt(t));
502                }
503
504                return slices;
505        }
506
507        /**
508         * Create a string representing the slice taken from given shape
509         * @param shape
510         * @param start
511         * @param stop
512         * @param step
513         * @return string representation
514         */
515        public static String createString(final int[] shape, final int[] start, final int[] stop, final int[] step) {
516                final int rank = shape.length;
517                if (rank == 0) {
518                        return "";
519                }
520                StringBuilder s = new StringBuilder();
521                for (int i = 0; i < rank; i++) {
522                        int l = shape[i];
523                        int d = step == null ? 1 : step[i];
524                        int b = start == null ? (d > 0 ? 0 : l - 1) : start[i];
525                        int e = stop == null ? (d > 0 ? l : -1) : stop[i];
526        
527                        appendSliceToString(s, l, b, e, d);
528                        s.append(',');
529                }
530        
531                return s.substring(0, s.length()-1);
532        }
533
534        /**
535         * @param slices
536         * @return string specifying slices
537         */
538        public static String createString(Slice... slices) {
539                if (slices == null || slices.length == 0) {
540                        return "";
541                }
542
543                StringBuilder t = new StringBuilder();
544                for (Slice s : slices) {
545                        t.append(s != null ? s.toString() : ':');
546                        t.append(',');
547                }
548
549                return t.substring(0, t.length() - 1);
550        }
551}