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