MultiSourceCorpus.java revision cd1e3ba5f7c3f5242345ff6f674281e3d6366e24
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
19
20import com.android.quicksearchbox.util.BarrierConsumer;
21
22import android.content.Context;
23
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.List;
27import java.util.concurrent.Executor;
28
29/**
30 * Base class for corpora backed by multiple sources.
31 */
32public abstract class MultiSourceCorpus extends AbstractCorpus {
33
34    private final Executor mExecutor;
35
36    private final ArrayList<Source> mSources;
37
38    // calculated values based on properties of sources:
39    private boolean mSourcePropertiesValid;
40    private int mQueryThreshold;
41    private boolean mQueryAfterZeroResults;
42    private boolean mVoiceSearchEnabled;
43
44
45    public MultiSourceCorpus(Context context, Config config,
46            Executor executor, Source... sources) {
47        super(context, config);
48        mExecutor = executor;
49
50        mSources = new ArrayList<Source>();
51        for (Source source : sources) {
52            addSource(source);
53        }
54
55    }
56
57    protected void addSource(Source source) {
58        if (source != null) {
59            mSources.add(source);
60            // invalidate calculated values:
61            mSourcePropertiesValid = false;
62        }
63    }
64
65    public Collection<Source> getSources() {
66        return mSources;
67    }
68
69    /**
70     * Creates a corpus result object for a set of source results.
71     * This method should not call {@link Result#fill}.
72     *
73     * @param query The query text.
74     * @param results The results of the queries.
75     * @param latency Latency in milliseconds of the suggestion queries.
76     * @return An instance of {@link Result} or a subclass of it.
77     */
78    protected Result createResult(String query, ArrayList<SourceResult> results, int latency) {
79        return new Result(query, results, latency);
80    }
81
82    /**
83     * Gets the sources to query for suggestions for the given input.
84     *
85     * @param query The current input.
86     * @param onlyCorpus If true, this is the only corpus being queried.
87     * @return The sources to query.
88     */
89    protected List<Source> getSourcesToQuery(String query, boolean onlyCorpus) {
90        List<Source> sources = new ArrayList<Source>();
91        for (Source candidate : getSources()) {
92            if (candidate.getQueryThreshold() <= query.length()) {
93                sources.add(candidate);
94            }
95        }
96        return sources;
97    }
98
99    private void calculateSourceProperties() {
100        mQueryThreshold = Integer.MAX_VALUE;
101        mQueryAfterZeroResults = false;
102        mVoiceSearchEnabled = false;
103        for (Source s : getSources()) {
104            mQueryThreshold = Math.min(mQueryThreshold, s.getQueryThreshold());
105            mQueryAfterZeroResults |= s.queryAfterZeroResults();
106            mVoiceSearchEnabled |= s.voiceSearchEnabled();
107        }
108        if (mQueryThreshold == Integer.MAX_VALUE) {
109            mQueryThreshold = 0;
110        }
111        mSourcePropertiesValid = true;
112    }
113
114    public int getQueryThreshold() {
115        if (!mSourcePropertiesValid) {
116            calculateSourceProperties();
117        }
118        return mQueryThreshold;
119    }
120
121    public boolean queryAfterZeroResults() {
122        if (!mSourcePropertiesValid) {
123            calculateSourceProperties();
124        }
125        return mQueryAfterZeroResults;
126    }
127
128    public boolean voiceSearchEnabled() {
129        if (!mSourcePropertiesValid) {
130            calculateSourceProperties();
131        }
132        return mVoiceSearchEnabled;
133    }
134
135    public CorpusResult getSuggestions(String query, int queryLimit, boolean onlyCorpus) {
136        LatencyTracker latencyTracker = new LatencyTracker();
137        List<Source> sources = getSourcesToQuery(query, onlyCorpus);
138        BarrierConsumer<SourceResult> consumer =
139                new BarrierConsumer<SourceResult>(sources.size());
140        boolean onlySource = sources.size() == 1;
141        for (Source source : sources) {
142            QueryTask<SourceResult> task = new QueryTask<SourceResult>(query, queryLimit,
143                    source, null, consumer, onlySource);
144            mExecutor.execute(task);
145        }
146        ArrayList<SourceResult> results = consumer.getValues();
147        int latency = latencyTracker.getLatency();
148        Result result = createResult(query, results, latency);
149        result.fill();
150        return result;
151    }
152
153    /**
154     * Base class for results returned by {@link MultiSourceCorpus#getSuggestions}.
155     * Subclasses of {@link MultiSourceCorpus} should override
156     * {@link MultiSourceCorpus#createResult} and return an instance of this class or a
157     * subclass.
158     */
159    protected class Result extends ListSuggestionCursor implements CorpusResult {
160
161        private final ArrayList<SourceResult> mResults;
162
163        private final int mLatency;
164
165        public Result(String userQuery, ArrayList<SourceResult> results, int latency) {
166            super(userQuery);
167            mResults = results;
168            mLatency = latency;
169        }
170
171        protected ArrayList<SourceResult> getResults() {
172            return mResults;
173        }
174
175        /**
176         * Fills the list of suggestions using the list of results.
177         * The default implementation concatenates the results.
178         */
179        public void fill() {
180            for (SourceResult result : getResults()) {
181                int count = result.getCount();
182                for (int i = 0; i < count; i++) {
183                    result.moveTo(i);
184                    add(new SuggestionPosition(result));
185                }
186            }
187        }
188
189        public Corpus getCorpus() {
190            return MultiSourceCorpus.this;
191        }
192
193        public int getLatency() {
194            return mLatency;
195        }
196
197        @Override
198        public void close() {
199            super.close();
200            for (SourceResult result : mResults) {
201                result.close();
202            }
203        }
204
205        @Override
206        public String toString() {
207            return getCorpus() + "[" + getUserQuery() + "]";
208        }
209    }
210
211}
212