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