17e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki/*
27e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * Copyright (C) 2010 The Android Open Source Project
37e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki *
47e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
57e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * you may not use this file except in compliance with the License.
67e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * You may obtain a copy of the License at
77e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki *
87e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
97e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki *
107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * Unless required by applicable law or agreed to in writing, software
117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * See the License for the specific language governing permissions and
147e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * limitations under the License.
157e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki */
167e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
177e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukipackage com.android.email.mail.store.imap;
187e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
207e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
217e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport android.util.Log;
227e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
237e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream;
247e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.InputStream;
257e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.text.ParseException;
267e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.text.SimpleDateFormat;
277e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.util.Date;
287e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.util.Locale;
297e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
307e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki/**
317e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * Class represents an IMAP "element" that is not a list.
327e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki *
337e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * An atom, quoted string, literal, are all represented by this.  Values like OK, STATUS are too.
347e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * Also, this class class may contain more arbitrary value like "BODY[HEADER.FIELDS ("DATE")]".
357e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki * See {@link ImapResponseParser}.
367e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki */
377e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukipublic abstract class ImapString extends ImapElement {
387e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private static final byte[] EMPTY_BYTES = new byte[0];
397e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
407e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public static final ImapString EMPTY = new ImapString() {
410fd444b0b4c07ac1545e0edc64b54fd738edfc94Makoto Onuki        @Override public void destroy() {
420fd444b0b4c07ac1545e0edc64b54fd738edfc94Makoto Onuki            // Don't call super.destroy().
430fd444b0b4c07ac1545e0edc64b54fd738edfc94Makoto Onuki            // It's a shared object.  We don't want the mDestroyed to be set on this.
440fd444b0b4c07ac1545e0edc64b54fd738edfc94Makoto Onuki        }
450fd444b0b4c07ac1545e0edc64b54fd738edfc94Makoto Onuki
467e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public String getString() {
477e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return "";
487e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
497e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
507e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public InputStream getAsStream() {
517e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return new ByteArrayInputStream(EMPTY_BYTES);
527e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
537e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
547e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public String toString() {
557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return "";
567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    };
587e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
617e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // handled by Locale.US
627e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private final static SimpleDateFormat DATE_TIME_FORMAT =
637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
647e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
657e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private boolean mIsInteger;
667e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private int mParsedInteger;
677e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private Date mParsedDate;
687e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isList() {
717e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return false;
727e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
747e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
757e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isString() {
767e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return true;
777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
807e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return true if and only if the length of the string is larger than 0.
817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     *
827e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
837e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * #parseBareString}.
847e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * treated literally.
867e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isEmpty() {
887e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().length() == 0;
897e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
907e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract String getString();
927e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
937e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract InputStream getAsStream();
947e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
967e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether it can be parsed as a number.
977e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
987e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isNumber() {
997e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (mIsInteger) {
1007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        try {
1037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mParsedInteger = Integer.parseInt(getString());
1047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mIsInteger = true;
1057e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1067e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        } catch (NumberFormatException e) {
1077e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1087e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return value parsed as a number.
1137e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1147e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final int getNumberOrZero() {
1157e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!isNumber()) {
1167e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return 0;
1177e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1187e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return mParsedInteger;
1197e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1207e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1217e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1227e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
1237e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1247e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isDate() {
1257e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (mParsedDate != null) {
1267e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1277e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1287e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (isEmpty()) {
1297e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1307e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1317e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        try {
1327e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mParsedDate = DATE_TIME_FORMAT.parse(getString());
1337e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1347e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        } catch (ParseException e) {
13531d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.w(Logging.LOG_TAG, getString() + " can't be parsed as a date.");
1367e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1377e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1387e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1397e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1407e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1417e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return value it can be parsed as a {@link Date}, or null otherwise.
1427e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1437e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final Date getDateOrNull() {
1447e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!isDate()) {
1457e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return null;
1467e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1477e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return mParsedDate;
1487e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1497e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1507e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1517e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether the value case-insensitively equals to {@code s}.
1527e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1537e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean is(String s) {
1547e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (s == null) {
1557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().equalsIgnoreCase(s);
1587e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1617e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1627e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether the value case-insensitively starts with {@code s}.
1637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1647e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean startsWith(String prefix) {
1657e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (prefix == null) {
1667e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1677e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1687e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final String me = this.getString();
1697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (me.length() < prefix.length()) {
1707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1717e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1727e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
1737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1747e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1757e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // To force subclasses to implement it.
1767e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
1777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract String toString();
1787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
1807e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean equalsForTest(ImapElement that) {
1817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!super.equalsForTest(that)) {
1827e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1837e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1847e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        ImapString thatString = (ImapString) that;
1857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().equals(thatString.getString());
1867e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki}
188