001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package javax.mail;
021    
022    import java.io.Serializable;
023    import java.util.Hashtable;
024    import java.util.Iterator;
025    import java.util.LinkedList;
026    import java.util.List;
027    
028    /**
029     * Representation of flags that may be associated with a message.
030     * Flags can either be system flags, defined by the {@link Flags.Flag Flag} inner class,
031     * or user-defined flags defined by a String. The system flags represent those expected
032     * to be provided by most folder systems; user-defined flags allow for additional flags
033     * on a per-provider basis.
034     * <p/>
035     * This class is Serializable but compatibility is not guaranteed across releases.
036     *
037     * @version $Rev: 920714 $ $Date: 2010-03-09 01:55:49 -0500 (Tue, 09 Mar 2010) $
038     */
039    public class Flags implements Cloneable, Serializable {
040    
041        private static final long serialVersionUID = 6243590407214169028L;
042    
043        public static final class Flag {
044            /**
045             * Flag that indicates that the message has been replied to; has a bit value of 1.
046             */
047            public static final Flag ANSWERED = new Flag(1);
048            /**
049             * Flag that indicates that the message has been marked for deletion and
050             * should be removed on a subsequent expunge operation; has a bit value of 2.
051             */
052            public static final Flag DELETED = new Flag(2);
053            /**
054             * Flag that indicates that the message is a draft; has a bit value of 4.
055             */
056            public static final Flag DRAFT = new Flag(4);
057            /**
058             * Flag that indicates that the message has been flagged; has a bit value of 8.
059             */
060            public static final Flag FLAGGED = new Flag(8);
061            /**
062             * Flag that indicates that the message has been delivered since the last time
063             * this folder was opened; has a bit value of 16.
064             */
065            public static final Flag RECENT = new Flag(16);
066            /**
067             * Flag that indicates that the message has been viewed; has a bit value of 32.
068             * This flag is set by the {@link Message#getInputStream()} and {@link Message#getContent()}
069             * methods.
070             */
071            public static final Flag SEEN = new Flag(32);
072            /**
073             * Flags that indicates if this folder supports user-defined flags; has a bit value of 0x80000000.
074             */
075            public static final Flag USER = new Flag(0x80000000);
076    
077            private final int mask;
078    
079            private Flag(int mask) {
080                this.mask = mask;
081            }
082        }
083    
084        // the Serialized form of this class required the following two fields to be persisted
085        // this leads to a specific type of implementation
086        private int system_flags;
087        private final Hashtable user_flags;
088    
089        /**
090         * Construct a Flags instance with no flags set.
091         */
092        public Flags() {
093            user_flags = new Hashtable();
094        }
095    
096        /**
097         * Construct a Flags instance with a supplied system flag set.
098         * @param flag the system flag to set
099         */
100        public Flags(Flag flag) {
101            system_flags = flag.mask;
102            user_flags = new Hashtable();
103        }
104    
105        /**
106         * Construct a Flags instance with a same flags set.
107         * @param flags the instance to copy
108         */
109        public Flags(Flags flags) {
110            system_flags = flags.system_flags;
111            user_flags = new Hashtable(flags.user_flags);
112        }
113    
114        /**
115         * Construct a Flags instance with the supplied user flags set.
116         * Question: should this automatically set the USER system flag?
117         * @param name the user flag to set
118         */
119        public Flags(String name) {
120            user_flags = new Hashtable();
121            user_flags.put(name.toLowerCase(), name);
122        }
123    
124        /**
125         * Set a system flag.
126         * @param flag the system flag to set
127         */
128        public void add(Flag flag) {
129            system_flags |= flag.mask;
130        }
131    
132        /**
133         * Set all system and user flags from the supplied Flags.
134         * Question: do we need to check compatibility of USER flags?
135         * @param flags the Flags to add
136         */
137        public void add(Flags flags) {
138            system_flags |= flags.system_flags;
139            user_flags.putAll(flags.user_flags);
140        }
141    
142        /**
143         * Set a user flag.
144         * Question: should this fail if the USER system flag is not set?
145         * @param name the user flag to set
146         */
147        public void add(String name) {
148            user_flags.put(name.toLowerCase(), name);
149        }
150    
151        /**
152         * Return a copy of this instance.
153         * @return a copy of this instance
154         */
155        public Object clone() {
156            return new Flags(this);
157        }
158    
159        /**
160         * See if the supplied system flags are set
161         * @param flag the system flags to check for
162         * @return true if the flags are set
163         */
164        public boolean contains(Flag flag) {
165            return (system_flags & flag.mask) != 0;
166        }
167    
168        /**
169         * See if all of the supplied Flags are set
170         * @param flags the flags to check for
171         * @return true if all the supplied system and user flags are set
172         */
173        public boolean contains(Flags flags) {
174            return ((system_flags & flags.system_flags) == flags.system_flags)
175                    && user_flags.keySet().containsAll(flags.user_flags.keySet());
176        }
177    
178        /**
179         * See if the supplied user flag is set
180         * @param name the user flag to check for
181         * @return true if the flag is set
182         */
183        public boolean contains(String name) {
184            return user_flags.containsKey(name.toLowerCase());
185        }
186    
187        /**
188         * Equality is defined as true if the other object is a instanceof Flags with the
189         * same system and user flags set (using a case-insensitive name comparison for user flags).
190         * @param other the instance to compare against
191         * @return true if the two instance are the same
192         */
193        public boolean equals(Object other) {
194            if (other == this) return true;
195            if (other instanceof Flags == false) return false;
196            final Flags flags = (Flags) other;
197            return system_flags == flags.system_flags && user_flags.keySet().equals(flags.user_flags.keySet());
198        }
199    
200        /**
201         * Calculate a hashCode for this instance
202         * @return a hashCode for this instance
203         */
204        public int hashCode() {
205            return system_flags ^ user_flags.keySet().hashCode();
206        }
207    
208        /**
209         * Return a list of {@link Flags.Flag Flags} containing the system flags that have been set
210         * @return the system flags that have been set
211         */
212        public Flag[] getSystemFlags() {
213            // assumption: it is quicker to calculate the size than it is to reallocate the array
214            int size = 0;
215            if ((system_flags & Flag.ANSWERED.mask) != 0) size += 1;
216            if ((system_flags & Flag.DELETED.mask) != 0) size += 1;
217            if ((system_flags & Flag.DRAFT.mask) != 0) size += 1;
218            if ((system_flags & Flag.FLAGGED.mask) != 0) size += 1;
219            if ((system_flags & Flag.RECENT.mask) != 0) size += 1;
220            if ((system_flags & Flag.SEEN.mask) != 0) size += 1;
221            if ((system_flags & Flag.USER.mask) != 0) size += 1;
222            Flag[] result = new Flag[size];
223            if ((system_flags & Flag.USER.mask) != 0) result[--size] = Flag.USER;
224            if ((system_flags & Flag.SEEN.mask) != 0) result[--size] = Flag.SEEN;
225            if ((system_flags & Flag.RECENT.mask) != 0) result[--size] = Flag.RECENT;
226            if ((system_flags & Flag.FLAGGED.mask) != 0) result[--size] = Flag.FLAGGED;
227            if ((system_flags & Flag.DRAFT.mask) != 0) result[--size] = Flag.DRAFT;
228            if ((system_flags & Flag.DELETED.mask) != 0) result[--size] = Flag.DELETED;
229            if ((system_flags & Flag.ANSWERED.mask) != 0) result[--size] = Flag.ANSWERED;
230            return result;
231        }
232    
233        /**
234         * Return a list of user flags that have been set
235         * @return a list of user flags
236         */
237        public String[] getUserFlags() {
238            return (String[]) user_flags.values().toArray(new String[user_flags.values().size()]);
239        }
240    
241        /**
242         * Unset the supplied system flag.
243         * Question: what happens if we unset the USER flags and user flags are set?
244         * @param flag the flag to clear
245         */
246        public void remove(Flag flag) {
247            system_flags &= ~flag.mask;
248        }
249    
250        /**
251         * Unset all flags from the supplied instance.
252         * @param flags the flags to clear
253         */
254        public void remove(Flags flags) {
255            system_flags &= ~flags.system_flags;
256            user_flags.keySet().removeAll(flags.user_flags.keySet());
257        }
258    
259        /**
260         * Unset the supplied user flag.
261         * @param name the flag to clear
262         */
263        public void remove(String name) {
264            user_flags.remove(name.toLowerCase());
265        }
266    }