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    }