1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
5  *
6  * The contents of this file are subject to the terms of either the GNU
7  * General Public License Version 2 only ("GPL") or the Common Development
8  * and Distribution License("CDDL") (collectively, the "License").  You
9  * may not use this file except in compliance with the License.  You can
10  * obtain a copy of the License at
11  * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12  * or LICENSE.txt.  See the License for the specific
13  * language governing permissions and limitations under the License.
14  *
15  * When distributing the software, include this License Header Notice in each
16  * file and include the License file at LICENSE.txt.
17  *
18  * GPL Classpath Exception:
19  * Oracle designates this particular file as subject to the "Classpath"
20  * exception as provided by Oracle in the GPL Version 2 section of the License
21  * file that accompanied this code.
22  *
23  * Modifications:
24  * If applicable, add the following below the License Header, with the fields
25  * enclosed by brackets [] replaced by your own identifying information:
26  * "Portions Copyright [year] [name of copyright owner]"
27  *
28  * Contributor(s):
29  * If you wish your version of this file to be governed by only the CDDL or
30  * only the GPL Version 2, indicate your decision by adding "[Contributor]
31  * elects to include this software in this distribution under the [CDDL or GPL
32  * Version 2] license."  If you don't indicate a single choice of license, a
33  * recipient has the option to distribute your version of this file under
34  * either the CDDL, the GPL Version 2 or to extend the choice of license to
35  * its licensees as provided above.  However, if you add GPL Version 2 code
36  * and therefore, elected the GPL Version 2 license, then the option applies
37  * only if the new code is made subject to such option by the copyright
38  * holder.
39  */

40
41 package javax.mail;
42
43 import java.io.Serializable;
44 import java.util.*;
45
46 /**
47  * The Flags class represents the set of flags on a Message.  Flags
48  * are composed of predefined system flags, and user defined flags. <p>
49  *
50  * A System flag is represented by the <code>Flags.Flag</code> 
51  * inner class. A User defined flag is represented as a String.
52  * User flags are case-independent. <p>
53  *
54  * A set of standard system flags are predefined.  Most folder
55  * implementations are expected to support these flags.  Some
56  * implementations may also support arbitrary user-defined flags.  The
57  * <code>getPermanentFlags</code> method on a Folder returns a Flags
58  * object that holds all the flags that are supported by that folder
59  * implementation. <p>
60  *
61  * A Flags object is serializable so that (for example) the
62  * use of Flags objects in search terms can be serialized
63  * along with the search terms. <p>
64  *
65  * <strong>Warning:</strong>
66  * Serialized objects of this class may not be compatible with future
67  * JavaMail API releases.  The current serialization support is
68  * appropriate for short term storage. <p>
69  *
70  * The below code sample illustrates how to set, examine, and get the 
71  * flags for a message.
72  * <pre>
73  *
74  * Message m = folder.getMessage(1);
75  * m.setFlag(Flags.Flag.DELETED, true); // set the DELETED flag
76  *
77  * // Check if DELETED flag is set on this message
78  * if (m.isSet(Flags.Flag.DELETED))
79  *    System.out.println("DELETED message");
80  *
81  * // Examine ALL system flags for this message
82  * Flags flags = m.getFlags();
83  * Flags.Flag[] sf = flags.getSystemFlags();
84  * for (int i = 0; i &lt; sf.length; i++) {
85  *    if (sf[i] == Flags.Flag.DELETED)
86  *            System.out.println("DELETED message");
87  *    else if (sf[i] == Flags.Flag.SEEN)
88  *            System.out.println("SEEN message");
89  *      ......
90  *      ......
91  * }
92  * </pre>
93  * <p>
94  *
95  * @see    Folder#getPermanentFlags
96  * @author John Mani
97  * @author Bill Shannon
98  */

99
100 public class Flags implements Cloneable, Serializable {
101
102     private int system_flags = 0;
103     // used as a case-independent Set that preserves the original case,
104     // the key is the lowercase flag name and the value is the original
105     private Hashtable<String, String> user_flags = null;
106
107     private final static int ANSWERED_BIT     = 0x01;
108     private final static int DELETED_BIT     = 0x02;
109     private final static int DRAFT_BIT         = 0x04;
110     private final static int FLAGGED_BIT     = 0x08;
111     private final static int RECENT_BIT        = 0x10;
112     private final static int SEEN_BIT        = 0x20;
113     private final static int USER_BIT        = 0x80000000;
114
115     private static final long serialVersionUID = 6243590407214169028L;
116
117     /**
118      * This inner class represents an individual system flag. A set
119      * of standard system flag objects are predefined here.
120      */

121     public static final class Flag {
122     /**
123      * This message has been answered. This flag is set by clients 
124      * to indicate that this message has been answered to.
125      */

126     public static final Flag ANSWERED = new Flag(ANSWERED_BIT);
127
128     /**
129      * This message is marked deleted. Clients set this flag to
130      * mark a message as deleted. The expunge operation on a folder
131      * removes all messages in that folder that are marked for deletion.
132      */

133     public static final Flag DELETED = new Flag(DELETED_BIT);
134
135     /**
136      * This message is a draft. This flag is set by clients
137      * to indicate that the message is a draft message.
138      */

139     public static final Flag DRAFT = new Flag(DRAFT_BIT);
140
141     /**
142      * This message is flagged. No semantic is defined for this flag.
143      * Clients alter this flag.
144      */

145     public static final Flag FLAGGED = new Flag(FLAGGED_BIT);
146
147     /**
148      * This message is recent. Folder implementations set this flag
149      * to indicate that this message is new to this folder, that is,
150      * it has arrived since the last time this folder was opened. <p>
151      *
152      * Clients cannot alter this flag.
153      */

154     public static final Flag RECENT = new Flag(RECENT_BIT);
155
156     /**
157      * This message is seen. This flag is implicitly set by the 
158      * implementation when this Message's content is returned 
159      * to the client in some form. The <code>getInputStream</code>
160      * and <code>getContent</code> methods on Message cause this
161      * flag to be set. <p>
162      *
163      * Clients can alter this flag.
164      */

165     public static final Flag SEEN = new Flag(SEEN_BIT);
166
167     /**
168      * A special flag that indicates that this folder supports
169      * user defined flags. <p>
170      *
171      * The implementation sets this flag. Clients cannot alter 
172      * this flag but can use it to determine if a folder supports
173      * user defined flags by using
174      * <code>folder.getPermanentFlags().contains(Flags.Flag.USER)</code>.
175      */

176     public static final Flag USER = new Flag(USER_BIT);
177
178     // flags are stored as bits for efficiency
179     private int bit;
180     private Flag(int bit) {
181         this.bit = bit;
182     }
183     }
184
185
186     /**
187      * Construct an empty Flags object.
188      */

189     public Flags() { }
190
191     /**
192      * Construct a Flags object initialized with the given flags.
193      *
194      * @param flags    the flags for initialization
195      */

196     @SuppressWarnings("unchecked")
197     public Flags(Flags flags) {
198     this.system_flags = flags.system_flags;
199     if (flags.user_flags != null)
200         this.user_flags = (Hashtable)flags.user_flags.clone();
201     }
202
203     /**
204      * Construct a Flags object initialized with the given system flag.
205      *
206      * @param flag    the flag for initialization
207      */

208     public Flags(Flag flag) {
209     this.system_flags |= flag.bit;
210     }
211
212     /**
213      * Construct a Flags object initialized with the given user flag.
214      *
215      * @param flag    the flag for initialization
216      */

217     public Flags(String flag) {
218     user_flags = new Hashtable<>(1);
219     user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
220     }
221
222     /**
223      * Add the specified system flag to this Flags object.
224      *
225      * @param flag    the flag to add
226      */

227     public void add(Flag flag) {
228     system_flags |= flag.bit;
229     }
230
231     /**
232      * Add the specified user flag to this Flags object.
233      *
234      * @param flag    the flag to add
235      */

236     public void add(String flag) {
237     if (user_flags == null)
238         user_flags = new Hashtable<>(1);
239     user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
240     }
241
242     /**
243      * Add all the flags in the given Flags object to this
244      * Flags object.
245      *
246      * @param f    Flags object
247      */

248     public void add(Flags f) {
249     system_flags |= f.system_flags; // add system flags
250
251     if (f.user_flags != null) { // add user-defined flags
252         if (user_flags == null)
253         user_flags = new Hashtable<>(1);
254
255         Enumeration<String> e = f.user_flags.keys();
256
257         while (e.hasMoreElements()) {
258         String s = e.nextElement();
259         user_flags.put(s, f.user_flags.get(s));
260         }
261     }
262     }
263
264     /**
265      * Remove the specified system flag from this Flags object.
266      *
267      * @param    flag     the flag to be removed
268      */

269     public void remove(Flag flag) {
270     system_flags &= ~flag.bit;
271     }
272
273     /**
274      * Remove the specified user flag from this Flags object.
275      *
276      * @param    flag     the flag to be removed
277      */

278     public void remove(String flag) {
279     if (user_flags != null)
280         user_flags.remove(flag.toLowerCase(Locale.ENGLISH));
281     }
282
283     /**
284      * Remove all flags in the given Flags object from this 
285      * Flags object.
286      *
287      * @param    f     the flag to be removed
288      */

289     public void remove(Flags f) {
290     system_flags &= ~f.system_flags; // remove system flags
291
292     if (f.user_flags != null) {
293         if (user_flags == null)
294         return;
295
296         Enumeration<String> e = f.user_flags.keys();
297         while (e.hasMoreElements())
298         user_flags.remove(e.nextElement());
299     }
300     }
301
302     /**
303      * Remove any flags <strong>not</strong> in the given Flags object.
304      * Useful for clearing flags not supported by a server.  If the
305      * given Flags object includes the Flags.Flag.USER flag, all user
306      * flags in this Flags object are retained.
307      *
308      * @param    f    the flags to keep
309      * @return        true if this Flags object changed
310      * @since        JavaMail 1.6
311      */

312     public boolean retainAll(Flags f) {
313     boolean changed = false;
314     int sf = system_flags & f.system_flags;
315     if (system_flags != sf) {
316         system_flags = sf;
317         changed = true;
318     }
319
320     // if we have user flags, and the USER flag is not set in "f",
321     // determine which user flags to clear
322     if (user_flags != null && (f.system_flags & USER_BIT) == 0) {
323         if (f.user_flags != null) {
324         Enumeration<String> e = user_flags.keys();
325         while (e.hasMoreElements()) {
326             String key = e.nextElement();
327             if (!f.user_flags.containsKey(key)) {
328             user_flags.remove(key);
329             changed = true;
330             }
331         }
332         } else {
333         // if anything in user_flags, throw them away
334         changed = user_flags.size() > 0;
335         user_flags = null;
336         }
337     }
338     return changed;
339     }
340
341     /**
342      * Check whether the specified system flag is present in this Flags object.
343      *
344      * @param    flag    the flag to test
345      * @return         true of the given flag is present, otherwise false.
346      */

347     public boolean contains(Flag flag) {
348     return (system_flags & flag.bit) != 0;
349     }
350
351     /**
352      * Check whether the specified user flag is present in this Flags object.
353      *
354      * @param    flag    the flag to test
355      * @return         true of the given flag is present, otherwise false.
356      */

357     public boolean contains(String flag) {
358     if (user_flags == null
359         return false;
360     else
361         return user_flags.containsKey(flag.toLowerCase(Locale.ENGLISH));
362     }
363
364     /**
365      * Check whether all the flags in the specified Flags object are
366      * present in this Flags object.
367      *
368      * @param    f    the flags to test
369      * @return    true if all flags in the given Flags object are present, 
370      *        otherwise false.
371      */

372     public boolean contains(Flags f) {
373     // Check system flags
374     if ((f.system_flags & system_flags) != f.system_flags)
375         return false;
376
377     // Check user flags
378     if (f.user_flags != null) {
379         if (user_flags == null)
380         return false;
381         Enumeration<String> e = f.user_flags.keys();
382
383         while (e.hasMoreElements()) {
384         if (!user_flags.containsKey(e.nextElement()))
385             return false;
386         }
387     }
388
389     // If we've made it till here, return true
390     return true;
391     }
392
393     /**
394      * Check whether the two Flags objects are equal.
395      *
396      * @return    true if they're equal
397      */

398     @Override
399     public boolean equals(Object obj) {
400     if (!(obj instanceof Flags))
401         return false;
402
403     Flags f = (Flags)obj;
404
405     // Check system flags
406     if (f.system_flags != this.system_flags)
407         return false;
408
409     // Check user flags
410     int size = this.user_flags == null ? 0 : this.user_flags.size();
411     int fsize = f.user_flags == null ? 0 : f.user_flags.size();
412     if (size == 0 && fsize == 0)
413         return true;
414     if (f.user_flags != null && this.user_flags != null && fsize == size)
415         return user_flags.keySet().equals(f.user_flags.keySet());
416
417     return false;
418     }
419
420     /**
421      * Compute a hash code for this Flags object.
422      *
423      * @return    the hash code
424      */

425     @Override
426     public int hashCode() {
427     int hash = system_flags;
428     if (user_flags != null) {
429         Enumeration<String> e = user_flags.keys();
430         while (e.hasMoreElements())
431         hash += e.nextElement().hashCode();
432     }
433     return hash;
434     }
435
436     /**
437      * Return all the system flags in this Flags object.  Returns
438      * an array of size zero if no flags are set.
439      *
440      * @return    array of Flags.Flag objects representing system flags
441      */

442     public Flag[] getSystemFlags() {
443     Vector<Flag> v = new Vector<>();
444     if ((system_flags & ANSWERED_BIT) != 0)
445         v.addElement(Flag.ANSWERED);
446     if ((system_flags & DELETED_BIT) != 0)
447         v.addElement(Flag.DELETED);
448     if ((system_flags & DRAFT_BIT) != 0)
449         v.addElement(Flag.DRAFT);
450     if ((system_flags & FLAGGED_BIT) != 0)
451         v.addElement(Flag.FLAGGED);
452     if ((system_flags & RECENT_BIT) != 0)
453         v.addElement(Flag.RECENT);
454     if ((system_flags & SEEN_BIT) != 0)
455         v.addElement(Flag.SEEN);
456     if ((system_flags & USER_BIT) != 0)
457         v.addElement(Flag.USER);
458
459     Flag[] f = new Flag[v.size()];
460     v.copyInto(f);
461     return f;
462     }
463
464     /**
465      * Return all the user flags in this Flags object.  Returns
466      * an array of size zero if no flags are set.
467      *
468      * @return    array of Strings, each String represents a flag.
469      */

470     public String[] getUserFlags() {
471     Vector<String> v = new Vector<>();
472     if (user_flags != null) {
473         Enumeration<String> e = user_flags.elements();
474
475         while (e.hasMoreElements())
476         v.addElement(e.nextElement());
477     }
478
479     String[] f = new String[v.size()];
480     v.copyInto(f);
481     return f;
482     }
483
484     /**
485      * Clear all of the system flags.
486      *
487      * @since    JavaMail 1.6
488      */

489     public void clearSystemFlags() {
490     system_flags = 0;
491     }
492
493     /**
494      * Clear all of the user flags.
495      *
496      * @since    JavaMail 1.6
497      */

498     public void clearUserFlags() {
499     user_flags = null;
500     }
501
502     /**
503      * Returns a clone of this Flags object.
504      */

505     @SuppressWarnings("unchecked")
506     @Override
507     public Object clone() {
508     Flags f = null;
509     try {
510         f = (Flags)super.clone();
511     } catch (CloneNotSupportedException cex) {
512         // ignore, can't happen
513     }
514     if (this.user_flags != null)
515         f.user_flags = (Hashtable)this.user_flags.clone();
516     return f;
517     }
518
519     /**
520      * Return a string representation of this Flags object.
521      * Note that the exact format of the string is subject to change.
522      */

523     public String toString() {
524     StringBuilder sb = new StringBuilder();
525
526     if ((system_flags & ANSWERED_BIT) != 0)
527         sb.append("\\Answered ");
528     if ((system_flags & DELETED_BIT) != 0)
529         sb.append("\\Deleted ");
530     if ((system_flags & DRAFT_BIT) != 0)
531         sb.append("\\Draft ");
532     if ((system_flags & FLAGGED_BIT) != 0)
533         sb.append("\\Flagged ");
534     if ((system_flags & RECENT_BIT) != 0)
535         sb.append("\\Recent ");
536     if ((system_flags & SEEN_BIT) != 0)
537         sb.append("\\Seen ");
538     if ((system_flags & USER_BIT) != 0)
539         sb.append("\\* ");
540
541     boolean first = true;
542     if (user_flags != null) {
543         Enumeration<String> e = user_flags.elements();
544
545         while (e.hasMoreElements()) {
546         if (first)
547             first = false;
548         else
549             sb.append(' ');
550         sb.append(e.nextElement());
551         }
552     }
553
554     if (first && sb.length() > 0)
555         sb.setLength(sb.length() - 1);    // smash trailing space
556
557     return sb.toString();
558     }
559
560     /*****
561     public static void main(String argv[]) throws Exception {
562     // a new flags object
563     Flags f1 = new Flags();
564     f1.add(Flags.Flag.DELETED);
565     f1.add(Flags.Flag.SEEN);
566     f1.add(Flags.Flag.RECENT);
567     f1.add(Flags.Flag.ANSWERED);
568
569     // check copy constructor with only system flags
570     Flags fc = new Flags(f1);
571     if (f1.equals(fc) && fc.equals(f1))
572         System.out.println("success");
573     else
574         System.out.println("fail");
575
576     // check clone with only system flags
577     fc = (Flags)f1.clone();
578     if (f1.equals(fc) && fc.equals(f1))
579         System.out.println("success");
580     else
581         System.out.println("fail");
582
583     // add a user flag and make sure it still works right
584     f1.add("MyFlag");
585
586     // shouldn't be equal here
587     if (!f1.equals(fc) && !fc.equals(f1))
588         System.out.println("success");
589     else
590         System.out.println("fail");
591
592     // check clone
593     fc = (Flags)f1.clone();
594     if (f1.equals(fc) && fc.equals(f1))
595         System.out.println("success");
596     else
597         System.out.println("fail");
598
599     // make sure user flag hash tables are separate
600     fc.add("AnotherFlag");
601     if (!f1.equals(fc) && !fc.equals(f1))
602         System.out.println("success");
603     else
604         System.out.println("fail");
605
606     // check copy constructor
607     fc = new Flags(f1);
608     if (f1.equals(fc) && fc.equals(f1))
609         System.out.println("success");
610     else
611         System.out.println("fail");
612
613     // another new flags object
614     Flags f2 = new Flags(Flags.Flag.ANSWERED);
615     f2.add("MyFlag");
616
617     if (f1.contains(Flags.Flag.DELETED))
618         System.out.println("success");
619     else
620         System.out.println("fail");
621         
622     if (f1.contains(Flags.Flag.SEEN))
623         System.out.println("success");
624     else
625         System.out.println("fail");
626
627     if (f1.contains(Flags.Flag.RECENT))
628         System.out.println("success");
629     else
630         System.out.println("fail");
631
632     if (f1.contains("MyFlag"))
633         System.out.println("success");
634     else
635         System.out.println("fail");
636
637     if (f2.contains(Flags.Flag.ANSWERED))
638         System.out.println("success");
639     else
640         System.out.println("fail");
641
642
643     System.out.println("----------------");
644
645     String[] s = f1.getUserFlags();
646     for (int i = 0; i < s.length; i++)
647         System.out.println(s[i]);
648     System.out.println("----------------");
649     s = f2.getUserFlags();
650     for (int i = 0; i < s.length; i++)
651         System.out.println(s[i]);
652
653     System.out.println("----------------");
654
655     if (f1.contains(f2)) // this should be true
656         System.out.println("success");
657     else
658         System.out.println("fail");
659
660     if (!f2.contains(f1)) // this should be false
661         System.out.println("success");
662     else
663         System.out.println("fail");
664
665     Flags f3 = new Flags();
666     f3.add(Flags.Flag.DELETED);
667     f3.add(Flags.Flag.SEEN);
668     f3.add(Flags.Flag.RECENT);
669     f3.add(Flags.Flag.ANSWERED);
670     f3.add("ANOTHERFLAG");
671     f3.add("MYFLAG");
672
673     f1.add("AnotherFlag");
674
675     if (f1.equals(f3))
676         System.out.println("equals success");
677     else
678         System.out.println("fail");
679     if (f3.equals(f1))
680         System.out.println("equals success");
681     else
682         System.out.println("fail");
683     System.out.println("f1 hash code " + f1.hashCode());
684     System.out.println("f3 hash code " + f3.hashCode());
685     if (f1.hashCode() == f3.hashCode())
686         System.out.println("success");
687     else
688         System.out.println("fail");
689     }
690     ****/

691 }
692