001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2001-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010//
011// jhyde, 10 August, 2001
012*/
013package mondrian.rolap;
014
015import java.io.Serializable;
016import java.util.Arrays;
017
018/**
019 * A <code>CellKey<code> is used as a key in maps which access cells by their
020 * position.
021 *
022 * <p>CellKey is also used within
023 * {@link mondrian.rolap.agg.SparseSegmentDataset} to store values within
024 * aggregations.
025 *
026 * <p>It is important that CellKey is memory-efficient, and that the
027 * {@link Object#hashCode} and {@link Object#equals} methods are extremely
028 * efficient. There are particular implementations for the most likely cases
029 * where the number of axes is 1, 2, 3 and 4 as well as a general
030 * implementation.
031 *
032 * <p>To create a key, call the
033 * {@link mondrian.rolap.CellKey.Generator#newCellKey(int[])} method.
034 *
035 * @author jhyde
036 * @since 10 August, 2001
037 */
038public interface CellKey extends Serializable {
039    /**
040     * Returns the number of axes.
041     *
042     * @return number of axes
043     */
044    int size();
045
046    /**
047     * Returns the axis keys as an array.
048     *
049     * <p>Note: caller should treat the array as immutable. If the contents of
050     * the array are modified, behavior is unspecified.
051     *
052     * @return Array of axis keys
053     */
054    int[] getOrdinals();
055
056    /**
057     * This method make a copy of the int array parameter.
058     * Throws a RuntimeException if the int array size is not the
059     * size of the CellKey.
060     *
061     * @param pos Array of axis keys
062     */
063    void setOrdinals(int[] pos);
064
065    /**
066     * Returns the <code>axis</code>th axis value.
067     *
068     * @param axis Axis ordinal
069     * @return Value of the <code>axis</code>th axis
070     * @throws ArrayIndexOutOfBoundsException if axis is out of range
071     */
072    int getAxis(int axis);
073
074    /**
075     * Sets a given axis.
076     *
077     * @param axis Axis ordinal
078     * @param value Value
079     * @throws RuntimeException if axis parameter is larger than {@link #size()}
080     */
081    void setAxis(int axis, int value);
082
083    /**
084     * Returns a mutable copy of this CellKey.
085     *
086     * @return Mutable copy
087     */
088    CellKey copy();
089
090    /**
091     * Returns the offset of the cell in a raster-scan order.
092     *
093     * <p>For example, if the axes have lengths {2, 5, 10} then cell (2, 3, 4)
094     * has offset
095     *
096     * <blockquote>
097     * (2 * mulitiplers[0]) + (3 * multipliers[1]) + (4 * multipliers[2])<br/>
098     * = (2 * 50) + (3 * 10) + (4 * 1)<br/>
099     * = 134</blockquote>
100     *
101     * <p>The multipliers are the product of the lengths of all later axes, in
102     * this case (50, 10, 1).
103     *
104     * @param axisMultipliers For each axis, the product of the lengths of later
105     *     axes
106     * @return The raster-scan ordinal of this cell
107     */
108    int getOffset(int[] axisMultipliers);
109
110    public abstract class Generator {
111        /**
112         * Creates a CellKey with a given number of axes.
113         *
114         * @param size Number of axes
115         * @return new CellKey
116         */
117        public static CellKey newCellKey(int size) {
118            switch (size) {
119            case 0:
120                return Zero.INSTANCE;
121            case 1:
122                return new One(0);
123            case 2:
124                return new Two(0, 0);
125            case 3:
126                return new Three(0, 0, 0);
127            case 4:
128                return new Four(0, 0, 0, 0);
129            default:
130                return new Many(new int[size]);
131            }
132        }
133
134        /**
135         * Creates a CellKey populated with the given coordinates.
136         *
137         * @param pos Coordinate array
138         * @return CellKey
139         */
140        public static CellKey newCellKey(int[] pos) {
141            switch (pos.length) {
142            case 0:
143                return Zero.INSTANCE;
144            case 1:
145                return new One(pos[0]);
146            case 2:
147                return new Two(pos[0], pos[1]);
148            case 3:
149                return new Three(pos[0], pos[1], pos[2]);
150            case 4:
151                return new Four(pos[0], pos[1], pos[2], pos[3]);
152            default:
153                return new Many(pos.clone());
154            }
155        }
156
157        /**
158         * Creates a CellKey implemented by an array to hold the coordinates,
159         * regardless of the size.
160         *
161         * <p>This is used for testing only. The CellKey will fail to compare
162         * equal to naturally created keys of size 0, 1, 2 or 3.
163         *
164         * @param size Number of coordinates
165         * @return CallKey
166         */
167        static CellKey newManyCellKey(int size) {
168            return new Many(new int[size]);
169        }
170
171        public static int getOffset(
172            int[] ordinals,
173            int[] axisMultipliers)
174        {
175            // TODO: We know that axisMultiplers[ordinals.length - 1] == 1. Can
176            // we avoid multiplying by it?
177            int offset = 0;
178            for (int i = 0; i < ordinals.length; i++) {
179                offset += ordinals[i] * axisMultipliers[i];
180            }
181            return offset;
182        }
183    }
184
185    public class Zero implements CellKey {
186        private static final long serialVersionUID = 6063541581473797367L;
187        private static final int[] EMPTY_INT_ARRAY = new int[0];
188        public static final Zero INSTANCE = new Zero();
189
190        /**
191         * Use singleton {@link #INSTANCE}.
192         */
193        private Zero() {
194        }
195
196        public Zero copy() {
197            // no need to make copy since there is no state
198            return this;
199        }
200
201        public int getOffset(int[] axisMultipliers) {
202            return 0;
203        }
204
205        public boolean equals(Object o) {
206            return o == this;
207        }
208
209        public int hashCode() {
210            return 11;
211        }
212
213        public int size() {
214            return 0;
215        }
216
217        public int[] getOrdinals() {
218            return EMPTY_INT_ARRAY;
219        }
220
221        public void setOrdinals(int[] pos) {
222            if (pos.length != 0) {
223                throw new IllegalArgumentException();
224            }
225        }
226
227        public int getAxis(int axis) {
228            throw new ArrayIndexOutOfBoundsException(axis);
229        }
230
231        public void setAxis(int axis, int value) {
232            throw new ArrayIndexOutOfBoundsException(axis);
233        }
234    }
235
236    public class One implements CellKey {
237        private static final long serialVersionUID = 2160238882970820960L;
238        private int ordinal0;
239
240        /**
241         * Creates a One.
242         *
243         * @param ordinal0 Ordinate #0
244         */
245        private One(int ordinal0) {
246            this.ordinal0 = ordinal0;
247        }
248
249        public int size() {
250            return 1;
251        }
252
253        public int[] getOrdinals() {
254            return new int[] {ordinal0};
255        }
256
257        public void setOrdinals(int[] pos) {
258            if (pos.length != 1) {
259                throw new IllegalArgumentException();
260            }
261            ordinal0 = pos[0];
262        }
263
264        public int getAxis(int axis) {
265            switch (axis) {
266            case 0:
267                return ordinal0;
268            default:
269                throw new ArrayIndexOutOfBoundsException(axis);
270            }
271        }
272
273        public void setAxis(int axis, int value) {
274            switch (axis) {
275            case 0:
276                ordinal0 = value;
277                break;
278            default:
279                throw new ArrayIndexOutOfBoundsException(axis);
280            }
281        }
282
283        public One copy() {
284            return new One(ordinal0);
285        }
286
287        public int getOffset(int[] axisMultipliers) {
288            return ordinal0;
289        }
290
291        public boolean equals(Object o) {
292            // here we cheat, we know that all CellKey's will be the same size
293            if (o instanceof One) {
294                One other = (One) o;
295                return (this.ordinal0 == other.ordinal0);
296            } else {
297                return false;
298            }
299        }
300
301        public String toString() {
302            return "(" + ordinal0 + ")";
303        }
304
305        public int hashCode() {
306            return 17 + ordinal0;
307        }
308    }
309
310    public class Two implements CellKey {
311        private static final long serialVersionUID = 1901188836648369359L;
312        private int ordinal0;
313        private int ordinal1;
314
315        /**
316         * Creates a Two.
317         *
318         * @param ordinal0 Ordinate #0
319         * @param ordinal1 Ordinate #1
320         */
321        private Two(int ordinal0, int ordinal1) {
322            this.ordinal0 = ordinal0;
323            this.ordinal1 = ordinal1;
324        }
325
326        public String toString() {
327            return "(" + ordinal0 + ", " + ordinal1 + ")";
328        }
329
330        public Two copy() {
331            return new Two(ordinal0, ordinal1);
332        }
333
334        public int getOffset(int[] axisMultipliers) {
335            return ordinal0 * axisMultipliers[0]
336                + ordinal1;
337        }
338
339        public boolean equals(Object o) {
340            if (o instanceof Two) {
341                Two other = (Two) o;
342                return (other.ordinal0 == this.ordinal0)
343                    && (other.ordinal1 == this.ordinal1);
344            } else {
345                return false;
346            }
347        }
348
349        public int hashCode() {
350            int h0 = 17 + ordinal0;
351            return h0 * 37 + ordinal1;
352        }
353
354        public int size() {
355            return 2;
356        }
357
358        public int[] getOrdinals() {
359            return new int[] {ordinal0, ordinal1};
360        }
361
362        public void setOrdinals(int[] pos) {
363            if (pos.length != 2) {
364                throw new IllegalArgumentException();
365            }
366            ordinal0 = pos[0];
367            ordinal1 = pos[1];
368        }
369
370        public int getAxis(int axis) {
371            switch (axis) {
372            case 0:
373                return ordinal0;
374            case 1:
375                return ordinal1;
376            default:
377                throw new ArrayIndexOutOfBoundsException(axis);
378            }
379        }
380
381        public void setAxis(int axis, int value) {
382            switch (axis) {
383            case 0:
384                ordinal0 = value;
385                break;
386            case 1:
387                ordinal1 = value;
388                break;
389            default:
390                throw new ArrayIndexOutOfBoundsException(axis);
391            }
392        }
393    }
394
395    class Three implements CellKey {
396        private static final long serialVersionUID = -2645858781233421151L;
397        private int ordinal0;
398        private int ordinal1;
399        private int ordinal2;
400
401        /**
402         * Creates a Three.
403         *
404         * @param ordinal0 Ordinate #0
405         * @param ordinal1 Ordinate #1
406         * @param ordinal2 Ordinate #2
407         */
408        private Three(int ordinal0, int ordinal1, int ordinal2) {
409            this.ordinal0 = ordinal0;
410            this.ordinal1 = ordinal1;
411            this.ordinal2 = ordinal2;
412        }
413
414        public String toString() {
415            return "(" + ordinal0 + ", " + ordinal1 + ", " + ordinal2 + ")";
416        }
417
418        public Three copy() {
419            return new Three(ordinal0, ordinal1, ordinal2);
420        }
421
422        public int getOffset(int[] axisMultipliers) {
423            return ordinal0 * axisMultipliers[0]
424                + ordinal1 * axisMultipliers[1]
425                + ordinal2;
426        }
427
428        public boolean equals(Object o) {
429            // here we cheat, we know that all CellKey's will be the same size
430            if (o instanceof Three) {
431                Three other = (Three) o;
432                return (other.ordinal0 == this.ordinal0)
433                    && (other.ordinal1 == this.ordinal1)
434                    && (other.ordinal2 == this.ordinal2);
435            } else {
436                return false;
437            }
438        }
439
440        public int hashCode() {
441            int h0 = 17 + ordinal0;
442            int h1 = h0 * 37 + ordinal1;
443            return h1 * 37 + ordinal2;
444        }
445
446        public int getAxis(int axis) {
447            switch (axis) {
448            case 0:
449                return ordinal0;
450            case 1:
451                return ordinal1;
452            case 2:
453                return ordinal2;
454            default:
455                throw new ArrayIndexOutOfBoundsException(axis);
456            }
457        }
458
459        public void setAxis(int axis, int value) {
460            switch (axis) {
461            case 0:
462                ordinal0 = value;
463                break;
464            case 1:
465                ordinal1 = value;
466                break;
467            case 2:
468                ordinal2 = value;
469                break;
470            default:
471                throw new ArrayIndexOutOfBoundsException(axis);
472            }
473        }
474
475        public int size() {
476            return 3;
477        }
478
479        public int[] getOrdinals() {
480            return new int[] {ordinal0, ordinal1, ordinal2};
481        }
482
483        public void setOrdinals(int[] pos) {
484            if (pos.length != 3) {
485                throw new IllegalArgumentException();
486            }
487            ordinal0 = pos[0];
488            ordinal1 = pos[1];
489            ordinal2 = pos[2];
490        }
491    }
492
493    class Four implements CellKey {
494        private static final long serialVersionUID = -2645858781233421151L;
495        private int ordinal0;
496        private int ordinal1;
497        private int ordinal2;
498        private int ordinal3;
499
500        /**
501         * Creates a Four.
502         */
503        private Four(
504            int ordinal0,
505            int ordinal1,
506            int ordinal2,
507            int ordinal3)
508        {
509            this.ordinal0 = ordinal0;
510            this.ordinal1 = ordinal1;
511            this.ordinal2 = ordinal2;
512            this.ordinal3 = ordinal3;
513        }
514
515        public String toString() {
516            return
517                "(" + ordinal0 + ", " + ordinal1
518                + ", " + ordinal2 + ", " + ordinal3 + ")";
519        }
520
521        public Four copy() {
522            return new Four(ordinal0, ordinal1, ordinal2, ordinal3);
523        }
524
525        public int getOffset(int[] axisMultipliers) {
526            return ordinal0 * axisMultipliers[0]
527                + ordinal1 * axisMultipliers[1]
528                + ordinal2 * axisMultipliers[2]
529                + ordinal3;
530        }
531
532        public boolean equals(Object o) {
533            // here we cheat, we know that all CellKey's will be the same size
534            if (o instanceof Four) {
535                Four other = (Four) o;
536                return (other.ordinal0 == this.ordinal0)
537                    && (other.ordinal1 == this.ordinal1)
538                    && (other.ordinal2 == this.ordinal2)
539                    && (other.ordinal3 == this.ordinal3);
540            } else {
541                return false;
542            }
543        }
544
545        public int hashCode() {
546            int h0 = 17 + ordinal0;
547            int h1 = h0 * 37 + ordinal1;
548            int h2 = h1 * 37 + ordinal2;
549            return h2 * 37 + ordinal3;
550        }
551
552        public int getAxis(int axis) {
553            switch (axis) {
554            case 0:
555                return ordinal0;
556            case 1:
557                return ordinal1;
558            case 2:
559                return ordinal2;
560            case 3:
561                return ordinal3;
562            default:
563                throw new ArrayIndexOutOfBoundsException(axis);
564            }
565        }
566
567        public void setAxis(int axis, int value) {
568            switch (axis) {
569            case 0:
570                ordinal0 = value;
571                break;
572            case 1:
573                ordinal1 = value;
574                break;
575            case 2:
576                ordinal2 = value;
577                break;
578            case 3:
579                ordinal3 = value;
580                break;
581            default:
582                throw new ArrayIndexOutOfBoundsException(axis);
583            }
584        }
585
586        public int size() {
587            return 4;
588        }
589
590        public int[] getOrdinals() {
591            return new int[] {ordinal0, ordinal1, ordinal2, ordinal3};
592        }
593
594        public void setOrdinals(int[] pos) {
595            if (pos.length != 4) {
596                throw new IllegalArgumentException();
597            }
598            ordinal0 = pos[0];
599            ordinal1 = pos[1];
600            ordinal2 = pos[2];
601            ordinal3 = pos[3];
602        }
603    }
604
605    public class Many implements CellKey {
606        private static final long serialVersionUID = 3438398157192694834L;
607        private final int[] ordinals;
608
609        /**
610         * Creates a Many.
611         * @param ordinals Ordinates
612         */
613        protected Many(int[] ordinals) {
614            this.ordinals = ordinals;
615        }
616
617        public final int size() {
618            return this.ordinals.length;
619        }
620
621        public final void setOrdinals(int[] pos) {
622            if (ordinals.length != pos.length) {
623                throw new IllegalArgumentException();
624            }
625            System.arraycopy(pos, 0, this.ordinals, 0, ordinals.length);
626        }
627
628        public final int[] getOrdinals() {
629            return this.ordinals;
630        }
631
632        public void setAxis(int axis, int value) {
633            this.ordinals[axis] = value;
634        }
635
636        public int getAxis(int axis) {
637            return this.ordinals[axis];
638        }
639
640        public String toString() {
641            StringBuilder buf = new StringBuilder();
642            buf.append('(');
643            for (int i = 0; i < ordinals.length; i++) {
644                if (i > 0) {
645                    buf.append(',');
646                }
647                buf.append(ordinals[i]);
648            }
649            buf.append(')');
650            return buf.toString();
651        }
652
653        public Many copy() {
654            return new Many(this.ordinals.clone());
655        }
656
657        public int getOffset(int[] axisMultipliers) {
658            return Generator.getOffset(ordinals, axisMultipliers);
659        }
660
661        public int hashCode() {
662            int h = 17;
663            for (int ordinal : ordinals) {
664                h = (h * 37) + ordinal;
665            }
666            return h;
667        }
668
669        public boolean equals(Object o) {
670            if (o instanceof Many) {
671                Many that = (Many) o;
672                return Arrays.equals(this.ordinals, that.ordinals);
673            }
674            return false;
675        }
676    }
677}
678
679// End CellKey.java