15c523858385176c33a7456bb84035de78552d22dMarc Blank/*
25c523858385176c33a7456bb84035de78552d22dMarc Blank * Copyright (C) 2010 The Android Open Source Project
35c523858385176c33a7456bb84035de78552d22dMarc Blank *
45c523858385176c33a7456bb84035de78552d22dMarc Blank * Licensed under the Apache License, Version 2.0 (the "License");
55c523858385176c33a7456bb84035de78552d22dMarc Blank * you may not use this file except in compliance with the License.
65c523858385176c33a7456bb84035de78552d22dMarc Blank * You may obtain a copy of the License at
75c523858385176c33a7456bb84035de78552d22dMarc Blank *
85c523858385176c33a7456bb84035de78552d22dMarc Blank *      http://www.apache.org/licenses/LICENSE-2.0
95c523858385176c33a7456bb84035de78552d22dMarc Blank *
105c523858385176c33a7456bb84035de78552d22dMarc Blank * Unless required by applicable law or agreed to in writing, software
115c523858385176c33a7456bb84035de78552d22dMarc Blank * distributed under the License is distributed on an "AS IS" BASIS,
125c523858385176c33a7456bb84035de78552d22dMarc Blank * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135c523858385176c33a7456bb84035de78552d22dMarc Blank * See the License for the specific language governing permissions and
145c523858385176c33a7456bb84035de78552d22dMarc Blank * limitations under the License.
155c523858385176c33a7456bb84035de78552d22dMarc Blank */
165c523858385176c33a7456bb84035de78552d22dMarc Blank
175c523858385176c33a7456bb84035de78552d22dMarc Blankpackage com.android.email.mail.store.imap;
185c523858385176c33a7456bb84035de78552d22dMarc Blank
195c523858385176c33a7456bb84035de78552d22dMarc Blankimport com.android.emailcommon.Logging;
20560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
215c523858385176c33a7456bb84035de78552d22dMarc Blank
225c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.ByteArrayInputStream;
235c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.InputStream;
245c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.text.ParseException;
255c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.text.SimpleDateFormat;
265c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Date;
275c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Locale;
285c523858385176c33a7456bb84035de78552d22dMarc Blank
295c523858385176c33a7456bb84035de78552d22dMarc Blank/**
305c523858385176c33a7456bb84035de78552d22dMarc Blank * Class represents an IMAP "element" that is not a list.
315c523858385176c33a7456bb84035de78552d22dMarc Blank *
325c523858385176c33a7456bb84035de78552d22dMarc Blank * An atom, quoted string, literal, are all represented by this.  Values like OK, STATUS are too.
335c523858385176c33a7456bb84035de78552d22dMarc Blank * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
345c523858385176c33a7456bb84035de78552d22dMarc Blank * See {@link ImapResponseParser}.
355c523858385176c33a7456bb84035de78552d22dMarc Blank */
365c523858385176c33a7456bb84035de78552d22dMarc Blankpublic abstract class ImapString extends ImapElement {
375c523858385176c33a7456bb84035de78552d22dMarc Blank    private static final byte[] EMPTY_BYTES = new byte[0];
385c523858385176c33a7456bb84035de78552d22dMarc Blank
395c523858385176c33a7456bb84035de78552d22dMarc Blank    public static final ImapString EMPTY = new ImapString() {
405c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public void destroy() {
415c523858385176c33a7456bb84035de78552d22dMarc Blank            // Don't call super.destroy().
425c523858385176c33a7456bb84035de78552d22dMarc Blank            // It's a shared object.  We don't want the mDestroyed to be set on this.
435c523858385176c33a7456bb84035de78552d22dMarc Blank        }
445c523858385176c33a7456bb84035de78552d22dMarc Blank
455c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public String getString() {
465c523858385176c33a7456bb84035de78552d22dMarc Blank            return "";
475c523858385176c33a7456bb84035de78552d22dMarc Blank        }
485c523858385176c33a7456bb84035de78552d22dMarc Blank
495c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public InputStream getAsStream() {
505c523858385176c33a7456bb84035de78552d22dMarc Blank            return new ByteArrayInputStream(EMPTY_BYTES);
515c523858385176c33a7456bb84035de78552d22dMarc Blank        }
525c523858385176c33a7456bb84035de78552d22dMarc Blank
535c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public String toString() {
545c523858385176c33a7456bb84035de78552d22dMarc Blank            return "";
555c523858385176c33a7456bb84035de78552d22dMarc Blank        }
565c523858385176c33a7456bb84035de78552d22dMarc Blank    };
575c523858385176c33a7456bb84035de78552d22dMarc Blank
585c523858385176c33a7456bb84035de78552d22dMarc Blank    // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
595c523858385176c33a7456bb84035de78552d22dMarc Blank    // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
605c523858385176c33a7456bb84035de78552d22dMarc Blank    // handled by Locale.US
615c523858385176c33a7456bb84035de78552d22dMarc Blank    private final static SimpleDateFormat DATE_TIME_FORMAT =
625c523858385176c33a7456bb84035de78552d22dMarc Blank            new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
635c523858385176c33a7456bb84035de78552d22dMarc Blank
645c523858385176c33a7456bb84035de78552d22dMarc Blank    private boolean mIsInteger;
655c523858385176c33a7456bb84035de78552d22dMarc Blank    private int mParsedInteger;
665c523858385176c33a7456bb84035de78552d22dMarc Blank    private Date mParsedDate;
675c523858385176c33a7456bb84035de78552d22dMarc Blank
685c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
695c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isList() {
705c523858385176c33a7456bb84035de78552d22dMarc Blank        return false;
715c523858385176c33a7456bb84035de78552d22dMarc Blank    }
725c523858385176c33a7456bb84035de78552d22dMarc Blank
735c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
745c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isString() {
755c523858385176c33a7456bb84035de78552d22dMarc Blank        return true;
765c523858385176c33a7456bb84035de78552d22dMarc Blank    }
775c523858385176c33a7456bb84035de78552d22dMarc Blank
785c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
795c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return true if and only if the length of the string is larger than 0.
805c523858385176c33a7456bb84035de78552d22dMarc Blank     *
815c523858385176c33a7456bb84035de78552d22dMarc Blank     * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
825c523858385176c33a7456bb84035de78552d22dMarc Blank     * #parseBareString}.
835c523858385176c33a7456bb84035de78552d22dMarc Blank     * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
845c523858385176c33a7456bb84035de78552d22dMarc Blank     * treated literally.
855c523858385176c33a7456bb84035de78552d22dMarc Blank     */
865c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isEmpty() {
875c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().length() == 0;
885c523858385176c33a7456bb84035de78552d22dMarc Blank    }
895c523858385176c33a7456bb84035de78552d22dMarc Blank
905c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract String getString();
915c523858385176c33a7456bb84035de78552d22dMarc Blank
925c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract InputStream getAsStream();
935c523858385176c33a7456bb84035de78552d22dMarc Blank
945c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
955c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether it can be parsed as a number.
965c523858385176c33a7456bb84035de78552d22dMarc Blank     */
975c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isNumber() {
985c523858385176c33a7456bb84035de78552d22dMarc Blank        if (mIsInteger) {
995c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1005c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1015c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
1025c523858385176c33a7456bb84035de78552d22dMarc Blank            mParsedInteger = Integer.parseInt(getString());
1035c523858385176c33a7456bb84035de78552d22dMarc Blank            mIsInteger = true;
1045c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1055c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (NumberFormatException e) {
1065c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1075c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1085c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1095c523858385176c33a7456bb84035de78552d22dMarc Blank
1105c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1115c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return value parsed as a number.
1125c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1135c523858385176c33a7456bb84035de78552d22dMarc Blank    public final int getNumberOrZero() {
1145c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!isNumber()) {
1155c523858385176c33a7456bb84035de78552d22dMarc Blank            return 0;
1165c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1175c523858385176c33a7456bb84035de78552d22dMarc Blank        return mParsedInteger;
1185c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1195c523858385176c33a7456bb84035de78552d22dMarc Blank
1205c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1215c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
1225c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1235c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isDate() {
1245c523858385176c33a7456bb84035de78552d22dMarc Blank        if (mParsedDate != null) {
1255c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1265c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1275c523858385176c33a7456bb84035de78552d22dMarc Blank        if (isEmpty()) {
1285c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1295c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1305c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
1315c523858385176c33a7456bb84035de78552d22dMarc Blank            mParsedDate = DATE_TIME_FORMAT.parse(getString());
1325c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1335c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (ParseException e) {
134560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.w(Logging.LOG_TAG, getString() + " can't be parsed as a date.");
1355c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1365c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1375c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1385c523858385176c33a7456bb84035de78552d22dMarc Blank
1395c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1405c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return value it can be parsed as a {@link Date}, or null otherwise.
1415c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1425c523858385176c33a7456bb84035de78552d22dMarc Blank    public final Date getDateOrNull() {
1435c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!isDate()) {
1445c523858385176c33a7456bb84035de78552d22dMarc Blank            return null;
1455c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1465c523858385176c33a7456bb84035de78552d22dMarc Blank        return mParsedDate;
1475c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1485c523858385176c33a7456bb84035de78552d22dMarc Blank
1495c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1505c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether the value case-insensitively equals to {@code s}.
1515c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1525c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean is(String s) {
1535c523858385176c33a7456bb84035de78552d22dMarc Blank        if (s == null) {
1545c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1555c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1565c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().equalsIgnoreCase(s);
1575c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1585c523858385176c33a7456bb84035de78552d22dMarc Blank
1595c523858385176c33a7456bb84035de78552d22dMarc Blank
1605c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1615c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether the value case-insensitively starts with {@code s}.
1625c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1635c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean startsWith(String prefix) {
1645c523858385176c33a7456bb84035de78552d22dMarc Blank        if (prefix == null) {
1655c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1665c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1675c523858385176c33a7456bb84035de78552d22dMarc Blank        final String me = this.getString();
1685c523858385176c33a7456bb84035de78552d22dMarc Blank        if (me.length() < prefix.length()) {
1695c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1705c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1715c523858385176c33a7456bb84035de78552d22dMarc Blank        return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
1725c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1735c523858385176c33a7456bb84035de78552d22dMarc Blank
1745c523858385176c33a7456bb84035de78552d22dMarc Blank    // To force subclasses to implement it.
1755c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
1765c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract String toString();
1775c523858385176c33a7456bb84035de78552d22dMarc Blank
1785c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
1795c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean equalsForTest(ImapElement that) {
1805c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!super.equalsForTest(that)) {
1815c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1825c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1835c523858385176c33a7456bb84035de78552d22dMarc Blank        ImapString thatString = (ImapString) that;
1845c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().equals(thatString.getString());
1855c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1865c523858385176c33a7456bb84035de78552d22dMarc Blank}
187