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 com.android.voicemail.impl.VvmLog;
20 import java.io.ByteArrayInputStream;
21 import java.io.InputStream;
22 import java.text.ParseException;
23 import java.text.SimpleDateFormat;
24 import java.util.Date;
25 import java.util.Locale;
26 
27 /**
28  * Class represents an IMAP "element" that is not a list.
29  *
30  * <p>An atom, quoted string, literal, are all represented by this. Values like OK, STATUS are too.
31  * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]". See
32  * {@link ImapResponseParser}.
33  */
34 public abstract class ImapString extends ImapElement {
35   private static final byte[] EMPTY_BYTES = new byte[0];
36 
37   public static final ImapString EMPTY =
38       new ImapString() {
39         @Override
40         public void destroy() {
41           // Don't call super.destroy().
42           // It's a shared object.  We don't want the mDestroyed to be set on this.
43         }
44 
45         @Override
46         public String getString() {
47           return "";
48         }
49 
50         @Override
51         public InputStream getAsStream() {
52           return new ByteArrayInputStream(EMPTY_BYTES);
53         }
54 
55         @Override
56         public String toString() {
57           return "";
58         }
59       };
60 
61   // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
62   // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
63   // handled by Locale.US
64   private static final SimpleDateFormat DATE_TIME_FORMAT =
65       new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
66 
67   private boolean isInteger;
68   private int parsedInteger;
69   private Date parsedDate;
70 
71   @Override
isList()72   public final boolean isList() {
73     return false;
74   }
75 
76   @Override
isString()77   public final boolean isString() {
78     return true;
79   }
80 
81   /**
82    * @return true if and only if the length of the string is larger than 0.
83    *     <p>Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
84    *     #parseBareString}. On the other hand, a quoted/literal string with value NIL (i.e. "NIL"
85    *     and {3}\r\nNIL) is treated literally.
86    */
isEmpty()87   public final boolean isEmpty() {
88     return getString().length() == 0;
89   }
90 
getString()91   public abstract String getString();
92 
getAsStream()93   public abstract InputStream getAsStream();
94 
95   /** @return whether it can be parsed as a number. */
isNumber()96   public final boolean isNumber() {
97     if (isInteger) {
98       return true;
99     }
100     try {
101       parsedInteger = Integer.parseInt(getString());
102       isInteger = true;
103       return true;
104     } catch (NumberFormatException e) {
105       return false;
106     }
107   }
108 
109   /** @return value parsed as a number, or 0 if the string is not a number. */
getNumberOrZero()110   public final int getNumberOrZero() {
111     return getNumber(0);
112   }
113 
114   /** @return value parsed as a number, or {@code defaultValue} if the string is not a number. */
getNumber(int defaultValue)115   public final int getNumber(int defaultValue) {
116     if (!isNumber()) {
117       return defaultValue;
118     }
119     return parsedInteger;
120   }
121 
122   /** @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}. */
isDate()123   public final boolean isDate() {
124     if (parsedDate != null) {
125       return true;
126     }
127     if (isEmpty()) {
128       return false;
129     }
130     try {
131       parsedDate = DATE_TIME_FORMAT.parse(getString());
132       return true;
133     } catch (ParseException e) {
134       VvmLog.w("ImapString", getString() + " can't be parsed as a date.");
135       return false;
136     }
137   }
138 
139   /** @return value it can be parsed as a {@link Date}, or null otherwise. */
getDateOrNull()140   public final Date getDateOrNull() {
141     if (!isDate()) {
142       return null;
143     }
144     return parsedDate;
145   }
146 
147   /** @return whether the value case-insensitively equals to {@code s}. */
is(String s)148   public final boolean is(String s) {
149     if (s == null) {
150       return false;
151     }
152     return getString().equalsIgnoreCase(s);
153   }
154 
155   /** @return whether the value case-insensitively starts with {@code s}. */
startsWith(String prefix)156   public final boolean startsWith(String prefix) {
157     if (prefix == null) {
158       return false;
159     }
160     final String me = this.getString();
161     if (me.length() < prefix.length()) {
162       return false;
163     }
164     return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
165   }
166 
167   // To force subclasses to implement it.
168   @Override
toString()169   public abstract String toString();
170 
171   @Override
equalsForTest(ImapElement that)172   public final boolean equalsForTest(ImapElement that) {
173     if (!super.equalsForTest(that)) {
174       return false;
175     }
176     ImapString thatString = (ImapString) that;
177     return getString().equals(thatString.getString());
178   }
179 }
180