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 }