1/*
2 * Copyright (C) 2010 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.quicksearchbox;
18
19import android.database.DataSetObservable;
20import android.database.DataSetObserver;
21import android.util.Log;
22
23/**
24 * Collects all corpus results for a single query.
25 */
26public class Suggestions {
27    private static final boolean DBG = false;
28    private static final String TAG = "QSB.Suggestions";
29
30    /** True if {@link Suggestions#close} has been called. */
31    private boolean mClosed = false;
32    protected final String mQuery;
33
34    /**
35     * The observers that want notifications of changes to the published suggestions.
36     * This object may be accessed on any thread.
37     */
38    private final DataSetObservable mDataSetObservable = new DataSetObservable();
39
40    private Source mSource;
41
42    private SourceResult mResult;
43
44    private int mRefCount = 0;
45
46    private boolean mDone = false;
47
48    public Suggestions(String query, Source source) {
49        mQuery = query;
50        mSource = source;
51    }
52
53    public void acquire() {
54        mRefCount++;
55    }
56
57    public void release() {
58        mRefCount--;
59        if (mRefCount <= 0) {
60            close();
61        }
62    }
63
64    public Source getSource() {
65        return mSource;
66    }
67
68    /**
69     * Marks the suggestions set as complete, regardless of whether all corpora have
70     * returned.
71     */
72    public void done() {
73        mDone = true;
74    }
75
76    /**
77     * Checks whether all sources have reported.
78     * Must be called on the UI thread, or before this object is seen by the UI thread.
79     */
80    public boolean isDone() {
81        return mDone || mResult != null;
82    }
83
84    /**
85     * Adds a list of corpus results. Must be called on the UI thread, or before this
86     * object is seen by the UI thread.
87     */
88    public void addResults(SourceResult result) {
89        if (isClosed()) {
90            result.close();
91            return;
92        }
93
94        if (DBG) {
95            Log.d(TAG, "addResults["+ hashCode() + "] source:" +
96                    result.getSource().getName() + " results:" + result.getCount());
97        }
98        if (!mQuery.equals(result.getUserQuery())) {
99          throw new IllegalArgumentException("Got result for wrong query: "
100                + mQuery + " != " + result.getUserQuery());
101        }
102        mResult = result;
103        notifyDataSetChanged();
104    }
105
106    /**
107     * Registers an observer that will be notified when the reported results or
108     * the done status changes.
109     */
110    public void registerDataSetObserver(DataSetObserver observer) {
111        if (mClosed) {
112            throw new IllegalStateException("registerDataSetObserver() when closed");
113        }
114        mDataSetObservable.registerObserver(observer);
115    }
116
117
118    /**
119     * Unregisters an observer.
120     */
121    public void unregisterDataSetObserver(DataSetObserver observer) {
122        mDataSetObservable.unregisterObserver(observer);
123    }
124
125    /**
126     * Calls {@link DataSetObserver#onChanged()} on all observers.
127     */
128    protected void notifyDataSetChanged() {
129        if (DBG) Log.d(TAG, "notifyDataSetChanged()");
130        mDataSetObservable.notifyChanged();
131    }
132
133    /**
134     * Closes all the source results and unregisters all observers.
135     */
136    private void close() {
137        if (DBG) Log.d(TAG, "close() [" + hashCode() + "]");
138        if (mClosed) {
139            throw new IllegalStateException("Double close()");
140        }
141        mClosed = true;
142        mDataSetObservable.unregisterAll();
143        if (mResult != null) {
144            mResult.close();
145        }
146        mResult = null;
147    }
148
149    public boolean isClosed() {
150        return mClosed;
151    }
152
153    @Override
154    protected void finalize() {
155        if (!mClosed) {
156            Log.e(TAG, "LEAK! Finalized without being closed: Suggestions[" + getQuery() + "]");
157        }
158    }
159
160    public String getQuery() {
161        return mQuery;
162    }
163
164    /**
165     * Gets the list of corpus results reported so far. Do not modify or hang on to
166     * the returned iterator.
167     */
168    public SourceResult getResult() {
169        return mResult;
170    }
171
172    public SourceResult getWebResult() {
173        return mResult;
174    }
175
176    /**
177     * Gets the number of source results.
178     * Must be called on the UI thread, or before this object is seen by the UI thread.
179     */
180    public int getResultCount() {
181        if (isClosed()) {
182            throw new IllegalStateException("Called getSourceCount() when closed.");
183        }
184        return mResult == null ? 0 : mResult.getCount();
185    }
186
187    @Override
188    public String toString() {
189        return "Suggestions@" + hashCode() + "{source=" + mSource
190                + ",getResultCount()=" + getResultCount() + "}";
191    }
192
193}
194