001    /*
002     * Copyright 2010-2016 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.utils;
018    
019    import org.jetbrains.annotations.NotNull;
020    
021    import java.lang.reflect.Array;
022    import java.util.*;
023    
024    /**
025     * A List which is optimised for the sizes of 0 and 1,
026     * in which cases it would not allocate array at all.
027     *
028     * This class was copied from com.intellij.util.SmartList.
029     */
030    @SuppressWarnings("unchecked")
031    public class SmartList<E> extends AbstractList<E> implements RandomAccess {
032        private int mySize;
033        private Object myElem; // null if mySize==0, (E)elem if mySize==1, Object[] if mySize>=2
034    
035        public SmartList() { }
036    
037        public SmartList(E element) {
038            add(element);
039        }
040    
041        public SmartList(@NotNull Collection<? extends E> elements) {
042            int size = elements.size();
043            if (size == 1) {
044                E element = elements instanceof List ? (E)((List)elements).get(0) : elements.iterator().next();
045                add(element);
046            }
047            else if (size > 0) {
048                mySize = size;
049                myElem = elements.toArray(new Object[size]);
050            }
051        }
052    
053        public SmartList(@NotNull E... elements) {
054            if (elements.length == 1) {
055                add(elements[0]);
056            }
057            else if (elements.length > 0) {
058                mySize = elements.length;
059                myElem = Arrays.copyOf(elements, mySize);
060            }
061        }
062    
063        @Override
064        public E get(int index) {
065            if (index < 0 || index >= mySize) {
066                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize);
067            }
068            if (mySize == 1) {
069                return (E)myElem;
070            }
071            return (E)((Object[])myElem)[index];
072        }
073    
074        @Override
075        public boolean add(E e) {
076            if (mySize == 0) {
077                myElem = e;
078            }
079            else if (mySize == 1) {
080                Object[] array = new Object[2];
081                array[0] = myElem;
082                array[1] = e;
083                myElem = array;
084            }
085            else {
086                Object[] array = (Object[])myElem;
087                int oldCapacity = array.length;
088                if (mySize >= oldCapacity) {
089                    // have to resize
090                    int newCapacity = oldCapacity * 3 / 2 + 1;
091                    int minCapacity = mySize + 1;
092                    if (newCapacity < minCapacity) {
093                        newCapacity = minCapacity;
094                    }
095                    Object[] oldArray = array;
096                    myElem = array = new Object[newCapacity];
097                    System.arraycopy(oldArray, 0, array, 0, oldCapacity);
098                }
099                array[mySize] = e;
100            }
101    
102            mySize++;
103            modCount++;
104            return true;
105        }
106    
107        @Override
108        public void add(int index, E e) {
109            if (index < 0 || index > mySize) {
110                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize);
111            }
112    
113            if (mySize == 0) {
114                myElem = e;
115            }
116            else if (mySize == 1 && index == 0) {
117                Object[] array = new Object[2];
118                array[0] = e;
119                array[1] = myElem;
120                myElem = array;
121            }
122            else {
123                Object[] array = new Object[mySize + 1];
124                if (mySize == 1) {
125                    array[0] = myElem; // index == 1
126                }
127                else {
128                    Object[] oldArray = (Object[])myElem;
129                    System.arraycopy(oldArray, 0, array, 0, index);
130                    System.arraycopy(oldArray, index, array, index + 1, mySize - index);
131                }
132                array[index] = e;
133                myElem = array;
134            }
135    
136            mySize++;
137            modCount++;
138        }
139    
140        @Override
141        public int size() {
142            return mySize;
143        }
144    
145        @Override
146        public void clear() {
147            myElem = null;
148            mySize = 0;
149            modCount++;
150        }
151    
152        @Override
153        public E set(int index, E element) {
154            if (index < 0 || index >= mySize) {
155                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize);
156            }
157    
158            E oldValue;
159            if (mySize == 1) {
160                oldValue = (E)myElem;
161                myElem = element;
162            }
163            else {
164                Object[] array = (Object[])myElem;
165                oldValue = (E)array[index];
166                array[index] = element;
167            }
168            return oldValue;
169        }
170    
171        @Override
172        public E remove(int index) {
173            if (index < 0 || index >= mySize) {
174                throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize);
175            }
176    
177            E oldValue;
178            if (mySize == 1) {
179                oldValue = (E)myElem;
180                myElem = null;
181            }
182            else {
183                Object[] array = (Object[])myElem;
184                oldValue = (E)array[index];
185    
186                if (mySize == 2) {
187                    myElem = array[1 - index];
188                }
189                else {
190                    int numMoved = mySize - index - 1;
191                    if (numMoved > 0) {
192                        System.arraycopy(array, index + 1, array, index, numMoved);
193                    }
194                    array[mySize - 1] = null;
195                }
196            }
197            mySize--;
198            modCount++;
199            return oldValue;
200        }
201    
202        private static class EmptyIterator<T> implements Iterator<T> {
203            private static final EmptyIterator INSTANCE = new EmptyIterator();
204            public static <T> EmptyIterator<T> getInstance() {
205                //noinspection unchecked
206                return INSTANCE;
207            }
208            @Override
209            public boolean hasNext() {
210                return false;
211            }
212    
213            @Override
214            public T next() {
215                throw new NoSuchElementException();
216            }
217    
218            @Override
219            public void remove() {
220                throw new IllegalStateException();
221            }
222        }
223    
224    
225        @NotNull
226        @Override
227        public Iterator<E> iterator() {
228            if (mySize == 0) {
229                return EmptyIterator.getInstance();
230            }
231            if (mySize == 1) {
232                return new SingletonIterator();
233            }
234            return super.iterator();
235        }
236    
237    
238        private abstract static class SingletonIteratorBase<T> implements Iterator<T> {
239            private boolean myVisited;
240    
241            @Override
242            public final boolean hasNext() {
243                return !myVisited;
244            }
245    
246            @Override
247            public final T next() {
248                if (myVisited) {
249                    throw new NoSuchElementException();
250                }
251                myVisited = true;
252                checkCoModification();
253                return getElement();
254            }
255    
256            protected abstract void checkCoModification();
257    
258            protected abstract T getElement();
259        }
260    
261        private class SingletonIterator extends SingletonIteratorBase<E> {
262            private final int myInitialModCount;
263    
264            public SingletonIterator() {
265                myInitialModCount = modCount;
266            }
267    
268            @Override
269            protected E getElement() {
270                return (E)myElem;
271            }
272    
273            @Override
274            protected void checkCoModification() {
275                if (modCount != myInitialModCount) {
276                    throw new ConcurrentModificationException("ModCount: " + modCount + "; expected: " + myInitialModCount);
277                }
278            }
279    
280            @Override
281            public void remove() {
282                checkCoModification();
283                clear();
284            }
285        }
286    
287        public void sort(Comparator<? super E> comparator) {
288            if (mySize >= 2) {
289                Arrays.sort((E[])myElem, 0, mySize, comparator);
290            }
291        }
292    
293        public int getModificationCount() {
294            return modCount;
295        }
296    
297        @NotNull
298        @Override
299        public <T> T[] toArray(@NotNull T[] a) {
300            int aLength = a.length;
301            if (mySize == 1) {
302                if (aLength != 0) {
303                    a[0] = (T)myElem;
304                }
305                else {
306                    T[] r = (T[])Array.newInstance(a.getClass().getComponentType(), 1);
307                    r[0] = (T)myElem;
308                    return r;
309                }
310            }
311            else if (aLength < mySize) {
312                return (T[])Arrays.copyOf((E[])myElem, mySize, a.getClass());
313            }
314            else if (mySize != 0) {
315                //noinspection SuspiciousSystemArraycopy
316                System.arraycopy(myElem, 0, a, 0, mySize);
317            }
318    
319            if (aLength > mySize) {
320                a[mySize] = null;
321            }
322            return a;
323        }
324    
325        /**
326         * Trims the capacity of this list to be the
327         * list's current size.  An application can use this operation to minimize
328         * the storage of a list instance.
329         */
330        public void trimToSize() {
331            if (mySize < 2) return;
332            Object[] array = (Object[])myElem;
333            int oldCapacity = array.length;
334            if (mySize < oldCapacity) {
335                modCount++;
336                myElem = Arrays.copyOf(array, mySize);
337            }
338        }
339    }