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