ImageListUber.java revision 666ea1b28a76aeba74744148b15099254d918671
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.gallery;
18
19import android.net.Uri;
20
21import com.android.camera.ImageManager;
22import com.android.camera.Util;
23
24import java.util.Arrays;
25import java.util.Comparator;
26import java.util.HashMap;
27import java.util.PriorityQueue;
28
29/**
30 * A union of different <code>IImageList</code>. This class can merge several
31 * <code>IImageList</code> into one list and sort them according to the
32 * timestamp (The sorting must be same as all the given lists).
33 */
34public class ImageListUber implements IImageList {
35    @SuppressWarnings("unused")
36    private static final String TAG = "ImageListUber";
37
38    private final IImageList [] mSubList;
39    private final PriorityQueue<MergeSlot> mQueue;
40
41    // This is an array of Longs wherein each Long consists of two components:
42    // "a number" and "an index of sublist".
43    //   * The lower 32bit indicates the number of consecutive entries that
44    //     belong to a given sublist.
45    //
46    //   * The higher 32bit component indicates which sublist we're referring
47    //     to.
48    private long[] mSkipList;
49    private int mSkipListSize;
50    private int [] mSkipCounts;
51    private int mLastListIndex;
52
53    public ImageListUber(IImageList [] sublist, int sort) {
54        mSubList = sublist.clone();
55        mQueue = new PriorityQueue<MergeSlot>(4,
56                sort == ImageManager.SORT_ASCENDING
57                ? new AscendingComparator()
58                : new DescendingComparator());
59        mSkipList = new long[16];
60        mSkipListSize = 0;
61        mSkipCounts = new int[mSubList.length];
62        mLastListIndex = -1;
63        mQueue.clear();
64        for (int i = 0, n = mSubList.length; i < n; ++i) {
65            IImageList list = mSubList[i];
66            MergeSlot slot = new MergeSlot(list, i);
67            if (slot.next()) mQueue.add(slot);
68        }
69    }
70
71    public HashMap<String, String> getBucketIds() {
72        HashMap<String, String> hashMap = new HashMap<String, String>();
73        for (IImageList list : mSubList) {
74            hashMap.putAll(list.getBucketIds());
75        }
76        return hashMap;
77    }
78
79    public int getCount() {
80        int count = 0;
81        for (IImageList subList : mSubList) {
82            count += subList.getCount();
83        }
84        return count;
85    }
86
87    public boolean isEmpty() {
88        for (IImageList subList : mSubList) {
89            if (!subList.isEmpty()) return false;
90        }
91        return true;
92    }
93
94    // mSkipCounts is used to tally the counts as we traverse
95    // the mSkipList.  It's a member variable only so that
96    // we don't have to allocate each time through.  Otherwise
97    // it could just as easily be a local.
98
99    public IImage getImageAt(int index) {
100        if (index < 0 || index > getCount()) {
101            throw new IndexOutOfBoundsException(
102                    "index " + index + " out of range max is " + getCount());
103        }
104
105        int skipCounts[] = mSkipCounts;
106        // zero out the mSkipCounts since that's only used for the
107        // duration of the function call.
108        Arrays.fill(skipCounts, 0);
109
110        // a counter of how many images we've skipped in
111        // trying to get to index.  alternatively we could
112        // have decremented index but, alas, I liked this
113        // way more.
114        int skipCount = 0;
115
116        // scan the existing mSkipList to see if we've computed
117        // enough to just return the answer
118        for (int i = 0, n = mSkipListSize; i < n; ++i) {
119            long v = mSkipList[i];
120
121            int offset = (int) (v & 0xFFFFFFFF);
122            int which  = (int) (v >> 32);
123            if (skipCount + offset > index) {
124                int subindex = mSkipCounts[which] + (index - skipCount);
125                return mSubList[which].getImageAt(subindex);
126            }
127            skipCount += offset;
128            mSkipCounts[which] += offset;
129        }
130
131        for (; true; ++skipCount) {
132            MergeSlot slot = nextMergeSlot();
133            if (slot == null) return null;
134            if (skipCount == index) {
135                IImage result = slot.mImage;
136                if (slot.next()) mQueue.add(slot);
137                return result;
138            }
139            if (slot.next()) mQueue.add(slot);
140        }
141    }
142
143    private MergeSlot nextMergeSlot() {
144        MergeSlot slot = mQueue.poll();
145        if (slot == null) return null;
146        if (slot.mListIndex == mLastListIndex) {
147            int lastIndex = mSkipListSize - 1;
148            ++mSkipList[lastIndex];
149        } else {
150            mLastListIndex = slot.mListIndex;
151            if (mSkipList.length == mSkipListSize) {
152                long [] temp = new long[mSkipListSize * 2];
153                System.arraycopy(mSkipList, 0, temp, 0, mSkipListSize);
154                mSkipList = temp;
155            }
156            mSkipList[mSkipListSize++] = (((long) mLastListIndex) << 32) | 1;
157        }
158        return slot;
159    }
160
161    public IImage getImageForUri(Uri uri) {
162        for (IImageList sublist : mSubList) {
163            IImage image = sublist.getImageForUri(uri);
164            if (image != null) return image;
165        }
166        return null;
167    }
168
169    /**
170     * Modify the skip list when an image is deleted by finding
171     * the relevant entry in mSkipList and decrementing the
172     * counter.  This is simple because deletion can never
173     * cause change the order of images.
174     */
175    private void modifySkipCountForDeletedImage(int index) {
176        int skipCount = 0;
177        for (int i = 0, n = mSkipListSize; i < n; i++) {
178            long v = mSkipList[i];
179            int offset = (int) (v & 0xFFFFFFFF);
180            if (skipCount + offset > index) {
181                mSkipList[i] = v - 1;
182                break;
183            }
184            skipCount += offset;
185        }
186    }
187
188    private boolean removeImage(IImage image, int index) {
189        IImageList list = image.getContainer();
190        if (list != null && list.removeImage(image)) {
191            modifySkipCountForDeletedImage(index);
192            return true;
193        }
194        return false;
195    }
196
197    public boolean removeImage(IImage image) {
198        return removeImage(image, getImageIndex(image));
199    }
200
201    public boolean removeImageAt(int index) {
202        IImage image = getImageAt(index);
203        if (image == null) return false;
204        return removeImage(image, index);
205    }
206
207    public synchronized int getImageIndex(IImage image) {
208        IImageList list = image.getContainer();
209        int listIndex = Util.indexOf(mSubList, list);
210        if (listIndex == -1) {
211            throw new IllegalArgumentException();
212        }
213        int listOffset = list.getImageIndex(image);
214
215        // Similar algorithm as getImageAt(int index)
216        int skipCount = 0;
217        for (int i = 0, n = mSkipListSize; i < n; ++i) {
218            long value = mSkipList[i];
219            int offset = (int) (value & 0xFFFFFFFF);
220            int which  = (int) (value >> 32);
221            if (which == listIndex) {
222                if (listOffset < offset) {
223                    return skipCount + listOffset;
224                }
225                listOffset -= offset;
226            }
227            skipCount += offset;
228        }
229
230        for (; true; ++skipCount) {
231            MergeSlot slot = nextMergeSlot();
232            if (slot == null) return -1;
233            if (slot.mImage == image) {
234                if (slot.next()) mQueue.add(slot);
235                return skipCount;
236            }
237            if (slot.next()) mQueue.add(slot);
238        }
239    }
240
241    private static class DescendingComparator implements Comparator<MergeSlot> {
242
243        public int compare(MergeSlot m1, MergeSlot m2) {
244            if (m1.mDateTaken != m2.mDateTaken) {
245                return m1.mDateTaken < m2.mDateTaken ? 1 : -1;
246            }
247            return m1.mListIndex - m2.mListIndex;
248        }
249    }
250
251    private static class AscendingComparator implements Comparator<MergeSlot> {
252
253        public int compare(MergeSlot m1, MergeSlot m2) {
254            if (m1.mDateTaken != m2.mDateTaken) {
255                return m1.mDateTaken < m2.mDateTaken ? -1 : 1;
256            }
257            return m1.mListIndex - m2.mListIndex;
258        }
259    }
260
261    /**
262     * A merging slot is used to trace the current position of a sublist. For
263     * each given sub list, there will be one corresponding merge slot. We
264     * use merge-sort-like algorithm to build the merged list. At begining,
265     * we put all the slots in a sorted heap (by timestamp). Each time, we
266     * pop the slot with earliest timestamp out, get the image, and then move
267     * the index forward, and put it back to the heap.
268     */
269    private static class MergeSlot {
270        private int mOffset = -1;
271        private final IImageList mList;
272
273        int mListIndex;
274        long mDateTaken;
275        IImage mImage;
276
277        public MergeSlot(IImageList list, int index) {
278            mList = list;
279            mListIndex = index;
280        }
281
282        public boolean next() {
283            if (mOffset >= mList.getCount() - 1) return false;
284            mImage = mList.getImageAt(++mOffset);
285            mDateTaken = mImage.getDateTaken();
286            return true;
287        }
288    }
289
290    public void close() {
291        for (int i = 0, n = mSubList.length; i < n; ++i) {
292            mSubList[i].close();
293        }
294    }
295}
296