ImapString.java revision 7e5ba0e1eaee76ab6e6c7ea9362348f660796596
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
197e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport com.android.email.Email;
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() {
417e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public String getString() {
427e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return "";
437e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
447e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
457e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public InputStream getAsStream() {
467e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return new ByteArrayInputStream(EMPTY_BYTES);
477e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
487e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
497e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        @Override public String toString() {
507e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return "";
517e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
527e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    };
537e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
547e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // This is used only for parsing IMAP's FETCH ENVELOPE command, in which
557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // en_US-like date format is used like "01-Jan-2009 11:20:39 -0800", so this should be
567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // handled by Locale.US
577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private final static SimpleDateFormat DATE_TIME_FORMAT =
587e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private boolean mIsInteger;
617e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private int mParsedInteger;
627e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    private Date mParsedDate;
637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
647e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
657e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isList() {
667e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return false;
677e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
687e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isString() {
717e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return true;
727e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
747e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
757e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return true if and only if the length of the string is larger than 0.
767e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     *
777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * Note: IMAP NIL is considered an empty string. See {@link ImapResponseParser
787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * #parseBareString}.
797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * On the other hand, a quoted/literal string with value NIL (i.e. "NIL" and {3}\r\nNIL) is
807e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * treated literally.
817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
827e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isEmpty() {
837e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().length() == 0;
847e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
867e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract String getString();
877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
887e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract InputStream getAsStream();
897e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
907e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether it can be parsed as a number.
927e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
937e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isNumber() {
947e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (mIsInteger) {
957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
967e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
977e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        try {
987e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mParsedInteger = Integer.parseInt(getString());
997e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mIsInteger = true;
1007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        } catch (NumberFormatException e) {
1027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1057e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1067e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1077e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return value parsed as a number.
1087e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final int getNumberOrZero() {
1107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!isNumber()) {
1117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return 0;
1127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1137e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return mParsedInteger;
1147e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1157e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1167e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1177e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether it can be parsed as a date using {@link #DATE_TIME_FORMAT}.
1187e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1197e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean isDate() {
1207e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (mParsedDate != null) {
1217e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1227e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1237e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (isEmpty()) {
1247e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1257e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1267e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        try {
1277e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            mParsedDate = DATE_TIME_FORMAT.parse(getString());
1287e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return true;
1297e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        } catch (ParseException e) {
1307e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            Log.w(Email.LOG_TAG, getString() + " can't be parsed as a date.");
1317e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1327e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1337e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1347e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1357e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1367e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return value it can be parsed as a {@link Date}, or null otherwise.
1377e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1387e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final Date getDateOrNull() {
1397e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!isDate()) {
1407e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return null;
1417e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1427e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return mParsedDate;
1437e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1447e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1457e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1467e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether the value case-insensitively equals to {@code s}.
1477e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1487e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean is(String s) {
1497e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (s == null) {
1507e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1517e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1527e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().equalsIgnoreCase(s);
1537e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1547e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1557e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1567e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    /**
1577e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     * @return whether the value case-insensitively starts with {@code s}.
1587e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki     */
1597e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean startsWith(String prefix) {
1607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (prefix == null) {
1617e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1627e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1637e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        final String me = this.getString();
1647e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (me.length() < prefix.length()) {
1657e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1667e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1677e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return me.substring(0, prefix.length()).equalsIgnoreCase(prefix);
1687e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1697e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1707e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    // To force subclasses to implement it.
1717e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
1727e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public abstract String toString();
1737e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki
1747e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    @Override
1757e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    public final boolean equalsForTest(ImapElement that) {
1767e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        if (!super.equalsForTest(that)) {
1777e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki            return false;
1787e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        }
1797e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        ImapString thatString = (ImapString) that;
1807e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki        return getString().equals(thatString.getString());
1817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki    }
1827e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki}
183