001    package ezvcard.types;
002    
003    import java.text.DecimalFormat;
004    import java.text.NumberFormat;
005    import java.util.List;
006    
007    import ezvcard.VCard;
008    import ezvcard.VCardSubTypes;
009    import ezvcard.VCardVersion;
010    import ezvcard.io.CompatibilityMode;
011    import ezvcard.io.SkipMeException;
012    import ezvcard.parameters.ValueParameter;
013    import ezvcard.util.GeoUri;
014    import ezvcard.util.HCardElement;
015    import ezvcard.util.XCardElement;
016    
017    /*
018     Copyright (c) 2012, Michael Angstadt
019     All rights reserved.
020    
021     Redistribution and use in source and binary forms, with or without
022     modification, are permitted provided that the following conditions are met: 
023    
024     1. Redistributions of source code must retain the above copyright notice, this
025     list of conditions and the following disclaimer. 
026     2. Redistributions in binary form must reproduce the above copyright notice,
027     this list of conditions and the following disclaimer in the documentation
028     and/or other materials provided with the distribution. 
029    
030     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
031     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
032     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
033     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
034     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
036     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
037     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
038     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
039     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
040    
041     The views and conclusions contained in the software and documentation are those
042     of the authors and should not be interpreted as representing official policies, 
043     either expressed or implied, of the FreeBSD Project.
044     */
045    
046    /**
047     * A set of latitude/longitude coordinates. There is no rule for what these
048     * coordinates must represent, but the meaning could vary depending on the value
049     * of the vCard KIND type.
050     * 
051     * <ul>
052     * <li>"individual": the location of the person's home or workplace.</li>
053     * <li>"group": the location of the group's meeting place.</li>
054     * <li>"org": the coordinates of the organization's headquarters.</li>
055     * <li>"location": the coordinates of the location itself.</li>
056     * </ul>
057     * 
058     * <pre>
059     * VCard vcard = new VCard();
060     * GeoType geo = new GeoType(-123.456, 12.54);
061     * vcard.setGeo(geo);
062     * </pre>
063     * 
064     * <p>
065     * vCard property name: GEO
066     * </p>
067     * <p>
068     * vCard versions: 2.1, 3.0, 4.0
069     * </p>
070     * @author Michael Angstadt
071     */
072    public class GeoType extends VCardType {
073            public static final String NAME = "GEO";
074            private GeoUri uri = new GeoUri();
075    
076            public GeoType() {
077                    this(null, null);
078            }
079    
080            /**
081             * @param latitude the latitude
082             * @param longitude the longitude
083             */
084            public GeoType(Double latitude, Double longitude) {
085                    super(NAME);
086                    setLatitude(latitude);
087                    setLongitude(longitude);
088            }
089    
090            /**
091             * Gets the latitude.
092             * @return the latitude
093             */
094            public Double getLatitude() {
095                    return uri.getCoordA();
096            }
097    
098            /**
099             * Sets the latitude.
100             * @param latitude the latitude
101             */
102            public void setLatitude(Double latitude) {
103                    uri.setCoordA(latitude);
104            }
105    
106            /**
107             * Gets the longitude.
108             * @return the longitude
109             */
110            public Double getLongitude() {
111                    return uri.getCoordB();
112            }
113    
114            /**
115             * Sets the longitude.
116             * @param longitude the longitude
117             */
118            public void setLongitude(Double longitude) {
119                    uri.setCoordB(longitude);
120            }
121    
122            /**
123             * Gets the raw object used for storing the GEO information. This can be
124             * used to supplement the GEO value with additional information (such as the
125             * altitude). Geo URIs are only supported by vCard version 4.0. Everything
126             * but latitude and longitude will be lost when marshalling to an earlier
127             * vCard version.
128             * @return the geo URI object
129             * @see <a href="http://tools.ietf.org/html/rfc5870">RFC 5870</a>
130             */
131            public GeoUri getGeoUri() {
132                    return uri;
133            }
134    
135            /**
136             * Gets the TYPE parameter.
137             * <p>
138             * vCard versions: 4.0
139             * </p>
140             * @return the TYPE value (typically, this will be either "work" or "home")
141             * or null if it doesn't exist
142             */
143            public String getType() {
144                    return subTypes.getType();
145            }
146    
147            /**
148             * Sets the TYPE parameter.
149             * <p>
150             * vCard versions: 4.0
151             * </p>
152             * @param type the TYPE value (this should be either "work" or "home") or
153             * null to remove
154             */
155            public void setType(String type) {
156                    subTypes.setType(type);
157            }
158    
159            /**
160             * Gets the MEDIATYPE parameter.
161             * <p>
162             * vCard versions: 4.0
163             * </p>
164             * @return the media type or null if not set
165             */
166            public String getMediaType() {
167                    return subTypes.getMediaType();
168            }
169    
170            /**
171             * Sets the MEDIATYPE parameter.
172             * <p>
173             * vCard versions: 4.0
174             * </p>
175             * @param mediaType the media type or null to remove
176             */
177            public void setMediaType(String mediaType) {
178                    subTypes.setMediaType(mediaType);
179            }
180    
181            /**
182             * Gets all PID parameter values.
183             * <p>
184             * vCard versions: 4.0
185             * </p>
186             * @return the PID values or empty set if there are none
187             * @see VCardSubTypes#getPids
188             */
189            public List<Integer[]> getPids() {
190                    return subTypes.getPids();
191            }
192    
193            /**
194             * Adds a PID value.
195             * <p>
196             * vCard versions: 4.0
197             * </p>
198             * @param localId the local ID
199             * @param clientPidMapRef the ID used to reference the property's globally
200             * unique identifier in the CLIENTPIDMAP property.
201             * @see VCardSubTypes#addPid(int, int)
202             */
203            public void addPid(int localId, int clientPidMapRef) {
204                    subTypes.addPid(localId, clientPidMapRef);
205            }
206    
207            /**
208             * Removes all PID values.
209             * <p>
210             * vCard versions: 4.0
211             * </p>
212             * @see VCardSubTypes#removePids
213             */
214            public void removePids() {
215                    subTypes.removePids();
216            }
217    
218            /**
219             * Gets the preference value.
220             * <p>
221             * vCard versions: 4.0
222             * </p>
223             * @return the preference value or null if it doesn't exist
224             * @see VCardSubTypes#getPref
225             */
226            public Integer getPref() {
227                    return subTypes.getPref();
228            }
229    
230            /**
231             * Sets the preference value.
232             * <p>
233             * vCard versions: 4.0
234             * </p>
235             * @param pref the preference value or null to remove
236             * @see VCardSubTypes#setPref
237             */
238            public void setPref(Integer pref) {
239                    subTypes.setPref(pref);
240            }
241    
242            /**
243             * Gets the ALTID.
244             * <p>
245             * vCard versions: 4.0
246             * </p>
247             * @return the ALTID or null if it doesn't exist
248             * @see VCardSubTypes#getAltId
249             */
250            public String getAltId() {
251                    return subTypes.getAltId();
252            }
253    
254            /**
255             * Sets the ALTID.
256             * <p>
257             * vCard versions: 4.0
258             * </p>
259             * @param altId the ALTID or null to remove
260             * @see VCardSubTypes#setAltId
261             */
262            public void setAltId(String altId) {
263                    subTypes.setAltId(altId);
264            }
265    
266            @Override
267            protected void doMarshalSubTypes(VCardSubTypes copy, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode, VCard vcard) {
268                    if (version == VCardVersion.V4_0) {
269                            copy.setValue(ValueParameter.URI);
270                    }
271            }
272    
273            @Override
274            protected void doMarshalText(StringBuilder sb, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
275                    if (getLatitude() == null || getLongitude() == null) {
276                            throw new SkipMeException("Latitude and/or longitude is missing.");
277                    }
278    
279                    if (version == VCardVersion.V4_0) {
280                            sb.append(uri.toString(6));
281                    } else {
282                            NumberFormat nf = new DecimalFormat("0.######");
283                            sb.append(nf.format(getLatitude()));
284                            sb.append(';');
285                            sb.append(nf.format(getLongitude()));
286                    }
287            }
288    
289            @Override
290            protected void doUnmarshalText(String value, VCardVersion version, List<String> warnings, CompatibilityMode compatibilityMode) {
291                    try {
292                            //4.0 syntax: GEO;VALUE=uri:geo:12,23
293                            uri = new GeoUri(value);
294                    } catch (IllegalArgumentException e) {
295                            //2.1/3.0 syntax: GEO:12;34
296                            String split[] = value.split(";");
297                            uri = new GeoUri();
298    
299                            try {
300                                    setLatitude(Double.valueOf(split[0]));
301                            } catch (NumberFormatException e2) {
302                                    //do nothing (handled below)
303                            }
304    
305                            boolean longMissing = false;
306                            if (split.length > 1) {
307                                    try {
308                                            setLongitude(Double.valueOf(split[1]));
309                                    } catch (NumberFormatException e2) {
310                                            //do nothing (handled below)
311                                    }
312                            } else {
313                                    longMissing = true;
314                            }
315    
316                            if (getLatitude() == null && getLongitude() == null) {
317                                    throw new SkipMeException("Unparseable value: \"" + value + "\"");
318                            } else if (longMissing) {
319                                    warnings.add("Longitude missing from " + NAME + " type value: \"" + value + "\"");
320                            } else if (getLatitude() == null) {
321                                    warnings.add("Could not parse latitude from " + NAME + " type value: \"" + value + "\"");
322                            } else if (getLongitude() == null) {
323                                    warnings.add("Could not parse longitude from " + NAME + " type value: \"" + value + "\"");
324                            }
325                    }
326            }
327    
328            @Override
329            protected void doMarshalXml(XCardElement parent, List<String> warnings, CompatibilityMode compatibilityMode) {
330                    StringBuilder sb = new StringBuilder();
331                    doMarshalText(sb, parent.version(), warnings, compatibilityMode);
332                    parent.uri(sb.toString());
333            }
334    
335            @Override
336            protected void doUnmarshalXml(XCardElement element, List<String> warnings, CompatibilityMode compatibilityMode) {
337                    String value = element.uri();
338                    if (value != null) {
339                            doUnmarshalText(value, element.version(), warnings, compatibilityMode);
340                    }
341            }
342    
343            @Override
344            protected void doUnmarshalHtml(HCardElement element, List<String> warnings) {
345                    String latitude = element.firstValue("latitude");
346                    if (latitude == null) {
347                            warnings.add("Latitude missing from " + NAME + " type.");
348                    } else {
349                            try {
350                                    setLatitude(Double.parseDouble(latitude));
351                            } catch (NumberFormatException e) {
352                                    warnings.add("Could not parse latitude from " + NAME + " type.");
353                            }
354                    }
355    
356                    String longitude = element.firstValue("longitude");
357                    if (longitude == null) {
358                            warnings.add("Longitude missing from " + NAME + " type.");
359                    } else {
360                            try {
361                                    setLongitude(Double.parseDouble(longitude));
362                            } catch (NumberFormatException e) {
363                                    warnings.add("Could not parse longitude from " + NAME + " type.");
364                            }
365                    }
366    
367                    if (getLatitude() == null && getLongitude() == null) {
368                            throw new SkipMeException("Unparsable value.");
369                    }
370            }
371    }