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) 2011-2013 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.util;
011
012import mondrian.olap.Util;
013
014import java.io.Serializable;
015import java.util.*;
016
017/**
018 * Implementation of {@link java.util.SortedSet} based on an array. The array
019 * must already be sorted in natural order.
020 *
021 * @param <E>
022 *
023 * @author Julian Hyde
024 */
025public class ArraySortedSet<E extends Comparable<E>>
026    extends AbstractSet<E>
027    implements SortedSet<E>, Serializable
028{
029    private static final long serialVersionUID = -7613058579094914399L;
030    private final E[] values;
031    private final int start;
032    private final int end;
033
034    /**
035     * Creates a set backed by an array. The array must be sorted, and is
036     * not copied.
037     *
038     * @param values Array of values
039     */
040    public ArraySortedSet(E[] values) {
041        this(values, 0, values.length);
042    }
043
044    /**
045     * Creates a set backed by a region of an array. The array must be
046     * sorted, and is not copied.
047     *
048     * @param values Array of values
049     * @param start Index of start of region
050     * @param end Index of first element after end of region
051     */
052    public ArraySortedSet(E[] values, int start, int end) {
053        super();
054        this.values = values;
055        this.start = start;
056        this.end = end;
057    }
058
059    public Iterator<E> iterator() {
060        return asList().iterator();
061    }
062
063    public int size() {
064        return end - start;
065    }
066
067    public Comparator<? super E> comparator() {
068        return null;
069    }
070
071    public SortedSet<E> subSet(E fromElement, E toElement) {
072        int from = Util.binarySearch(values, start, end, fromElement);
073        if (from < 0) {
074            from = - (from + 1);
075        }
076        int to = Util.binarySearch(values, from, end, toElement);
077        if (to < 0) {
078            to = - (to + 1);
079        }
080        return subset(from, to);
081    }
082
083    public SortedSet<E> headSet(E toElement) {
084        int to = Util.binarySearch(values, start, end, toElement);
085        if (to < 0) {
086            to = - (to + 1);
087        }
088        return subset(start, to);
089    }
090
091    public SortedSet<E> tailSet(E fromElement) {
092        int from = Util.binarySearch(values, start, end, fromElement);
093        if (from < 0) {
094            from = - (from + 1);
095        }
096        return subset(from, end);
097    }
098
099    private SortedSet<E> subset(int from, int to) {
100        if (from == start && to == end) {
101            return this;
102        }
103        return new ArraySortedSet<E>(values, from, to);
104    }
105
106    private List<E> asList() {
107        //noinspection unchecked
108        List<E> list = Arrays.asList(values);
109        if (start > 0 || end < values.length) {
110            list = list.subList(start, end);
111        }
112        return list;
113    }
114
115    public E first() {
116        try {
117            return values[start];
118        } catch (ArrayIndexOutOfBoundsException e) {
119            throw new NoSuchElementException();
120        }
121    }
122
123    public E last() {
124        try {
125            return values[end - 1];
126        } catch (ArrayIndexOutOfBoundsException e) {
127            throw new NoSuchElementException();
128        }
129    }
130
131    @Override
132    public Object[] toArray() {
133        if (start == 0 && end == values.length) {
134            return values.clone();
135        } else {
136            final Object[] os = new Object[end - start];
137            System.arraycopy(values, start, os, 0, end - start);
138            return os;
139        }
140    }
141
142    @SuppressWarnings({"unchecked"})
143    @Override
144    public <T> T[] toArray(T[] a) {
145        int size = size();
146        T[] r = a.length >= size
147            ? a
148            : (T[]) java.lang.reflect.Array.newInstance(
149                a.getClass().getComponentType(), size);
150        //noinspection SuspiciousSystemArraycopy
151        System.arraycopy(values, start, r, 0, end - start);
152        if (r.length > size) {
153            r[size] = null;
154        }
155        return r;
156    }
157
158    /**
159     * Performs a merge between two {@link ArraySortedSet} instances
160     * in O(n) time, returning a third instance that doesn't include
161     * duplicates.
162     *
163     * <p>For example,
164     * <tt>ArraySortedSet("a", "b", "c").merge(ArraySortedSet("a", "c",
165     * "e"))</tt> returns
166     * <tt>ArraySortedSet("a", "b", "c", "e")}</tt>.</p>
167     *
168     * @param arrayToMerge Other set to combine with this
169     * @return Set containing union of the elements of inputs
170     *
171     * @see Util#intersect(java.util.SortedSet, java.util.SortedSet)
172     */
173    public ArraySortedSet<E> merge(
174        ArraySortedSet<E> arrayToMerge)
175    {
176        assert arrayToMerge != null;
177
178        // No need to merge when one array is empty.
179        if (this.size() == 0) {
180            return arrayToMerge;
181        }
182        if (arrayToMerge.size() == 0) {
183            return this;
184        }
185
186        int p1 = 0, p2 = 0, m = 0, k = this.size() + arrayToMerge.size();
187
188        final E[] data1 = this.values;
189        final E[] data2 = arrayToMerge.values;
190        @SuppressWarnings({"unchecked"})
191        E[] merged =
192            Util.genericArray(
193                (Class<E>) this.values[0].getClass(),
194                k);
195
196        while (p1 < this.size() && p2 < arrayToMerge.size()) {
197            final int compare =
198                data1[p1].compareTo(data2[p2]);
199            if (compare == 0) {
200                merged[m++] = data1[p1++];
201                p2++;
202            } else if (compare < 0) {
203                merged[m++] = data1[p1++];
204            } else {
205                merged[m++] = data2[p2++];
206            }
207        }
208
209        while (p1 < this.size()) {
210            merged[m++] = data1[p1++];
211        }
212
213        while (p2 < arrayToMerge.size()) {
214            merged[m++] = data2[p2++];
215        }
216
217        // Note that m < k if there were duplicates. Result has fewer elements
218        // than sum of inputs. But it's not worth truncating the array.
219        return new ArraySortedSet<E>(merged, 0, m);
220    }
221
222    @Override
223    public boolean contains(Object o) {
224        //noinspection unchecked
225        return o != null
226            && Util.binarySearch(values, start, end, (E) o) >= 0;
227    }
228}
229
230// End ArraySortedSet.java