001package jmri.configurexml; 002 003import javax.annotation.CheckForNull; 004import javax.annotation.Nonnull; 005import java.util.HashMap; 006import java.util.Map; 007import org.jdom2.Attribute; 008import org.jdom2.Element; 009 010/** 011 * Abstract class to provide basic error handling for XmlAdapter 012 * 013 * @author Bob Jacobsen Copyright (c) 2009 014 * @see XmlAdapter 015 */ 016public abstract class AbstractXmlAdapter implements XmlAdapter { 017 018 private ErrorHandler errorHandler = XmlAdapter.getDefaultExceptionHandler(); 019 020 public static final String STR_TRUE = "true"; 021 public static final String STR_FALSE = "false"; 022 public static final String STR_YES = "yes"; 023 public static final String STR_NO = "no"; 024 025 /** 026 * Return the value of a named child element, or an empty string if the 027 * child is missing. 028 * @param parent parent element 029 * @param childName child element name 030 * @return child value, or an empty string 031 */ 032 protected String childValue(Element parent, String childName) { 033 Element child = parent.getChild(childName); 034 return child == null ? "" : child.getValue(); 035 } 036 037 /** 038 * Return an empty string for a null value. 039 * @param value value to convert 040 * @return value, or an empty string if null 041 */ 042 protected String nullToEmpty(String value) { 043 return value == null ? "" : value; 044 } 045 046 /** {@inheritDoc} */ 047 @Override 048 public void handleException( 049 String description, 050 String operation, 051 String systemName, 052 String userName, 053 Exception exception) { 054 if (errorHandler != null) { 055 this.errorHandler.handle(new ErrorMemo(this, operation, description, systemName, userName, exception)); 056 } 057 } 058 059 /** {@inheritDoc} */ 060 @Override 061 public boolean load(Element e) throws JmriConfigureXmlException { 062 throw new UnsupportedOperationException("One of the other load methods must be implemented."); 063 } 064 065 /** {@inheritDoc} */ 066 @Override 067 public void load(Element e, Object o) throws JmriConfigureXmlException { 068 log.error("Invalid method called"); 069 } 070 071 /** {@inheritDoc} */ 072 @Override 073 public boolean load(@Nonnull Element shared, Element perNode) throws JmriConfigureXmlException { // may not need exception 074 return this.load(shared); 075 } 076 077 /** {@inheritDoc} */ 078 @Override 079 public void load(@Nonnull Element shared, Element perNode, Object o) throws JmriConfigureXmlException { // may not need exception 080 this.load(shared, o); 081 } 082 083 /** 084 * Determine if this set of configured objects should be loaded after basic 085 * GUI construction is completed. 086 * <p> 087 * Default behavior is to load when requested. Classes that should wait 088 * until basic GUI is constructed should override this method and return 089 * true 090 * 091 * @return true to defer loading 092 * @see jmri.configurexml.XmlAdapter#loadDeferred() 093 * @since 2.11.2 094 */ 095 @Override 096 public boolean loadDeferred() { 097 return false; 098 } 099 100 /** {@inheritDoc} */ 101 @Override 102 public int loadOrder() { 103 return 50; 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public Element store(@Nonnull Object o, boolean shared) { 109 if (shared) { 110 return this.store(o); 111 } 112 return null; 113 } 114 115 /** {@inheritDoc} */ 116 @Override 117 public void setExceptionHandler(ErrorHandler errorHandler) { 118 this.errorHandler = errorHandler; 119 } 120 121 /** {@inheritDoc} */ 122 @Override 123 public ErrorHandler getExceptionHandler() { 124 return this.errorHandler; 125 } 126 127 /** 128 * Service method to handle attribute input of 129 * boolean (true/yes vs false/no) values. Not being present 130 * is not an error. Not parsing (which shouldn't happen due to 131 * the XML Schema checks) invokes the default error handler. 132 * @param element the element to parse. 133 * @param name element attribute name. 134 * @param def default value if name not present in element. 135 * @return boolean value of attribute, else default if not present or error. 136 */ 137 public final boolean getAttributeBooleanValue(@Nonnull Element element, @Nonnull String name, boolean def) { 138 Attribute a; 139 String val = null; 140 try { 141 a = element.getAttribute(name); 142 if (a == null) { 143 return def; 144 } 145 val = a.getValue(); 146 if ( val.equals(STR_YES) || val.equals(STR_TRUE) ) { 147 return true; 148 } 149 if ( val.equals(STR_NO) || val.equals(STR_FALSE) ) { 150 return false; 151 } 152 return def; 153 } catch (Exception ex) { 154 log.debug("caught exception", ex); 155 ErrorMemo em = new ErrorMemo(this, 156 "getAttributeBooleanValue threw exception", 157 "element: "+element.getName(), 158 "attribute: "+name, 159 "value: "+val, 160 ex); 161 getExceptionHandler().handle(em); 162 return def; 163 } 164 } 165 166 /** 167 * Service method to handle attribute input of 168 * integer values. Not being present 169 * is not an error. Not parsing (which shouldn't happen due to 170 * the XML Schema checks) invokes the default error handler. 171 * @param element the element to parse. 172 * @param name element attribute name. 173 * @param def default value if name not present in element. 174 * @return integer value of attribute, else default if not present or error. 175 */ 176 public final int getAttributeIntegerValue(@Nonnull Element element, @Nonnull String name, int def) { 177 Attribute a; 178 String val = null; 179 try { 180 a = element.getAttribute(name); 181 if (a == null) { 182 return def; 183 } 184 val = a.getValue(); 185 return a.getIntValue(); 186 } catch (Exception ex) { 187 log.debug("caught exception", ex); 188 ErrorMemo em = new ErrorMemo(this, 189 "getAttributeIntegerValue threw exception", 190 "element: "+element.getName(), 191 "attribute: "+name, 192 "value: "+val, 193 ex); 194 getExceptionHandler().handle(em); 195 return def; 196 } 197 } 198 199 /** 200 * Service method to handle attribute input of 201 * double values. Not being present 202 * is not an error. Not parsing (which shouldn't happen due to 203 * the XML Schema checks) invokes the default error handler. 204 * @param element the element to parse. 205 * @param name element attribute name. 206 * @param def default value if name not present in element. 207 * @return double value of attribute, else default if not present or error. 208 */ 209 public final double getAttributeDoubleValue(@Nonnull Element element, @Nonnull String name, double def) { 210 Attribute a; 211 String val = null; 212 try { 213 a = element.getAttribute(name); 214 if (a == null) { 215 return def; 216 } 217 val = a.getValue(); 218 return a.getDoubleValue(); 219 } catch (Exception ex) { 220 log.debug("caught exception", ex); 221 ErrorMemo em = new ErrorMemo(this, 222 "getAttributeDoubleValue threw exception", 223 "element: "+element.getName(), 224 "attribute: "+name, 225 "value: "+val, 226 ex); 227 getExceptionHandler().handle(em); 228 return def; 229 } 230 } 231 232 /** 233 * Service method to handle attribute input of 234 * float values. Not being present 235 * is not an error. Not parsing (which shouldn't happen due to 236 * the XML Schema checks) invokes the default error handler. 237 * 238 * @param element the element to parse. 239 * @param name element attribute name. 240 * @param def default value if name not present in element. 241 * @return float value of attribute, else default if not present or error. 242 */ 243 public final float getAttributeFloatValue(@Nonnull Element element, @Nonnull String name, float def) { 244 Attribute a; 245 String val = null; 246 try { 247 a = element.getAttribute(name); 248 if (a == null) { 249 return def; 250 } 251 val = a.getValue(); 252 return a.getFloatValue(); 253 } catch (Exception ex) { 254 log.debug("caught exception", ex); 255 ErrorMemo em = new ErrorMemo(this, 256 "getAttributeFloatValue threw exception", 257 "element: "+element.getName(), 258 "attribute: "+name, 259 "value: "+val, 260 ex); 261 getExceptionHandler().handle(em); 262 return def; 263 } 264 } 265 266 /** 267 * Base for support of Enum load/store to XML files. 268 * <p> 269 * Public to be usable by adapters in other configXML packages. 270 * @param <T> The ENUM implementation type. 271 */ 272 public abstract static class EnumIO <T extends Enum<T>> { 273 274 /** 275 * Convert an enum value to a String for storage in an XML file. 276 * @param e enum value. 277 * @return storage string. 278 */ 279 @Nonnull 280 public abstract String outputFromEnum(@Nonnull T e); 281 282 /** 283 * Convert a String value from an XML file to an enum value. 284 * @param s storage string 285 * @return enum value. 286 */ 287 @Nonnull 288 public abstract T inputFromString(@CheckForNull String s); 289 290 /** 291 * Convert a JDOM Attribute from an XML file to an enum value 292 * @param a JDOM attribute. 293 * @return enum value. 294 */ 295 @Nonnull 296 public T inputFromAttribute(@Nonnull Attribute a) { 297 return inputFromString(a.getValue()); 298 } 299 } 300 301 /** 302 * Support for Enum I/O to XML using the enum's ordinal numbers in String form.<p> 303 * String or mapped I/LO should he preferred.<p> 304 * This converts to and from ordinal numbers 305 * so the order of definitions in the enum has to 306 * match up with the (former) constant values. 307 * <p> 308 * Public to be usable by adapters in other configXML packages. 309 * @param <T> generic Enum class. 310 */ 311 public static class EnumIoOrdinals <T extends Enum<T>> extends EnumIO<T> { 312 313 public EnumIoOrdinals(@Nonnull Class<T> clazz) { 314 this.clazz = clazz; 315 } 316 final Class<T> clazz; 317 318 /** {@inheritDoc} */ 319 @Override 320 @Nonnull 321 public String outputFromEnum(@Nonnull T e) { 322 int ordinal = e.ordinal(); 323 return Integer.toString(ordinal); 324 } 325 326 /** {@inheritDoc} */ 327 @Override 328 @Nonnull 329 public T inputFromString(@CheckForNull String s) { 330 if (s == null) { 331 log.error("from String null get {} for {}", clazz.getEnumConstants()[0].name(), clazz); 332 return clazz.getEnumConstants()[0]; 333 } 334 335 try { 336 int content = Integer.parseInt(s); 337 return clazz.getEnumConstants()[content]; 338 } catch (RuntimeException e) { 339 log.error("from String {} get {} for {}", s, clazz.getEnumConstants()[0].name(), clazz, e); 340 return clazz.getEnumConstants()[0]; 341 } 342 } 343 344 } 345 346 /** 347 * Support for Enum I/O to XML using the enum's element names. 348 * @param <T> generic enum class. 349 */ 350 public static class EnumIoNames <T extends Enum<T>> extends EnumIO<T> { // public to be usable by adapters in other configXML packages 351 352 /** 353 * This constructor converts to and from strings 354 * using the enum element names. 355 * @param clazz enum class. 356 */ 357 public EnumIoNames(@Nonnull Class<T> clazz) { 358 this.clazz = clazz; 359 360 mapToEnum = new HashMap<>(); 361 for (T t : clazz.getEnumConstants() ) { 362 mapToEnum.put(t.name(), t); 363 } 364 365 } 366 367 final Class<T> clazz; 368 final Map<String, T> mapToEnum; 369 370 /** {@inheritDoc} */ 371 @Override 372 @Nonnull 373 public String outputFromEnum(@Nonnull T e) { 374 String retval = e.name(); 375 log.trace("from {} make String {} for {}", e, retval, clazz); 376 return retval; 377 } 378 379 /** {@inheritDoc} */ 380 @Override 381 @Nonnull 382 public T inputFromString(@CheckForNull String s) { 383 if (s == null) { 384 log.error("from String null get {} for {}", clazz.getEnumConstants()[0].name(), clazz); 385 return clazz.getEnumConstants()[0]; 386 } 387 388 T retval = mapToEnum.get(s); 389 if (retval == null) { 390 log.error("from String {} get {} for {}", s, clazz.getEnumConstants()[0].name(), clazz); 391 return clazz.getEnumConstants()[0]; 392 } else { 393 log.trace("from String {} get {} for {}", s, retval, clazz); 394 return retval; 395 } 396 } 397 } 398 399 /** 400 * Support for Enum I/O to XML using the enum's element names; 401 * for backward compatibility, it will also accept ordinal 402 * numbers when reading. 403 * @param <T> generic enum class. 404 */ 405 public static class EnumIoNamesNumbers <T extends Enum<T>> extends EnumIoNames<T> { // public to be usable by adapters in other configXML packages 406 407 /** 408 * This constructor converts to and from strings 409 * using the enum element names and, on read only, ordinal numbers 410 * @param clazz enum class type. 411 */ 412 public EnumIoNamesNumbers(@Nonnull Class<T> clazz) { 413 super(clazz); 414 415 for (T t : clazz.getEnumConstants() ) { // append to existing map 416 mapToEnum.put(Integer.toString(t.ordinal()), t); 417 } 418 419 } 420 } 421 422 /** 423 * Support for Enum I/O to XML using explicit mapping.<p> 424 * This converts to and from ordinal numbers 425 * so the order of definitions in the enum has to 426 * match up with the (former) constant values. 427 * @param <T> generic enum class. 428 */ 429 public static class EnumIoMapped <T extends Enum<T>> extends EnumIO<T> { // public to be usable by adapters in other configXML packages 430 431 /** 432 * @param clazz enum class. 433 * @param mapToEnum Substitutes an explicit mapping 434 * for mapping from Strings to enums; this could allow e.g. 435 * accepting both name and number versions. Multiple entries 436 * are OK: this can map both "1" and "Foo" to Foo for past-schema support. 437 * @param mapFromEnum Substitutes an explicit mapping 438 * enum entries to Strings; this determines what will 439 * be written out. 440 */ 441 public EnumIoMapped(@Nonnull Class<T> clazz, @Nonnull Map<String, T> mapToEnum, @Nonnull Map<T, String> mapFromEnum) { 442 this.clazz = clazz; 443 444 this.mapToEnum = mapToEnum; 445 446 this.mapFromEnum = mapFromEnum; 447 } 448 449 /** 450 * @param clazz enum class. 451 * @param mapToEnum Substitutes an explicit mapping 452 * for mapping from Strings to enums; this could allow e.g. 453 * accepting both name and number versions. Multiple entries 454 * are OK: this can map both "1" and "Foo" to Foo for past-schema support. 455 * The mapping from enums to Strings uses the enum names. 456 */ 457 public EnumIoMapped(@Nonnull Class<T> clazz, @Nonnull Map<String, T> mapToEnum) { 458 this.clazz = clazz; 459 460 this.mapToEnum = mapToEnum; 461 462 this.mapFromEnum = new HashMap<>(); 463 for (T t : clazz.getEnumConstants() ) { 464 this.mapFromEnum.put(t, t.name()); 465 } 466 } 467 468 final Class<T> clazz; 469 final Map<T, String> mapFromEnum; 470 final Map<String, T> mapToEnum; 471 472 /** {@inheritDoc} */ 473 @Override 474 @Nonnull 475 public String outputFromEnum(@Nonnull T e) { 476 String retval = mapFromEnum.get(e); 477 log.trace("from {} make String {} for {}", e, retval, clazz); 478 return retval; 479 } 480 481 /** {@inheritDoc} */ 482 @Override 483 @Nonnull 484 public T inputFromString(@CheckForNull String s) { 485 if (s == null) { 486 log.error("from String null get {} for {}", clazz.getEnumConstants()[0].name(), clazz); 487 return clazz.getEnumConstants()[0]; 488 } 489 490 try { 491 T retval = mapToEnum.get(s); 492 if (retval == null) { 493 log.error("from String {} get {} for {}", s, clazz.getEnumConstants()[0].name(), clazz); 494 return clazz.getEnumConstants()[0]; 495 } else { 496 log.trace("from String {} get {} for {}", s, retval, clazz); 497 return retval; 498 } 499 } catch (RuntimeException e) { 500 log.error("from String {} get {} for {}", s, clazz.getEnumConstants()[0].name(), clazz, e); 501 return clazz.getEnumConstants()[0]; 502 } 503 } 504 } 505 506 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractXmlAdapter.class); 507}