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 < 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