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 }