001 package ezvcard.parameter;
002
003 import java.nio.charset.Charset;
004 import java.nio.charset.IllegalCharsetNameException;
005 import java.nio.charset.UnsupportedCharsetException;
006 import java.util.ArrayList;
007 import java.util.Collections;
008 import java.util.EnumSet;
009 import java.util.HashMap;
010 import java.util.HashSet;
011 import java.util.List;
012 import java.util.Map;
013 import java.util.Set;
014
015 import ezvcard.VCardDataType;
016 import ezvcard.VCardVersion;
017 import ezvcard.Warning;
018 import ezvcard.property.Address;
019 import ezvcard.property.Organization;
020 import ezvcard.property.StructuredName;
021 import ezvcard.util.GeoUri;
022 import ezvcard.util.ListMultimap;
023
024 /*
025 Copyright (c) 2013, Michael Angstadt
026 All rights reserved.
027
028 Redistribution and use in source and binary forms, with or without
029 modification, are permitted provided that the following conditions are met:
030
031 1. Redistributions of source code must retain the above copyright notice, this
032 list of conditions and the following disclaimer.
033 2. Redistributions in binary form must reproduce the above copyright notice,
034 this list of conditions and the following disclaimer in the documentation
035 and/or other materials provided with the distribution.
036
037 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
038 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
039 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
041 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
042 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
043 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
045 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
046 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
047
048 The views and conclusions contained in the software and documentation are those
049 of the authors and should not be interpreted as representing official policies,
050 either expressed or implied, of the FreeBSD Project.
051 */
052
053 /**
054 * Holds the parameters (aka "sub types") of a vCard property.
055 * @author Michael Angstadt
056 */
057 public class VCardParameters extends ListMultimap<String, String> {
058 public static final String ALTID = "ALTID";
059 public static final String CALSCALE = "CALSCALE";
060 public static final String CHARSET = "CHARSET";
061 public static final String ENCODING = "ENCODING";
062 public static final String GEO = "GEO";
063 public static final String INDEX = "INDEX";
064 public static final String LABEL = "LABEL";
065 public static final String LANGUAGE = "LANGUAGE";
066 public static final String LEVEL = "LEVEL";
067 public static final String MEDIATYPE = "MEDIATYPE";
068 public static final String PID = "PID";
069 public static final String PREF = "PREF";
070 public static final String SORT_AS = "SORT-AS";
071 public static final String TYPE = "TYPE";
072 public static final String TZ = "TZ";
073 public static final String VALUE = "VALUE";
074
075 private static final Map<String, Set<VCardVersion>> supportedVersions;
076 static {
077 Map<String, Set<VCardVersion>> m = new HashMap<String, Set<VCardVersion>>();
078 m.put(ALTID, EnumSet.of(VCardVersion.V4_0));
079 m.put(CALSCALE, EnumSet.of(VCardVersion.V4_0));
080 m.put(CHARSET, EnumSet.of(VCardVersion.V2_1));
081 m.put(GEO, EnumSet.of(VCardVersion.V4_0));
082 m.put(INDEX, EnumSet.of(VCardVersion.V4_0));
083
084 //don't check LABEL because this is removed and converted to LABEL properties for 2.1 and 3.0 vCards
085 //m.put(LABEL, EnumSet.of(VCardVersion.V4_0));
086
087 m.put(LEVEL, EnumSet.of(VCardVersion.V4_0));
088 m.put(MEDIATYPE, EnumSet.of(VCardVersion.V4_0));
089 m.put(PID, EnumSet.of(VCardVersion.V4_0));
090
091 //don't check PREF because this is removed and converted to "TYPE=PREF" for 2.1 and 3.0 vCards
092 //m.put(PREF, EnumSet.of(VCardVersion.V4_0));
093
094 m.put(SORT_AS, EnumSet.of(VCardVersion.V4_0));
095 m.put(TZ, EnumSet.of(VCardVersion.V4_0));
096
097 supportedVersions = Collections.unmodifiableMap(m);
098 }
099
100 /**
101 * Creates a list of parameters.
102 */
103 public VCardParameters() {
104 //empty
105 }
106
107 /**
108 * Creates a copy of an existing parameter list.
109 * @param orig the object to copy
110 */
111 public VCardParameters(VCardParameters orig) {
112 super(orig);
113 }
114
115 /**
116 * <p>
117 * Gets the ENCODING parameter. This is used when the property value is
118 * encoded in a form other than plain text.
119 * </p>
120 * <p>
121 * <b>Supported versions:</b> {@code 2.1, 3.0}
122 * </p>
123 * @return the encoding or null if not found
124 */
125 public Encoding getEncoding() {
126 String value = first(ENCODING);
127 return (value == null) ? null : Encoding.get(value);
128 }
129
130 /**
131 * <p>
132 * Sets the ENCODING parameter. This is used when the property value is
133 * encoded in a form other than plain text.
134 * </p>
135 * <p>
136 * <b>Supported versions:</b> {@code 2.1, 3.0}
137 * </p>
138 * @param encoding the encoding or null to remove
139 */
140 public void setEncoding(Encoding encoding) {
141 replace(ENCODING, (encoding == null) ? null : encoding.getValue());
142 }
143
144 /**
145 * <p>
146 * Gets the VALUE parameter. This defines what kind of data type the
147 * property has, such as "text" or "URI". Only used in text-based vCards.
148 * </p>
149 * <p>
150 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
151 * </p>
152 * @return the value or null if not found
153 */
154 public VCardDataType getValue() {
155 String value = first(VALUE);
156 return (value == null) ? null : VCardDataType.get(value);
157 }
158
159 /**
160 * <p>
161 * Sets the VALUE parameter. This defines what kind of data type the
162 * property has, such as "text" or "URI". Only used in text-based vCards.
163 * </p>
164 * <p>
165 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
166 * </p>
167 * @param value the value or null to remove
168 */
169 public void setValue(VCardDataType value) {
170 replace(VALUE, (value == null) ? null : value.getName());
171 }
172
173 /**
174 * <p>
175 * Removes the VALUE parameter. This defines what kind of data type the
176 * property has, such as "text" or "URI". Only used in text-based vCards.
177 * </p>
178 * <p>
179 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
180 * </p>
181 */
182 public void removeValue() {
183 removeAll(VALUE);
184 }
185
186 /**
187 * <p>
188 * Gets the CHARSET parameter.
189 * </p>
190 * <p>
191 * <b>Supported versions:</b> {@code 2.1}
192 * </p>
193 * @return the value or null if not found
194 */
195 public String getCharset() {
196 return first(CHARSET);
197 }
198
199 /**
200 * <p>
201 * Sets the CHARSET parameter.
202 * </p>
203 * <p>
204 * <b>Supported versions:</b> {@code 2.1}
205 * </p>
206 * @param charset the value or null to remove
207 */
208 public void setCharset(String charset) {
209 replace(CHARSET, charset);
210 }
211
212 /**
213 * <p>
214 * Gets the LANGUAGE parameter.
215 * </p>
216 * <p>
217 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
218 * </p>
219 * @return the language (e.g. "en-US") or null if not set
220 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a>
221 */
222 public String getLanguage() {
223 return first(LANGUAGE);
224 }
225
226 /**
227 * <p>
228 * Sets the LANGUAGE parameter.
229 * </p>
230 * <p>
231 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
232 * </p>
233 * @param language the language (e.g "en-US") or null to remove
234 * @see <a href="http://tools.ietf.org/html/rfc5646">RFC 5646</a>
235 */
236 public void setLanguage(String language) {
237 replace(LANGUAGE, language);
238 }
239
240 /**
241 * <p>
242 * Gets the LABEL parameter.
243 * </p>
244 * <p>
245 * <b>Supported versions:</b> {@code 4.0}
246 * </p>
247 * @return the address label or null if not set
248 */
249 public String getLabel() {
250 return first(LABEL);
251 }
252
253 /**
254 * <p>
255 * Sets the LABEL parameter.
256 * </p>
257 * <p>
258 * <b>Supported versions:</b> {@code 4.0}
259 * </p>
260 * @param label the address label or null to remove
261 */
262 public void setLabel(String label) {
263 replace(LABEL, label);
264 }
265
266 /**
267 * <p>
268 * Gets the TZ parameter.
269 * </p>
270 * <p>
271 * <b>Supported versions:</b> {@code 4.0}
272 * </p>
273 * @return the timezone (e.g. "America/New_York") or null if not set
274 */
275 public String getTimezone() {
276 return first(TZ);
277 }
278
279 /**
280 * <p>
281 * Sets the TZ parameter.
282 * </p>
283 * <p>
284 * <b>Supported versions:</b> {@code 4.0}
285 * </p>
286 * @param tz the timezone (e.g. "America/New_York") or null to remove
287 */
288 public void setTimezone(String tz) {
289 replace(TZ, tz);
290 }
291
292 /**
293 * <p>
294 * Gets all TYPE parameters.
295 * </p>
296 * <p>
297 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
298 * </p>
299 * @return the values or empty set if not found
300 */
301 public Set<String> getTypes() {
302 return new HashSet<String>(get(TYPE));
303 }
304
305 /**
306 * <p>
307 * Adds a TYPE parameter.
308 * </p>
309 * <p>
310 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
311 * </p>
312 * @param type the value
313 */
314 public void addType(String type) {
315 put(TYPE, type);
316 }
317
318 /**
319 * <p>
320 * Gets the first TYPE parameter.
321 * </p>
322 * <p>
323 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
324 * </p>
325 * @return the value or null if not found.
326 */
327 public String getType() {
328 Set<String> types = getTypes();
329 return types.isEmpty() ? null : types.iterator().next();
330 }
331
332 /**
333 * <p>
334 * Sets the TYPE parameter.
335 * </p>
336 * <p>
337 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
338 * </p>
339 * @param type the value or null to remove
340 */
341 public void setType(String type) {
342 replace(TYPE, type);
343 }
344
345 /**
346 * <p>
347 * Removes a TYPE parameter.
348 * </p>
349 * <p>
350 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
351 * </p>
352 * @param type the value to remove
353 */
354 public void removeType(String type) {
355 remove(TYPE, type);
356 }
357
358 /**
359 * <p>
360 * Removes all TYPE parameters.
361 * </p>
362 * <p>
363 * <b>Supported versions:</b> {@code 2.1, 3.0, 4.0}
364 * </p>
365 */
366 public void removeTypes() {
367 removeAll(TYPE);
368 }
369
370 /**
371 * <p>
372 * Gets the preference value. The lower the number, the more preferred this
373 * property instance is compared with other properties in the same vCard of
374 * the same type. If a property doesn't have a preference value, then it is
375 * considered the least preferred.
376 * </p>
377 *
378 * <p>
379 * In the vCard below, the address on the second row is the most preferred
380 * because it has the lowest PREF value.
381 * </p>
382 *
383 * <pre>
384 * ADR;TYPE=work;PREF=2:
385 * ADR;TYPE=work;PREF=1:
386 * ADR;TYPE=home:
387 * </pre>
388 *
389 * <p>
390 * Preference values must be numeric and must be between 1 and 100.
391 * </p>
392 *
393 * <p>
394 * <b>Supported versions:</b> {@code 4.0}
395 * </p>
396 * @throws IllegalStateException if the parameter value is malformed and
397 * cannot be parsed
398 * @return the preference value or null if it doesn't exist or null if it
399 * couldn't be parsed into a number
400 */
401 public Integer getPref() {
402 String pref = first(PREF);
403 if (pref == null) {
404 return null;
405 }
406
407 try {
408 return Integer.valueOf(pref);
409 } catch (NumberFormatException e) {
410 throw new IllegalStateException(PREF + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e);
411 }
412 }
413
414 /**
415 * <p>
416 * Sets the preference value. The lower the number, the more preferred this
417 * property instance is compared with other properties in the same vCard of
418 * the same type. If a property doesn't have a preference value, then it is
419 * considered the least preferred.
420 * </p>
421 *
422 * <p>
423 * In the vCard below, the address on the second row is the most preferred
424 * because it has the lowest PREF value.
425 * </p>
426 *
427 * <pre>
428 * ADR;TYPE=work;PREF=2:
429 * ADR;TYPE=work;PREF=1:
430 * ADR;TYPE=home:
431 * </pre>
432 *
433 * <p>
434 * Preference values must be numeric and must be between 1 and 100.
435 * </p>
436 *
437 * <p>
438 * <b>Supported versions:</b> {@code 4.0}
439 * </p>
440 * @param pref the preference value or null to remove
441 * @throws IllegalArgumentException if the value is not between 1 and 100
442 */
443 public void setPref(Integer pref) {
444 if (pref != null && (pref < 1 || pref > 100)) {
445 throw new IllegalArgumentException("Preference value must be between 1 and 100 inclusive.");
446 }
447 String value = (pref == null) ? null : pref.toString();
448 replace(PREF, value);
449 }
450
451 /**
452 * <p>
453 * Gets the ALTID parameter value. This is used to specify alternative
454 * representations of the same type.
455 * </p>
456 *
457 * <p>
458 * For example, a vCard may contain multiple NOTE properties that each have
459 * the same ALTID. This means that each NOTE contains a different
460 * representation of the same information. In the example below, the first
461 * three NOTEs have the same ALTID. They each contain the same message, but
462 * each is written in a different language. The other NOTEs have different
463 * (or absent) ALTIDs, which means they are not associated with the top
464 * three.
465 * </p>
466 *
467 * <pre>
468 * NOTE;ALTID=1;LANGUAGE=en: Hello world!
469 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde!
470 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo!
471 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau.
472 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue.
473 * NOTE: This vCard will self-destruct in 5 seconds.
474 * </pre>
475 *
476 * <p>
477 * <b>Supported versions:</b> {@code 4.0}
478 * </p>
479 * @return the ALTID or null if it doesn't exist
480 */
481 public String getAltId() {
482 return first(ALTID);
483 }
484
485 /**
486 * <p>
487 * Sets the ALTID parameter value. This is used to specify alternative
488 * representations of the same type.
489 * </p>
490 *
491 * <p>
492 * For example, a vCard may contain multiple NOTE properties that each have
493 * the same ALTID. This means that each NOTE contains a different
494 * representation of the same information. In the example below, the first
495 * three NOTEs have the same ALTID. They each contain the same message, but
496 * each is written in a different language. The other NOTEs have different
497 * (or absent) ALTIDs, which means they are not associated with the top
498 * three.
499 * </p>
500 *
501 * <pre>
502 * NOTE;ALTID=1;LANGUAGE=en: Hello world!
503 * NOTE;ALTID=1;LANGUAGE=fr: Bonjour tout le monde!
504 * NOTE;ALTID=1;LANGUAGE=es: �Hola, mundo!
505 * NOTE;ALTID=2;LANGUAGE=de: Meine Lieblingsfarbe ist blau.
506 * NOTE;ALTID=2;LANGUAGE=en: My favorite color is blue.
507 * NOTE: This vCard will self-destruct in 5 seconds.
508 * </pre>
509 *
510 * <p>
511 * <b>Supported versions:</b> {@code 4.0}
512 * </p>
513 * @param altId the ALTID or null to remove
514 */
515 public void setAltId(String altId) {
516 replace(ALTID, altId);
517 }
518
519 /**
520 * <p>
521 * Gets the GEO parameter value. This is used to associate global
522 * positioning information with a vCard property. It can be used with the
523 * {@link Address} property.
524 * </p>
525 * <p>
526 * <b>Supported versions:</b> {@code 4.0}
527 * </p>
528 * @throws IllegalStateException if the parameter value is malformed and
529 * cannot be parsed
530 * @return the latitude (index 0) and longitude (index 1) or null if not
531 * present or null if the parameter value was in an incorrect format
532 */
533 public double[] getGeo() {
534 String value = first(GEO);
535 if (value == null) {
536 return null;
537 }
538
539 try {
540 GeoUri geoUri = GeoUri.parse(value);
541 return new double[] { geoUri.getCoordA(), geoUri.getCoordB() };
542 } catch (IllegalArgumentException e) {
543 throw new IllegalStateException(GEO + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e);
544 }
545 }
546
547 /**
548 * <p>
549 * Sets the GEO parameter value. This is used to associate global
550 * positioning information with a vCard property. It can be used with the
551 * {@link Address} property.
552 * </p>
553 * <p>
554 * <b>Supported versions:</b> {@code 4.0}
555 * </p>
556 * @param latitude the latitude
557 * @param longitude the longitude
558 */
559 public void setGeo(double latitude, double longitude) {
560 GeoUri geoUri = new GeoUri.Builder(latitude, longitude).build();
561 replace(GEO, geoUri.toString());
562 }
563
564 /**
565 * <p>
566 * Gets the SORT-AS parameter value(s). This contains typically two string
567 * values which the vCard should be sorted by (family and given names). This
568 * is useful if the person's last name (defined in the N property) starts
569 * with characters that should be ignored during sorting. It can be used
570 * with the {@link StructuredName} and {@link Organization} properties.
571 * </p>
572 * <p>
573 * <b>Supported versions:</b> {@code 4.0}
574 * </p>
575 * @return the name(s) (e.g. { "Aboville", "Christine" } if the family name
576 * is "d'Aboville" and the given name is "Christine") or empty list of the
577 * parameter doesn't exist
578 */
579 public List<String> getSortAs() {
580 return get(SORT_AS);
581 }
582
583 /**
584 * <p>
585 * Sets the SORT-AS parameter value(s). This is useful with the N property
586 * when the person's last name starts with characters that should be ignored
587 * during sorting. It can be used with the {@link StructuredName} and
588 * {@link Organization} properties.
589 * </p>
590 * <p>
591 * <b>Supported versions:</b> {@code 4.0}
592 * </p>
593 * @param names the names in the order they should be sorted in (e.g.
594 * ["Aboville", "Christine"] if the family name is "d'Aboville" and the
595 * given name is "Christine") or empty parameter list to remove
596 */
597 public void setSortAs(String... names) {
598 removeAll(SORT_AS);
599 if (names != null && names.length > 0) {
600 for (String name : names) {
601 put(SORT_AS, name);
602 }
603 }
604 }
605
606 /**
607 * <p>
608 * Gets the type of calendar that is used for a date or date-time property
609 * value.
610 * </p>
611 * <p>
612 * <b>Supported versions:</b> {@code 4.0}
613 * </p>
614 * @return the type of calendar or null if not found
615 */
616 public Calscale getCalscale() {
617 String value = first(CALSCALE);
618 return (value == null) ? null : Calscale.get(value);
619 }
620
621 /**
622 * <p>
623 * Sets the type of calendar that is used for a date or date-time property
624 * value.
625 * </p>
626 * <p>
627 * <b>Supported versions:</b> {@code 4.0}
628 * </p>
629 * @param value the type of calendar or null to remove
630 */
631 public void setCalscale(Calscale value) {
632 replace(CALSCALE, (value == null) ? null : value.getValue());
633 }
634
635 /**
636 * <p>
637 * Gets all PID parameter values. PIDs can exist on any property where
638 * multiple instances are allowed (such as EMAIL or ADR, but not N because
639 * only 1 instance of N is allowed).
640 * </p>
641 * <p>
642 * When used in conjunction with the CLIENTPIDMAP property, it allows an
643 * individual property instance to be uniquely identifiable. This feature is
644 * made use of when two different versions of the same vCard have to be
645 * merged together (called "synchronizing").
646 * </p>
647 * <p>
648 * <b>Supported versions:</b> {@code 4.0}
649 * </p>
650 * @throws IllegalStateException if the parameter value is malformed and
651 * cannot be parsed
652 * @return the PID values or empty set if there are none. Index 0 is the
653 * local ID and index 1 is the ID used to reference the CLIENTPIDMAP
654 * property. Index 0 will never be null, but index 1 may be null.
655 */
656 public List<Integer[]> getPids() {
657 List<String> values = get(PID);
658 List<Integer[]> pids = new ArrayList<Integer[]>(values.size());
659 for (String value : values) {
660 String split[] = value.split("\\.");
661 try {
662 Integer localId = Integer.valueOf(split[0]);
663 Integer clientPidMapRef = (split.length > 1) ? Integer.valueOf(split[1]) : null;
664 pids.add(new Integer[] { localId, clientPidMapRef });
665 } catch (NumberFormatException e) {
666 throw new IllegalStateException(PID + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e);
667 }
668 }
669 return pids;
670 }
671
672 /**
673 * <p>
674 * Adds a PID parameter value. PIDs can exist on any property where multiple
675 * instances are allowed (such as EMAIL or ADR, but not N because only 1
676 * instance of N is allowed).
677 * </p>
678 * <p>
679 * When used in conjunction with the CLIENTPIDMAP property, it allows an
680 * individual property instance to be uniquely identifiable. This feature is
681 * made use of when two different versions of the same vCard have to be
682 * merged together (called "synchronizing").
683 * </p>
684 * <p>
685 * <b>Supported versions:</b> {@code 4.0}
686 * </p>
687 * @param localId the local ID
688 */
689 public void addPid(int localId) {
690 put(PID, localId + "");
691 }
692
693 /**
694 * <p>
695 * Adds a PID parameter value. PIDs can exist on any property where multiple
696 * instances are allowed (such as EMAIL or ADR, but not N because only 1
697 * instance of N is allowed).
698 * </p>
699 * <p>
700 * When used in conjunction with the CLIENTPIDMAP property, it allows an
701 * individual property instance to be uniquely identifiable. This feature is
702 * made use of when two different versions of the same vCard have to be
703 * merged together (called "synchronizing").
704 * </p>
705 * <p>
706 * <b>Supported versions:</b> {@code 4.0}
707 * </p>
708 * @param localId the local ID
709 * @param clientPidMapRef the ID used to reference the property's globally
710 * unique identifier in the CLIENTPIDMAP property.
711 */
712 public void addPid(int localId, int clientPidMapRef) {
713 put(PID, localId + "." + clientPidMapRef);
714 }
715
716 /**
717 * <p>
718 * Removes all PID values.
719 * </p>
720 * <p>
721 * <b>Supported versions:</b> {@code 4.0}
722 * </p>
723 */
724 public void removePids() {
725 removeAll(PID);
726 }
727
728 /**
729 * <p>
730 * Gets the MEDIATYPE parameter. This is used in properties that have a URL
731 * as a value, such as PHOTO and SOUND. It defines the content type of the
732 * referenced resource.
733 * </p>
734 * <p>
735 * <b>Supported versions:</b> {@code 4.0}
736 * </p>
737 * @return the media type (e.g. "image/jpeg") or null if it doesn't exist
738 */
739 public String getMediaType() {
740 return first(MEDIATYPE);
741 }
742
743 /**
744 * <p>
745 * Sets the MEDIATYPE parameter. This is used in properties that have a URL
746 * as a value, such as PHOTO and SOUND. It defines the content type of the
747 * referenced resource.
748 * </p>
749 * <p>
750 * <b>Supported versions:</b> {@code 4.0}
751 * </p>
752 * @param mediaType the media type (e.g. "image/jpeg") or null to remove
753 */
754 public void setMediaType(String mediaType) {
755 replace(MEDIATYPE, mediaType);
756 }
757
758 /**
759 * <p>
760 * Gets the LEVEL parameter. This is used to define the level of skill or
761 * level of interest the person has towards something.
762 * </p>
763 * <p>
764 * <b>Supported versions:</b> {@code 4.0}
765 * </p>
766 * @return the level (e.g. "beginner") or null if not found
767 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
768 */
769 public String getLevel() {
770 return first(LEVEL);
771 }
772
773 /**
774 * <p>
775 * Sets the LEVEL parameter. This is used to define the level of skill or
776 * level of interest the person has towards something.
777 * </p>
778 * <p>
779 * <b>Supported versions:</b> {@code 4.0}
780 * </p>
781 * @param level the level (e.g. "beginner") or null to remove
782 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
783 */
784 public void setLevel(String level) {
785 replace(LEVEL, level);
786 }
787
788 /**
789 * <p>
790 * Gets the sorted position of this property when it is grouped together
791 * with other properties of the same type. Properties with low index values
792 * are put at the beginning of the sorted list and properties with high
793 * index values are put at the end of the list.
794 * </p>
795 *
796 * <p>
797 * <b>Supported versions:</b> {@code 4.0}
798 * </p>
799 * @throws IllegalStateException if the parameter value is malformed and
800 * cannot be parsed
801 * @return the INDEX value or null if it doesn't exist or null if it
802 * couldn't be parsed into a number
803 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
804 */
805 public Integer getIndex() {
806 String index = first(INDEX);
807 if (index == null) {
808 return null;
809 }
810
811 try {
812 return Integer.valueOf(index);
813 } catch (NumberFormatException e) {
814 throw new IllegalStateException(INDEX + " parameter value is malformed and could not be parsed. Retrieve its raw text value instead.", e);
815 }
816 }
817
818 /**
819 * <p>
820 * Sets the sorted position of this property when it is grouped together
821 * with other properties of the same type. Properties with low index values
822 * are put at the beginning of the sorted list and properties with high
823 * index values are put at the end of the list.
824 * </p>
825 *
826 * <p>
827 * <b>Supported versions:</b> {@code 4.0}
828 * </p>
829 * @param index the INDEX value (must be greater than 0) or null to remove
830 * @see <a href="http://tools.ietf.org/html/rfc6715">RFC 6715</a>
831 * @throws IllegalArgumentException if the value is not greater than 0
832 */
833 public void setIndex(Integer index) {
834 if (index != null && index <= 0) {
835 throw new IllegalArgumentException("Index value must be greater than 0.");
836 }
837 String value = (index == null) ? null : index.toString();
838 replace(INDEX, value);
839 }
840
841 /**
842 * Checks this parameters list for data consistency problems or deviations
843 * from the spec. These problems will not prevent the vCard from being
844 * written to a data stream, but may prevent it from being parsed correctly
845 * by the consuming application.
846 * @param version the vCard version to validate against
847 * @return a list of warnings or an empty list if no problems were found
848 */
849 public List<Warning> validate(VCardVersion version) {
850 List<Warning> warnings = new ArrayList<Warning>(0);
851
852 {
853 int nonStandardCode = 3;
854 int valueNotSupportedCode = 4;
855
856 String value = first(CALSCALE);
857 if (value != null && Calscale.find(value) == null) {
858 warnings.add(new Warning(nonStandardCode, CALSCALE, value, Calscale.all()));
859 }
860
861 value = first(ENCODING);
862 if (value != null) {
863 Encoding encoding = Encoding.find(value);
864 if (encoding == null) {
865 warnings.add(new Warning(nonStandardCode, ENCODING, value, Encoding.all()));
866 } else if (!encoding.isSupported(version)) {
867 warnings.add(new Warning(valueNotSupportedCode, ENCODING, value));
868 }
869 }
870
871 value = first(VALUE);
872 if (value != null) {
873 VCardDataType dataType = VCardDataType.find(value);
874 if (dataType == null) {
875 warnings.add(new Warning(nonStandardCode, VALUE, value, VCardDataType.all()));
876 } else if (!dataType.isSupported(version)) {
877 warnings.add(new Warning(valueNotSupportedCode, VALUE, value));
878 }
879 }
880 }
881
882 {
883 int malformedCode = 5;
884
885 try {
886 getGeo();
887 } catch (IllegalStateException e) {
888 warnings.add(new Warning(malformedCode, GEO, first(GEO)));
889 }
890
891 try {
892 getIndex();
893 } catch (IllegalStateException e) {
894 warnings.add(new Warning(malformedCode, INDEX, first(INDEX)));
895 }
896
897 try {
898 getPids();
899 } catch (IllegalStateException e) {
900 warnings.add(new Warning(malformedCode, PID, first(PID)));
901 }
902
903 try {
904 getPref();
905 } catch (IllegalStateException e) {
906 warnings.add(new Warning(malformedCode, PREF, first(PREF)));
907 }
908 }
909
910 {
911 int paramNotSupportedCode = 6;
912 for (Map.Entry<String, Set<VCardVersion>> entry : supportedVersions.entrySet()) {
913 String name = entry.getKey();
914 String value = first(name);
915 if (value == null) {
916 continue;
917 }
918
919 Set<VCardVersion> versions = entry.getValue();
920 if (!versions.contains(version)) {
921 warnings.add(new Warning(paramNotSupportedCode, name));
922 }
923 }
924 }
925
926 {
927 int invalidCharsetCode = 22;
928 String charsetStr = getCharset();
929 if (charsetStr != null) {
930 try {
931 Charset.forName(charsetStr);
932 } catch (IllegalCharsetNameException e) {
933 warnings.add(new Warning(invalidCharsetCode, charsetStr));
934 } catch (UnsupportedCharsetException e) {
935 warnings.add(new Warning(invalidCharsetCode, charsetStr));
936 }
937 }
938 }
939
940 return warnings;
941 }
942
943 @Override
944 protected String sanitizeKey(String key) {
945 return (key == null) ? null : key.toUpperCase();
946 }
947 }