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: 467553 $ $Date: 2006-10-25 00:01:51 -0400 (Wed, 25 Oct 2006) $
038     */
039    public class Flags implements Cloneable, Serializable {
040        public static final class Flag {
041            /**
042             * Flag that indicates that the message has been replied to; has a bit value of 1.
043             */
044            public static final Flag ANSWERED = new Flag(1);
045            /**
046             * Flag that indicates that the message has been marked for deletion and
047             * should be removed on a subsequent expunge operation; has a bit value of 2.
048             */
049            public static final Flag DELETED = new Flag(2);
050            /**
051             * Flag that indicates that the message is a draft; has a bit value of 4.
052             */
053            public static final Flag DRAFT = new Flag(4);
054            /**
055             * Flag that indicates that the message has been flagged; has a bit value of 8.
056             */
057            public static final Flag FLAGGED = new Flag(8);
058            /**
059             * Flag that indicates that the message has been delivered since the last time
060             * this folder was opened; has a bit value of 16.
061             */
062            public static final Flag RECENT = new Flag(16);
063            /**
064             * Flag that indicates that the message has been viewed; has a bit value of 32.
065             * This flag is set by the {@link Message#getInputStream()} and {@link Message#getContent()}
066             * methods.
067             */
068            public static final Flag SEEN = new Flag(32);
069            /**
070             * Flags that indicates if this folder supports user-defined flags; has a bit value of 0x80000000.
071             */
072            public static final Flag USER = new Flag(0x80000000);
073    
074            private final int mask;
075    
076            private Flag(int mask) {
077                this.mask = mask;
078            }
079        }
080    
081        // the Serialized form of this class required the following two fields to be persisted
082        // this leads to a specific type of implementation
083        private int system_flags;
084        private final Hashtable user_flags;
085    
086        /**
087         * Construct a Flags instance with no flags set.
088         */
089        public Flags() {
090            user_flags = new Hashtable();
091        }
092    
093        /**
094         * Construct a Flags instance with a supplied system flag set.
095         * @param flag the system flag to set
096         */
097        public Flags(Flag flag) {
098            system_flags = flag.mask;
099            user_flags = new Hashtable();
100        }
101    
102        /**
103         * Construct a Flags instance with a same flags set.
104         * @param flags the instance to copy
105         */
106        public Flags(Flags flags) {
107            system_flags = flags.system_flags;
108            user_flags = new Hashtable(flags.user_flags);
109        }
110    
111        /**
112         * Construct a Flags instance with the supplied user flags set.
113         * Question: should this automatically set the USER system flag?
114         * @param name the user flag to set
115         */
116        public Flags(String name) {
117            user_flags = new Hashtable();
118            user_flags.put(name.toLowerCase(), name);
119        }
120    
121        /**
122         * Set a system flag.
123         * @param flag the system flag to set
124         */
125        public void add(Flag flag) {
126            system_flags |= flag.mask;
127        }
128    
129        /**
130         * Set all system and user flags from the supplied Flags.
131         * Question: do we need to check compatibility of USER flags?
132         * @param flags the Flags to add
133         */
134        public void add(Flags flags) {
135            system_flags |= flags.system_flags;
136            user_flags.putAll(flags.user_flags);
137        }
138    
139        /**
140         * Set a user flag.
141         * Question: should this fail if the USER system flag is not set?
142         * @param name the user flag to set
143         */
144        public void add(String name) {
145            user_flags.put(name.toLowerCase(), name);
146        }
147    
148        /**
149         * Return a copy of this instance.
150         * @return a copy of this instance
151         */
152        public Object clone() {
153            return new Flags(this);
154        }
155    
156        /**
157         * See if the supplied system flags are set
158         * @param flag the system flags to check for
159         * @return true if the flags are set
160         */
161        public boolean contains(Flag flag) {
162            return (system_flags & flag.mask) != 0;
163        }
164    
165        /**
166         * See if all of the supplied Flags are set
167         * @param flags the flags to check for
168         * @return true if all the supplied system and user flags are set
169         */
170        public boolean contains(Flags flags) {
171            return ((system_flags & flags.system_flags) == flags.system_flags)
172                    && user_flags.keySet().containsAll(flags.user_flags.keySet());
173        }
174    
175        /**
176         * See if the supplied user flag is set
177         * @param name the user flag to check for
178         * @return true if the flag is set
179         */
180        public boolean contains(String name) {
181            return user_flags.containsKey(name.toLowerCase());
182        }
183    
184        /**
185         * Equality is defined as true if the other object is a instanceof Flags with the
186         * same system and user flags set (using a case-insensitive name comparison for user flags).
187         * @param other the instance to compare against
188         * @return true if the two instance are the same
189         */
190        public boolean equals(Object other) {
191            if (other == this) return true;
192            if (other instanceof Flags == false) return false;
193            final Flags flags = (Flags) other;
194            return system_flags == flags.system_flags && user_flags.keySet().equals(flags.user_flags.keySet());
195        }
196    
197        /**
198         * Calculate a hashCode for this instance
199         * @return a hashCode for this instance
200         */
201        public int hashCode() {
202            return system_flags ^ user_flags.keySet().hashCode();
203        }
204    
205        /**
206         * Return a list of {@link Flags.Flag Flags} containing the system flags that have been set
207         * @return the system flags that have been set
208         */
209        public Flag[] getSystemFlags() {
210            // assumption: it is quicker to calculate the size than it is to reallocate the array
211            int size = 0;
212            if ((system_flags & Flag.ANSWERED.mask) != 0) size += 1;
213            if ((system_flags & Flag.DELETED.mask) != 0) size += 1;
214            if ((system_flags & Flag.DRAFT.mask) != 0) size += 1;
215            if ((system_flags & Flag.FLAGGED.mask) != 0) size += 1;
216            if ((system_flags & Flag.RECENT.mask) != 0) size += 1;
217            if ((system_flags & Flag.SEEN.mask) != 0) size += 1;
218            if ((system_flags & Flag.USER.mask) != 0) size += 1;
219            Flag[] result = new Flag[size];
220            if ((system_flags & Flag.USER.mask) != 0) result[--size] = Flag.USER;
221            if ((system_flags & Flag.SEEN.mask) != 0) result[--size] = Flag.SEEN;
222            if ((system_flags & Flag.RECENT.mask) != 0) result[--size] = Flag.RECENT;
223            if ((system_flags & Flag.FLAGGED.mask) != 0) result[--size] = Flag.FLAGGED;
224            if ((system_flags & Flag.DRAFT.mask) != 0) result[--size] = Flag.DRAFT;
225            if ((system_flags & Flag.DELETED.mask) != 0) result[--size] = Flag.DELETED;
226            if ((system_flags & Flag.ANSWERED.mask) != 0) result[--size] = Flag.ANSWERED;
227            return result;
228        }
229    
230        /**
231         * Return a list of user flags that have been set
232         * @return a list of user flags
233         */
234        public String[] getUserFlags() {
235            return (String[]) user_flags.values().toArray(new String[user_flags.values().size()]);
236        }
237    
238        /**
239         * Unset the supplied system flag.
240         * Question: what happens if we unset the USER flags and user flags are set?
241         * @param flag the flag to clear
242         */
243        public void remove(Flag flag) {
244            system_flags &= ~flag.mask;
245        }
246    
247        /**
248         * Unset all flags from the supplied instance.
249         * @param flags the flags to clear
250         */
251        public void remove(Flags flags) {
252            system_flags &= ~flags.system_flags;
253            user_flags.keySet().removeAll(flags.user_flags.keySet());
254        }
255    
256        /**
257         * Unset the supplied user flag.
258         * @param name the flag to clear
259         */
260        public void remove(String name) {
261            user_flags.remove(name.toLowerCase());
262        }
263    }