1aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding/*
2aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * Copyright 2018 The Android Open Source Project
3aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding *
4aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * Licensed under the Apache License, Version 2.0 (the "License");
5aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * you may not use this file except in compliance with the License.
6aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * You may obtain a copy of the License at
7aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding *
8aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding *      http://www.apache.org/licenses/LICENSE-2.0
9aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding *
10aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * Unless required by applicable law or agreed to in writing, software
11aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * distributed under the License is distributed on an "AS IS" BASIS,
12aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * See the License for the specific language governing permissions and
14aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * limitations under the License.
15aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding */
16aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
17aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingpackage androidx.car.widget;
18aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
19aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.ArrayList;
20aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.Arrays;
21aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.Collection;
22aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.Iterator;
23aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.List;
24aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingimport java.util.function.Predicate;
25aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
26aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding/**
27aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding * A helper class for building the list of buckets for alpha jump.
28aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding */
29aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Hardingpublic class AlphaJumpBucketer {
30aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    private static final Character[] DEFAULT_INITIAL_CHARS = {
31aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
32aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
33aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
34aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    private static final String[] PREFIX_WORDS = {"a", "the"};
35aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
36aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    private final List<Bucket> mBuckets;
37aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
38aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public AlphaJumpBucketer() {
39aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        mBuckets = new ArrayList<>();
40aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        for (Character ch : DEFAULT_INITIAL_CHARS) {
41aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            if (ch == '0') {
42aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                mBuckets.add(new Bucket("123", (String s) -> s.matches("^[0-9]")));
43aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            } else {
44aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                String prefix = new String(new char[] {ch});
45aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                mBuckets.add(new Bucket(prefix, (String s) -> s.startsWith(prefix.toLowerCase())));
46aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            }
47aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
48aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
49aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
50aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public AlphaJumpBucketer(Bucket[] buckets) {
51aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        mBuckets = Arrays.asList(buckets);
52aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
53aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
54aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    /**
55aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * Creates a collection of {@link IAlphaJumpAdapter.Bucket}s from the given list of strings.
56aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     */
57aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public Collection<IAlphaJumpAdapter.Bucket> createBuckets(String[] values) {
58aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        return createBuckets(Arrays.asList(values));
59aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
60aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
61aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    /**
62aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * Creates a collection of {@link IAlphaJumpAdapter.Bucket}s from the given iterable collection
63aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * of strings.
64aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    */
65aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public Collection<IAlphaJumpAdapter.Bucket> createBuckets(Iterable<String> values) {
66aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        return createBuckets(values.iterator());
67aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
68aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
69aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    /**
70aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * Creates the collection of {@link IAlphaJumpAdapter.Bucket}s from the given enumeration of
71aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * values.
72aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     */
73aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public Collection<IAlphaJumpAdapter.Bucket> createBuckets(Iterator<String> values) {
74aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        int index = 0;
75aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        while (values.hasNext()) {
76aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            String value = values.next();
77aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            for (Bucket bucket : mBuckets) {
78aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                if (bucket.matchString(value, index)) {
79aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                    break;
80aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                }
81aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            }
82aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            index++;
83aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
84aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        ArrayList<IAlphaJumpAdapter.Bucket> buckets = new ArrayList<>();
85aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        buckets.addAll(mBuckets);
86aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        return buckets;
87aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
88aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
89aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    /**
90aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * "Preprocess" a string so that we remove some common prefixes and so on when performing the
91aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * bucketing.
92aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     *
93aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * @param s The string to pre-process.
94aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * @return The input string with whitespace trimmed, and also words like "the", "a" and so on
95aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     *    removed.
96aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     */
97aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    private static String preprocess(String s) {
98aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        s = s.trim().toLowerCase();
99aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
100aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        for (String word : PREFIX_WORDS) {
101aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            if (s.startsWith(word + " ")) {
102aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                s = s.substring(0, word.length() + 1).trim();
103aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                break;
104aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            }
105aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
106aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
107aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        return s;
108aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
109aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
110aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    /**
111aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     * A basic implementation of {@link IAlphaJumpAdapter.Bucket}.
112aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding     */
113aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    public static class Bucket implements IAlphaJumpAdapter.Bucket {
114aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        private CharSequence mLabel;
115aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        private int mIndex;
116aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        private Predicate<String> mStringMatcher;
117aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        private boolean mIsEmpty;
118aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
119aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        Bucket(CharSequence label, Predicate<String> stringMatcher) {
120aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            mLabel = label;
121aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            mIndex = -1;
122aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            mIsEmpty = true;
123aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            mStringMatcher = stringMatcher;
124aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
125aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
126aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        boolean matchString(String s, int index) {
127aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            boolean match = mStringMatcher.test(preprocess(s));
128aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            if (match) {
129aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                if (mIndex < 0) {
130aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                    mIndex = index;
131aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                }
132aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding                mIsEmpty = false;
133aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            }
134aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            return match;
135aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
136aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
137aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        @Override
138aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        public boolean isEmpty() {
139aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            return mIsEmpty;
140aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
141aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
142aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        @Override
143aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        public CharSequence getLabel() {
144aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            return mLabel;
145aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
146aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding
147aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        @Override
148aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        public int getIndex() {
149aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding            return mIndex;
150aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding        }
151aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding    }
152aa1fd35cceebd3e32536cb11c30d07e28aabc9d1Dean Harding}
153