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;
205c523858385176c33a7456bb84035de78552d22dMarc Blank
215c523858385176c33a7456bb84035de78552d22dMarc Blankimport android.util.Log;
225c523858385176c33a7456bb84035de78552d22dMarc Blank
235c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.ByteArrayInputStream;
245c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.io.InputStream;
255c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.text.ParseException;
265c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.text.SimpleDateFormat;
275c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Date;
285c523858385176c33a7456bb84035de78552d22dMarc Blankimport java.util.Locale;
295c523858385176c33a7456bb84035de78552d22dMarc Blank
305c523858385176c33a7456bb84035de78552d22dMarc Blank/**
315c523858385176c33a7456bb84035de78552d22dMarc Blank * Class represents an IMAP "element" that is not a list.
325c523858385176c33a7456bb84035de78552d22dMarc Blank *
335c523858385176c33a7456bb84035de78552d22dMarc Blank * An atom, quoted string, literal, are all represented by this.  Values like OK, STATUS are too.
345c523858385176c33a7456bb84035de78552d22dMarc Blank * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
355c523858385176c33a7456bb84035de78552d22dMarc Blank * See {@link ImapResponseParser}.
365c523858385176c33a7456bb84035de78552d22dMarc Blank */
375c523858385176c33a7456bb84035de78552d22dMarc Blankpublic abstract class ImapString extends ImapElement {
385c523858385176c33a7456bb84035de78552d22dMarc Blank    private static final byte[] EMPTY_BYTES = new byte[0];
395c523858385176c33a7456bb84035de78552d22dMarc Blank
405c523858385176c33a7456bb84035de78552d22dMarc Blank    public static final ImapString EMPTY = new ImapString() {
415c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public void destroy() {
425c523858385176c33a7456bb84035de78552d22dMarc Blank            // Don't call super.destroy().
435c523858385176c33a7456bb84035de78552d22dMarc Blank            // It's a shared object.  We don't want the mDestroyed to be set on this.
445c523858385176c33a7456bb84035de78552d22dMarc Blank        }
455c523858385176c33a7456bb84035de78552d22dMarc Blank
465c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public String getString() {
475c523858385176c33a7456bb84035de78552d22dMarc Blank            return "";
485c523858385176c33a7456bb84035de78552d22dMarc Blank        }
495c523858385176c33a7456bb84035de78552d22dMarc Blank
505c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public InputStream getAsStream() {
515c523858385176c33a7456bb84035de78552d22dMarc Blank            return new ByteArrayInputStream(EMPTY_BYTES);
525c523858385176c33a7456bb84035de78552d22dMarc Blank        }
535c523858385176c33a7456bb84035de78552d22dMarc Blank
545c523858385176c33a7456bb84035de78552d22dMarc Blank        @Override public String toString() {
555c523858385176c33a7456bb84035de78552d22dMarc Blank            return "";
565c523858385176c33a7456bb84035de78552d22dMarc Blank        }
575c523858385176c33a7456bb84035de78552d22dMarc Blank    };
585c523858385176c33a7456bb84035de78552d22dMarc Blank
595c523858385176c33a7456bb84035de78552d22dMarc Blank    // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
605c523858385176c33a7456bb84035de78552d22dMarc Blank    // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
615c523858385176c33a7456bb84035de78552d22dMarc Blank    // handled by Locale.US
625c523858385176c33a7456bb84035de78552d22dMarc Blank    private final static SimpleDateFormat DATE_TIME_FORMAT =
635c523858385176c33a7456bb84035de78552d22dMarc Blank            new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
645c523858385176c33a7456bb84035de78552d22dMarc Blank
655c523858385176c33a7456bb84035de78552d22dMarc Blank    private boolean mIsInteger;
665c523858385176c33a7456bb84035de78552d22dMarc Blank    private int mParsedInteger;
675c523858385176c33a7456bb84035de78552d22dMarc Blank    private Date mParsedDate;
685c523858385176c33a7456bb84035de78552d22dMarc Blank
695c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
705c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isList() {
715c523858385176c33a7456bb84035de78552d22dMarc Blank        return false;
725c523858385176c33a7456bb84035de78552d22dMarc Blank    }
735c523858385176c33a7456bb84035de78552d22dMarc Blank
745c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
755c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isString() {
765c523858385176c33a7456bb84035de78552d22dMarc Blank        return true;
775c523858385176c33a7456bb84035de78552d22dMarc Blank    }
785c523858385176c33a7456bb84035de78552d22dMarc Blank
795c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
805c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return true if and only if the length of the string is larger than 0.
815c523858385176c33a7456bb84035de78552d22dMarc Blank     *
825c523858385176c33a7456bb84035de78552d22dMarc Blank     * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
835c523858385176c33a7456bb84035de78552d22dMarc Blank     * #parseBareString}.
845c523858385176c33a7456bb84035de78552d22dMarc Blank     * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
855c523858385176c33a7456bb84035de78552d22dMarc Blank     * treated literally.
865c523858385176c33a7456bb84035de78552d22dMarc Blank     */
875c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isEmpty() {
885c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().length() == 0;
895c523858385176c33a7456bb84035de78552d22dMarc Blank    }
905c523858385176c33a7456bb84035de78552d22dMarc Blank
915c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract String getString();
925c523858385176c33a7456bb84035de78552d22dMarc Blank
935c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract InputStream getAsStream();
945c523858385176c33a7456bb84035de78552d22dMarc Blank
955c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
965c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether it can be parsed as a number.
975c523858385176c33a7456bb84035de78552d22dMarc Blank     */
985c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isNumber() {
995c523858385176c33a7456bb84035de78552d22dMarc Blank        if (mIsInteger) {
1005c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1015c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1025c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
1035c523858385176c33a7456bb84035de78552d22dMarc Blank            mParsedInteger = Integer.parseInt(getString());
1045c523858385176c33a7456bb84035de78552d22dMarc Blank            mIsInteger = true;
1055c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1065c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (NumberFormatException e) {
1075c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1085c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1095c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1105c523858385176c33a7456bb84035de78552d22dMarc Blank
1115c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1125c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return value parsed as a number.
1135c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1145c523858385176c33a7456bb84035de78552d22dMarc Blank    public final int getNumberOrZero() {
1155c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!isNumber()) {
1165c523858385176c33a7456bb84035de78552d22dMarc Blank            return 0;
1175c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1185c523858385176c33a7456bb84035de78552d22dMarc Blank        return mParsedInteger;
1195c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1205c523858385176c33a7456bb84035de78552d22dMarc Blank
1215c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1225c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
1235c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1245c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean isDate() {
1255c523858385176c33a7456bb84035de78552d22dMarc Blank        if (mParsedDate != null) {
1265c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1275c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1285c523858385176c33a7456bb84035de78552d22dMarc Blank        if (isEmpty()) {
1295c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1305c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1315c523858385176c33a7456bb84035de78552d22dMarc Blank        try {
1325c523858385176c33a7456bb84035de78552d22dMarc Blank            mParsedDate = DATE_TIME_FORMAT.parse(getString());
1335c523858385176c33a7456bb84035de78552d22dMarc Blank            return true;
1345c523858385176c33a7456bb84035de78552d22dMarc Blank        } catch (ParseException e) {
1355c523858385176c33a7456bb84035de78552d22dMarc Blank            Log.w(Logging.LOG_TAG, getString() + " can't be parsed as a date.");
1365c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1375c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1385c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1395c523858385176c33a7456bb84035de78552d22dMarc Blank
1405c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1415c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return value it can be parsed as a {@link Date}, or null otherwise.
1425c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1435c523858385176c33a7456bb84035de78552d22dMarc Blank    public final Date getDateOrNull() {
1445c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!isDate()) {
1455c523858385176c33a7456bb84035de78552d22dMarc Blank            return null;
1465c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1475c523858385176c33a7456bb84035de78552d22dMarc Blank        return mParsedDate;
1485c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1495c523858385176c33a7456bb84035de78552d22dMarc Blank
1505c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1515c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether the value case-insensitively equals to {@code s}.
1525c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1535c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean is(String s) {
1545c523858385176c33a7456bb84035de78552d22dMarc Blank        if (s == null) {
1555c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1565c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1575c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().equalsIgnoreCase(s);
1585c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1595c523858385176c33a7456bb84035de78552d22dMarc Blank
1605c523858385176c33a7456bb84035de78552d22dMarc Blank
1615c523858385176c33a7456bb84035de78552d22dMarc Blank    /**
1625c523858385176c33a7456bb84035de78552d22dMarc Blank     * @return whether the value case-insensitively starts with {@code s}.
1635c523858385176c33a7456bb84035de78552d22dMarc Blank     */
1645c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean startsWith(String prefix) {
1655c523858385176c33a7456bb84035de78552d22dMarc Blank        if (prefix == null) {
1665c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1675c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1685c523858385176c33a7456bb84035de78552d22dMarc Blank        final String me = this.getString();
1695c523858385176c33a7456bb84035de78552d22dMarc Blank        if (me.length() < prefix.length()) {
1705c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1715c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1725c523858385176c33a7456bb84035de78552d22dMarc Blank        return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
1735c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1745c523858385176c33a7456bb84035de78552d22dMarc Blank
1755c523858385176c33a7456bb84035de78552d22dMarc Blank    // To force subclasses to implement it.
1765c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
1775c523858385176c33a7456bb84035de78552d22dMarc Blank    public abstract String toString();
1785c523858385176c33a7456bb84035de78552d22dMarc Blank
1795c523858385176c33a7456bb84035de78552d22dMarc Blank    @Override
1805c523858385176c33a7456bb84035de78552d22dMarc Blank    public final boolean equalsForTest(ImapElement that) {
1815c523858385176c33a7456bb84035de78552d22dMarc Blank        if (!super.equalsForTest(that)) {
1825c523858385176c33a7456bb84035de78552d22dMarc Blank            return false;
1835c523858385176c33a7456bb84035de78552d22dMarc Blank        }
1845c523858385176c33a7456bb84035de78552d22dMarc Blank        ImapString thatString = (ImapString) that;
1855c523858385176c33a7456bb84035de78552d22dMarc Blank        return getString().equals(thatString.getString());
1865c523858385176c33a7456bb84035de78552d22dMarc Blank    }
1875c523858385176c33a7456bb84035de78552d22dMarc Blank}
188