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.email.mail.store.imap;
18
19import java.util.ArrayList;
20
21/**
22 * Class represents an IMAP list.
23 */
24public class ImapList extends ImapElement {
25    /**
26     * {@link ImapList} representing an empty list.
27     */
28    public static final ImapList EMPTY = new ImapList() {
29        @Override public void destroy() {
30            // Don't call super.destroy().
31            // It's a shared object.  We don't want the mDestroyed to be set on this.
32        }
33
34        @Override void add(ImapElement e) {
35            throw new RuntimeException();
36        }
37    };
38
39    private ArrayList<ImapElement> mList = new ArrayList<ImapElement>();
40
41    /* package */ void add(ImapElement e) {
42        if (e == null) {
43            throw new RuntimeException("Can't add null");
44        }
45        mList.add(e);
46    }
47
48    @Override
49    public final boolean isString() {
50        return false;
51    }
52
53    @Override
54    public final boolean isList() {
55        return true;
56    }
57
58    public final int size() {
59        return mList.size();
60    }
61
62    public final boolean isEmpty() {
63        return size() == 0;
64    }
65
66    /**
67     * Return true if the element at {@code index} exists, is string, and equals to {@code s}.
68     * (case insensitive)
69     */
70    public final boolean is(int index, String s) {
71        return is(index, s, false);
72    }
73
74    /**
75     * Same as {@link #is(int, String)}, but does the prefix match if {@code prefixMatch}.
76     */
77    public final boolean is(int index, String s, boolean prefixMatch) {
78        if (!prefixMatch) {
79            return getStringOrEmpty(index).is(s);
80        } else {
81            return getStringOrEmpty(index).startsWith(s);
82        }
83    }
84
85    /**
86     * Return the element at {@code index}.
87     * If {@code index} is out of range, returns {@link ImapElement#NONE}.
88     */
89    public final ImapElement getElementOrNone(int index) {
90        return (index >= mList.size()) ? ImapElement.NONE : mList.get(index);
91    }
92
93    /**
94     * Return the element at {@code index} if it's a list.
95     * If {@code index} is out of range or not a list, returns {@link ImapList#EMPTY}.
96     */
97    public final ImapList getListOrEmpty(int index) {
98        ImapElement el = getElementOrNone(index);
99        return el.isList() ? (ImapList) el : EMPTY;
100    }
101
102    /**
103     * Return the element at {@code index} if it's a string.
104     * If {@code index} is out of range or not a string, returns {@link ImapString#EMPTY}.
105     */
106    public final ImapString getStringOrEmpty(int index) {
107        ImapElement el = getElementOrNone(index);
108        return el.isString() ? (ImapString) el : ImapString.EMPTY;
109    }
110
111    /**
112     * Return an element keyed by {@code key}.  Return null if not found.  {@code key} has to be
113     * at an even index.
114     */
115    /* package */ final ImapElement getKeyedElementOrNull(String key, boolean prefixMatch) {
116        for (int i = 1; i < size(); i += 2) {
117            if (is(i-1, key, prefixMatch)) {
118                return mList.get(i);
119            }
120        }
121        return null;
122    }
123
124    /**
125     * Return an {@link ImapList} keyed by {@code key}.
126     * Return {@link ImapList#EMPTY} if not found.
127     */
128    public final ImapList getKeyedListOrEmpty(String key) {
129        return getKeyedListOrEmpty(key, false);
130    }
131
132    /**
133     * Return an {@link ImapList} keyed by {@code key}.
134     * Return {@link ImapList#EMPTY} if not found.
135     */
136    public final ImapList getKeyedListOrEmpty(String key, boolean prefixMatch) {
137        ImapElement e = getKeyedElementOrNull(key, prefixMatch);
138        return (e != null) ? ((ImapList) e) : ImapList.EMPTY;
139    }
140
141    /**
142     * Return an {@link ImapString} keyed by {@code key}.
143     * Return {@link ImapString#EMPTY} if not found.
144     */
145    public final ImapString getKeyedStringOrEmpty(String key) {
146        return getKeyedStringOrEmpty(key, false);
147    }
148
149    /**
150     * Return an {@link ImapString} keyed by {@code key}.
151     * Return {@link ImapString#EMPTY} if not found.
152     */
153    public final ImapString getKeyedStringOrEmpty(String key, boolean prefixMatch) {
154        ImapElement e = getKeyedElementOrNull(key, prefixMatch);
155        return (e != null) ? ((ImapString) e) : ImapString.EMPTY;
156    }
157
158    /**
159     * Return true if it contains {@code s}.
160     */
161    public final boolean contains(String s) {
162        for (int i = 0; i < size(); i++) {
163            if (getStringOrEmpty(i).is(s)) {
164                return true;
165            }
166        }
167        return false;
168    }
169
170    @Override
171    public void destroy() {
172        if (mList != null) {
173            for (ImapElement e : mList) {
174                e.destroy();
175            }
176            mList = null;
177        }
178        super.destroy();
179    }
180
181    @Override
182    public String toString() {
183        return mList.toString();
184    }
185
186    /**
187     * Return the text representations of the contents concatenated with ",".
188     */
189    public final String flatten() {
190        return flatten(new StringBuilder()).toString();
191    }
192
193    /**
194     * Returns text representations (i.e. getString()) of contents joined together with
195     * "," as the separator.
196     *
197     * Only used for building the capability string passed to vendor policies.
198     *
199     * We can't use toString(), because it's for debugging (meaning the format may change any time),
200     * and it won't expand literals.
201     */
202    private final StringBuilder flatten(StringBuilder sb) {
203        sb.append('[');
204        for (int i = 0; i < mList.size(); i++) {
205            if (i > 0) {
206                sb.append(',');
207            }
208            final ImapElement e = getElementOrNone(i);
209            if (e.isList()) {
210                getListOrEmpty(i).flatten(sb);
211            } else if (e.isString()) {
212                sb.append(getStringOrEmpty(i).getString());
213            }
214        }
215        sb.append(']');
216        return sb;
217    }
218
219    @Override
220    public boolean equalsForTest(ImapElement that) {
221        if (!super.equalsForTest(that)) {
222            return false;
223        }
224        ImapList thatList = (ImapList) that;
225        if (size() != thatList.size()) {
226            return false;
227        }
228        for (int i = 0; i < size(); i++) {
229            if (!mList.get(i).equalsForTest(thatList.getElementOrNone(i))) {
230                return false;
231            }
232        }
233        return true;
234    }
235}
236