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 
17 package com.android.email.mail.store.imap;
18 
19 import java.util.ArrayList;
20 
21 /**
22  * Class represents an IMAP list.
23  */
24 public 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 
add(ImapElement e)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
isString()49     public final boolean isString() {
50         return false;
51     }
52 
53     @Override
isList()54     public final boolean isList() {
55         return true;
56     }
57 
size()58     public final int size() {
59         return mList.size();
60     }
61 
isEmpty()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      */
is(int index, String s)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      */
is(int index, String s, boolean prefixMatch)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      */
getElementOrNone(int index)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      */
getListOrEmpty(int index)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      */
getStringOrEmpty(int index)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      */
getKeyedElementOrNull(String key, boolean prefixMatch)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      */
getKeyedListOrEmpty(String key)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      */
getKeyedListOrEmpty(String key, boolean prefixMatch)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      */
getKeyedStringOrEmpty(String key)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      */
getKeyedStringOrEmpty(String key, boolean prefixMatch)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      */
contains(String s)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
destroy()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
toString()182     public String toString() {
183         return mList.toString();
184     }
185 
186     /**
187      * Return the text representations of the contents concatenated with ",".
188      */
flatten()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      */
flatten(StringBuilder sb)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
equalsForTest(ImapElement that)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