001 package ezvcard;
002
003 import java.util.ArrayList;
004 import java.util.HashSet;
005 import java.util.List;
006 import java.util.Set;
007
008 import ezvcard.parameters.CalscaleParameter;
009 import ezvcard.parameters.EncodingParameter;
010 import ezvcard.parameters.LevelParameter;
011 import ezvcard.parameters.TypeParameter;
012 import ezvcard.parameters.ValueParameter;
013 import ezvcard.util.GeoUri;
014 import ezvcard.util.ListMultimap;
015
016 /*
017 Copyright (c) 2012, Michael Angstadt
018 All rights reserved.
019
020 Redistribution and use in source and binary forms, with or without
021 modification, are permitted provided that the following conditions are met:
022
023 1. Redistributions of source code must retain the above copyright notice, this
024 list of conditions and the following disclaimer.
025 2. Redistributions in binary form must reproduce the above copyright notice,
026 this list of conditions and the following disclaimer in the documentation
027 and/or other materials provided with the distribution.
028
029 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
030 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
031 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
032 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
033 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
034 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
035 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
036 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
037 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
038 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
039
040 The views and conclusions contained in the software and documentation are those
041 of the authors and should not be interpreted as representing official policies,
042 either expressed or implied, of the FreeBSD Project.
043 */
044
045 /**
046 * Holds the parameters (aka "sub types") of a vCard Type.
047 * @author Michael Angstadt
048 */
049 public class VCardSubTypes {
050 private final ListMultimap<String, String> subTypes;
051
052 public VCardSubTypes() {
053 subTypes = new ListMultimap<String, String>();
054 }
055
056 /**
057 * Copy constructor.
058 * @param orig the object to copy
059 */
060 public VCardSubTypes(VCardSubTypes orig) {
061 subTypes = new ListMultimap<String, String>(orig.subTypes);
062 }
063
064 /**
065 * Adds a value to a Sub Type.
066 * @param name the Sub Type name
067 * @param value the value to add
068 */
069 public void put(String name, String value) {
070 subTypes.put(name.toUpperCase(), value);
071 }
072
073 /**
074 * Adds a value to a Sub Type, replacing all existing values that the Sub
075 * Type has.
076 * @param name the Sub Type name
077 * @param value the values to replace all existing values with
078 * @return the values of the Sub Type that were replaced
079 */
080 public List<String> replace(String name, String value) {
081 List<String> replaced = removeAll(name);
082 if (value != null) {
083 put(name, value);
084 }
085 return replaced;
086 }
087
088 /**
089 * Removes a Sub Type.
090 * @param name the Sub Type name
091 * @return the values of the Sub Type that were removed
092 */
093 public List<String> removeAll(String name) {
094 return subTypes.remove(name.toUpperCase());
095 }
096
097 /**
098 * Removes a value from a Sub Type.
099 * @param name the Sub Type name
100 * @param value the value to remove
101 */
102 public void remove(String name, String value) {
103 subTypes.remove(name.toUpperCase(), value);
104 }
105
106 /**
107 * Gets the values of a Sub Type
108 * @param name the Sub Type name
109 * @return the values or an empty list if the Sub Type doesn't exist
110 */
111 public List<String> get(String name) {
112 return subTypes.get(name.toUpperCase());
113 }
114
115 /**
116 * Gets the first value of a Sub Type.
117 * @param name the Sub Type name
118 * @return the first value or null if the Sub Type doesn't exist
119 */
120 public String getFirst(String name) {
121 List<String> list = get(name);
122 return list.isEmpty() ? null : list.get(0);
123 }
124
125 /**
126 * Gets the names of all the Sub Types.
127 * @return the names of all the Sub Types or an empty set if there are no
128 * Sub Types
129 */
130 public Set<String> getNames() {
131 return subTypes.keySet();
132 }
133
134 /**
135 * Gets the object used to store the Sub Types.
136 * @return the object used to store the Sub Types
137 */
138 public ListMultimap<String, String> getMultimap() {
139 return subTypes;
140 }
141
142 /**
143 * Gets the ENCODING sub type. This is used when the type value is encoded
144 * in a form other than plain text.
145 * <p>
146 * vCard versions: 2.1, 3.0
147 * </p>
148 * @return the encoding or null if not found
149 */
150 public EncodingParameter getEncoding() {
151 String value = getFirst(EncodingParameter.NAME);
152 if (value == null) {
153 return null;
154 }
155 EncodingParameter encoding = EncodingParameter.valueOf(value);
156 if (encoding == null) {
157 encoding = new EncodingParameter(value);
158 }
159 return encoding;
160 }
161
162 /**
163 * Sets the ENCODING sub type. This is used when the type value is encoded
164 * in a form other than plain text.
165 * <p>
166 * vCard versions: 2.1, 3.0
167 * </p>
168 * @param encoding the encoding or null to remove
169 */
170 public void setEncoding(EncodingParameter encoding) {
171 replace(EncodingParameter.NAME, (encoding == null) ? null : encoding.getValue());
172 }
173
174 /**
175 * Gets the VALUE sub type. This defines what kind of value the type has,
176 * such as "text" or "URI".
177 * <p>
178 * vCard versions: 2.1, 3.0, 4.0
179 * </p>
180 * @return the value or null if not found
181 */
182 public ValueParameter getValue() {
183 String value = getFirst(ValueParameter.NAME);
184 if (value == null) {
185 return null;
186 }
187 ValueParameter p = ValueParameter.valueOf(value);
188 if (p == null) {
189 p = new ValueParameter(value);
190 }
191 return p;
192 }
193
194 /**
195 * Sets the VALUE sub type. This defines what kind of value the type has,
196 * such as "text" or "URI".
197 * <p>
198 * vCard versions: 2.1, 3.0, 4.0
199 * </p>
200 * @param value the value or null to remove
201 */
202 public void setValue(ValueParameter value) {
203 replace(ValueParameter.NAME, (value == null) ? null : value.getValue());
204 }
205
206 /**
207 * Gets the CHARSET sub type.
208 * <p>
209 * vCard versions: 2.1
210 * </p>
211 * @return the value or null if not found
212 */
213 public String getCharset() {
214 return getFirst("CHARSET");
215 }
216
217 /**
218 * Sets the CHARSET sub type
219 * <p>
220 * vCard versions: 2.1
221 * </p>
222 * @param charset the value or null to remove
223 */
224 public void setCharset(String charset) {
225 replace("CHARSET", charset);
226 }
227
228 /**
229 * Gets the LANGUAGE sub type.
230 * <p>
231 * vCard versions: 2.1, 3.0, 4.0
232 * </p>
233 * @return the language (e.g. "en-US") or null if not set
234 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a>
235 */
236 public String getLanguage() {
237 return getFirst("LANGUAGE");
238 }
239
240 /**
241 * Sets the LANGUAGE sub type.
242 * <p>
243 * vCard versions: 2.1, 3.0, 4.0
244 * </p>
245 * @param language the language (e.g "en-US") or null to remove
246 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a>
247 */
248 public void setLanguage(String language) {
249 replace("LANGUAGE", language);
250 }
251
252 /**
253 * Gets all TYPE sub types.
254 * <p>
255 * vCard versions: 2.1, 3.0, 4.0
256 * </p>
257 * @return the values or empty set if not found
258 */
259 public Set<String> getTypes() {
260 return new HashSet<String>(get(TypeParameter.NAME));
261 }
262
263 /**
264 * Adds a TYPE sub type
265 * <p>
266 * vCard versions: 2.1, 3.0, 4.0
267 * </p>
268 * @param type the value
269 */
270 public void addType(String type) {
271 put(TypeParameter.NAME, type);
272 }
273
274 /**
275 * Gets the first TYPE sub type.
276 * <p>
277 * vCard versions: 2.1, 3.0, 4.0
278 * </p>
279 * @return the value or null if not found.
280 */
281 public String getType() {
282 Set<String> types = getTypes();
283 return types.isEmpty() ? null : types.iterator().next();
284 }
285
286 /**
287 * Sets the TYPE sub type.
288 * <p>
289 * vCard versions: 2.1, 3.0, 4.0
290 * </p>
291 * @param type the value or null to remove
292 */
293 public void setType(String type) {
294 replace(TypeParameter.NAME, type);
295 }
296
297 /**
298 * Removes a TYPE sub type.
299 * <p>
300 * vCard versions: 2.1, 3.0, 4.0
301 * </p>
302 * @param type the value to remove
303 */
304 public void removeType(String type) {
305 remove(TypeParameter.NAME, type);
306 }
307
308 /**
309 * <p>
310 * Gets the preference value. The lower the number, the more preferred this
311 * type is compared to other types in the vCard with the same name. If a
312 * type doesn't have a preference value, then it is considered the
313 * <b>least</b> preferred.
314 * </p>
315 *
316 * <p>
317 * In the vCard below, the address on the second row is the most preferred
318 * because it has the lowest PREF value.
319 * </p>
320 *
321 * <pre>
322 * ADR;TYPE=work;PREF=2:
323 * ADR;TYPE=work;PREF=1:
324 * ADR;TYPE=home:
325 * </pre>
326 *
327 * <p>
328 * Preference values must be numeric and must be between 1 and 100.
329 * </p>
330 *
331 * <p>
332 * vCard versions: 4.0
333 * </p>
334 * @return the preference value or null if it doesn't exist or null if it
335 * couldn't be parsed into a number
336 */
337 public Integer getPref() {
338 String pref = getFirst("PREF");
339 if (pref == null) {
340 return null;
341 }
342
343 try {
344 return Integer.valueOf(pref);
345 } catch (NumberFormatException e) {
346 return null;
347 }
348 }
349
350 /**
351 * <p>
352 * Sets the preference value. The lower the number, the more preferred this
353 * type is compared to other types in the vCard with the same name. If a
354 * type doesn't have a preference value, then it is considered the
355 * <b>least</b> preferred.
356 * </p>
357 *
358 * <p>
359 * In the vCard below, the address on the second row is the most preferred
360 * because it has the lowest PREF value.
361 * </p>
362 *
363 * <pre>
364 * ADR;TYPE=work;PREF=2:
365 * ADR;TYPE=work;PREF=1:
366 * ADR;TYPE=home:
367 * </pre>
368 *
369 * <p>
370 * Preference values must be numeric and must be between 1 and 100.
371 * </p>
372 *
373 * <p>
374 * vCard versions: 4.0
375 * </p>
376 * @param pref the preference value or null to remove
377 * @throws IllegalArgumentException if the value is not between 1 and 100
378 */
379 public void setPref(Integer pref) {
380 if (pref != null && (pref < 1 || pref > 100)) {
381 throw new IllegalArgumentException("Preference value must be between 1 and 100 inclusive.");
382 }
383 String value = (pref == null) ? null : pref.toString();
384 replace("PREF", value);
385 }
386
387 /**
388 * Gets the ALTID parameter value. This is used to specify alternative
389 * representations of the same type.
390 *
391 * <p>
392 * For example, a vCard may contain multiple NOTE types that each have the
393 * same ALTID. This means that each NOTE contains a different representation
394 * of the same information. In the example below, the first three NOTEs have
395 * the same ALTID. They each contain the same message, but each is written
396 * in a different language. The other NOTEs have different (or absent)
397 * ALTIDs, which means they are not associated with the top three.
398 * </p>
399 *
400 * <pre>
401 * NOTE;ALTID=1;LANGUAGE=en: Hello world!
402 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde!
403 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo!
404 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau.
405 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue.
406 * NOTE: This vCard will self-destruct in 5 seconds.
407 * </pre>
408 *
409 * <p>
410 * vCard versions: 4.0
411 * </p>
412 * @return the ALTID or null if it doesn't exist
413 */
414 public String getAltId() {
415 return getFirst("ALTID");
416 }
417
418 /**
419 * Sets the ALTID parameter value. This is used to specify alternative
420 * representations of the same type.
421 *
422 * <p>
423 * For example, a vCard may contain multiple NOTE types that each have the
424 * same ALTID. This means that each NOTE contains a different representation
425 * of the same information. In the example below, the first three NOTEs have
426 * the same ALTID. They each contain the same message, but each is written
427 * in a different language. The other NOTEs have different (or absent)
428 * ALTIDs, which means they are not associated with the top three.
429 * </p>
430 *
431 * <pre>
432 * NOTE;ALTID=1;LANGUAGE=en: Hello world!
433 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde!
434 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo!
435 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau.
436 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue.
437 * NOTE: This vCard will self-destruct in 5 seconds.
438 * </pre>
439 *
440 * <p>
441 * vCard versions: 4.0
442 * </p>
443 * @param altId the ALTID or null to remove
444 */
445 public void setAltId(String altId) {
446 replace("ALTID", altId);
447 }
448
449 /**
450 * Gets the GEO parameter value. This is used to associate global
451 * positioning information with a vCard type. It can be used with the ADR
452 * type.
453 * <p>
454 * vCard versions: 4.0
455 * </p>
456 * @return the latitude (index 0) and longitude (index 1) or null if not
457 * present or null if the parameter value was in an incorrect format
458 */
459 public double[] getGeo() {
460 String value = getFirst("GEO");
461 if (value == null) {
462 return null;
463 }
464
465 try {
466 GeoUri geoUri = new GeoUri(value);
467 return new double[] { geoUri.getCoordA(), geoUri.getCoordB() };
468 } catch (IllegalArgumentException e) {
469 return null;
470 }
471 }
472
473 /**
474 * Sets the GEO parameter value. This is used to associate global
475 * positioning information with a vCard type. It can be used with the ADR
476 * type.
477 * <p>
478 * vCard versions: 4.0
479 * </p>
480 * @param latitude the latitude
481 * @param longitude the longitude
482 */
483 public void setGeo(double latitude, double longitude) {
484 GeoUri geoUri = new GeoUri(latitude, longitude, null, null, null);
485 replace("GEO", geoUri.toString());
486 }
487
488 /**
489 * Gets the SORT-AS parameter value(s). This contains typically two string
490 * values which the vCard should be sorted by (family and given names). This
491 * is useful if the person's last name (defined in the N property) starts
492 * with characters that should be ignored during sorting. It can be used
493 * with the N and ORG types.
494 * <p>
495 * vCard versions: 4.0
496 * </p>
497 * @return the name(s) (e.g. { "Aboville", "Christine" } if the family name
498 * is "d'Aboville" and the given name is "Christine") or empty list of the
499 * parameter doesn't exist
500 */
501 public List<String> getSortAs() {
502 return get("SORT-AS");
503 }
504
505 /**
506 * Sets the SORT-AS parameter value(s). This is useful with the N property
507 * when the person's last name starts with characters that should be ignored
508 * during sorting. It can be used with the N and ORG types.
509 * <p>
510 * vCard versions: 4.0
511 * </p>
512 * @param names the names in the order they should be sorted in (e.g.
513 * ["Aboville", "Christine"] if the family name is "d'Aboville" and the
514 * given name is "Christine") or empty parameter list to remove
515 */
516 public void setSortAs(String... names) {
517 removeAll("SORT-AS");
518 if (names != null && names.length > 0) {
519 for (String name : names) {
520 put("SORT-AS", name);
521 }
522 }
523 }
524
525 /**
526 * Gets the CALSCALE parameter value. This defines the type of calendar that
527 * is used.
528 * <p>
529 * vCard versions: 4.0
530 * </p>
531 * @return the type of calendar or null if not found
532 */
533 public CalscaleParameter getCalscale() {
534 String value = getFirst(CalscaleParameter.NAME);
535 if (value == null) {
536 return null;
537 }
538 CalscaleParameter p = CalscaleParameter.valueOf(value);
539 if (p == null) {
540 p = new CalscaleParameter(value);
541 }
542 return p;
543 }
544
545 /**
546 * Gets the CALSCALE parameter value. This is used with date/time types and
547 * defines the type of calendar that is used.
548 * <p>
549 * vCard versions: 4.0
550 * </p>
551 * @param value the type of calendar or null to remove
552 */
553 public void setCalscale(CalscaleParameter value) {
554 replace(CalscaleParameter.NAME, (value == null) ? null : value.getValue());
555 }
556
557 /**
558 * <p>
559 * Gets all PID parameter values. PIDs can exist on any property where
560 * multiple instances are allowed (such as EMAIL or ADR, but not N because
561 * only 1 instance of N is allowed).
562 * </p>
563 * <p>
564 * When used in conjunction with the CLIENTPIDMAP property, it allows an
565 * individual property instance to be uniquely identifiable. This feature is
566 * made use of when two different versions of the same vCard have to be
567 * merged together (called "synchronizing").
568 * </p>
569 * <p>
570 * vCard versions: 4.0
571 * </p>
572 * @return the PID values or empty set if there are none. Index 0 is the
573 * local ID and index 1 is the ID used to reference the CLIENTPIDMAP
574 * property. Index 0 will never be null, but index 1 may be null.
575 */
576 public List<Integer[]> getPids() {
577 List<String> values = get("PID");
578 List<Integer[]> pids = new ArrayList<Integer[]>(values.size());
579 for (String value : values) {
580 try {
581 String split[] = value.split("\\.");
582 Integer pid[] = new Integer[2];
583 pid[0] = Integer.valueOf(split[0]);
584 if (split.length > 1) {
585 pid[1] = Integer.valueOf(split[1]);
586 }
587 pids.add(pid);
588 } catch (NumberFormatException e) {
589 //skip
590 }
591 }
592 return pids;
593 }
594
595 /**
596 * <p>
597 * Adds a PID parameter value. PIDs can exist on any property where multiple
598 * instances are allowed (such as EMAIL or ADR, but not N because only 1
599 * instance of N is allowed).
600 * </p>
601 * <p>
602 * When used in conjunction with the CLIENTPIDMAP property, it allows an
603 * individual property instance to be uniquely identifiable. This feature is
604 * made use of when two different versions of the same vCard have to be
605 * merged together (called "synchronizing").
606 * </p>
607 * <p>
608 * vCard versions: 4.0
609 * </p>
610 * @param localId the local ID
611 */
612 public void addPid(int localId) {
613 put("PID", Integer.toString(localId));
614 }
615
616 /**
617 * <p>
618 * Adds a PID parameter value. PIDs can exist on any property where multiple
619 * instances are allowed (such as EMAIL or ADR, but not N because only 1
620 * instance of N is allowed).
621 * </p>
622 * <p>
623 * When used in conjunction with the CLIENTPIDMAP property, it allows an
624 * individual property instance to be uniquely identifiable. This feature is
625 * made use of when two different versions of the same vCard have to be
626 * merged together (called "synchronizing").
627 * </p>
628 * <p>
629 * vCard versions: 4.0
630 * </p>
631 * @param localId the local ID
632 * @param clientPidMapRef the ID used to reference the property's globally
633 * unique identifier in the CLIENTPIDMAP property.
634 */
635 public void addPid(int localId, int clientPidMapRef) {
636 put("PID", localId + "." + clientPidMapRef);
637 }
638
639 /**
640 * Removes all PID values.
641 * <p>
642 * vCard versions: 4.0
643 * </p>
644 */
645 public void removePids() {
646 removeAll("PID");
647 }
648
649 /**
650 * Gets the MEDIATYPE parameter. This is used in properties that have a URL
651 * as a value, such as PHOTO and SOUND. It defines the content type of the
652 * referenced resource.
653 * <p>
654 * vCard versions: 4.0
655 * </p>
656 * @return the media type (e.g. "image/jpeg") or null if it doesn't exist
657 */
658 public String getMediaType() {
659 return getFirst("MEDIATYPE");
660 }
661
662 /**
663 * Sets the MEDIATYPE parameter. This is used in properties that have a URL
664 * as a value, such as PHOTO and SOUND. It defines the content type of the
665 * referenced resource.
666 * <p>
667 * vCard versions: 4.0
668 * </p>
669 * @param mediaType the media type (e.g. "image/jpeg") or null to remove
670 */
671 public void setMediaType(String mediaType) {
672 replace("MEDIATYPE", mediaType);
673 }
674
675 /**
676 * Gets the LEVEL parameter. This is used to define the level of skill or
677 * level of interest the person has towards something.
678 * <p>
679 * vCard versions: 4.0
680 * </p>
681 * @return the level (e.g. "beginner") or null if not found
682 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
683 */
684 public String getLevel() {
685 return getFirst(LevelParameter.NAME);
686 }
687
688 /**
689 * Sets the LEVEL parameter. This is used to define the level of skill or
690 * level of interest the person has towards something.
691 * <p>
692 * vCard versions: 4.0
693 * </p>
694 * @param level the level (e.g. "beginner") or null to remove
695 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
696 */
697 public void setLevel(String level) {
698 replace(LevelParameter.NAME, level);
699 }
700
701 /**
702 * Gets the INDEX parameter. When present, it specifies the order in which a
703 * collection of multi-valued property instances should be sorted.
704 * Properties with low INDEX values are put at the beginning of the sorted
705 * list.
706 *
707 * <p>
708 * vCard versions: 4.0
709 * </p>
710 * @return the INDEX value or null if it doesn't exist or null if it
711 * couldn't be parsed into a number
712 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
713 */
714 public Integer getIndex() {
715 String index = getFirst("INDEX");
716 if (index == null) {
717 return null;
718 }
719
720 try {
721 return Integer.valueOf(index);
722 } catch (NumberFormatException e) {
723 return null;
724 }
725 }
726
727 /**
728 * Sets the INDEX parameter. When present, it specifies the order in which a
729 * collection of multi-valued property instances should be sorted.
730 * Properties with low INDEX values are put at the beginning of the sorted
731 * list.
732 *
733 * <p>
734 * vCard versions: 4.0
735 * </p>
736 * @param index the INDEX value (must be greater than 0) or null to remove
737 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
738 * @throws IllegalArgumentException if the value is not greater than 0
739 */
740 public void setIndex(Integer index) {
741 if (index != null && index <= 0) {
742 throw new IllegalArgumentException("INDEX value must be greater than 0.");
743 }
744 String value = (index == null) ? null : index.toString();
745 replace("INDEX", value);
746 }
747 }