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