001/* 002 The contents of this file are subject to the Mozilla Public License Version 1.1 003 (the "License"); you may not use this file except in compliance with the License. 004 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005 Software distributed under the License is distributed on an "AS IS" basis, 006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007 specific language governing rights and limitations under the License. 008 009 The Original Code is "Unmodifiable.java". Description: 010 "Factory for unmodifiable wrappers of model classes" 011 012 The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 2015. All Rights Reserved. 014 015 Contributor(s): ______________________________________. 016 017 Alternatively, the contents of this file may be used under the terms of the 018 GNU General Public License (the "GPL"), in which case the provisions of the GPL are 019 applicable instead of those above. If you wish to allow use of your version of this 020 file only under the terms of the GPL and not to allow others to use your version 021 of this file under the MPL, indicate your decision by deleting the provisions above 022 and replace them with the notice and other provisions required by the GPL License. 023 If you do not delete the provisions above, a recipient may use your version of 024 this file under either the MPL or the GPL. 025 */ 026 027package ca.uhn.hl7v2.model; 028 029import ca.uhn.hl7v2.AcknowledgmentCode; 030import ca.uhn.hl7v2.HL7Exception; 031import ca.uhn.hl7v2.HapiContext; 032import ca.uhn.hl7v2.Location; 033import ca.uhn.hl7v2.parser.Parser; 034 035import java.io.IOException; 036 037/** 038 * A static helper class that allows to obtain unmodifiable message wrappers, i.e. all modification to these wrappers 039 * result in an {@link java.lang.UnsupportedOperationException} or {@link java.lang.IllegalArgumentException}. 040 * <p/> 041 * Note that these wrappers have no HL7-specific type information, e.g. an {@link UnmodifiableMessage} 042 * just implements {@link ca.uhn.hl7v2.model.Message} but not some concrete event type. It is possible 043 * to use the {@link ca.uhn.hl7v2.util.Terser}, generic Getter methods, message iterators or visitors. 044 * All structures or types returned by these methods will be unmodifiable as well. 045 * <p/> 046 * Also note that the original message does not automatically become immutable. 047 */ 048public final class Unmodifiable { 049 050 private Unmodifiable() { 051 } 052 053 054 /** 055 * Returns an unmodifiable wrapper around the message. When accessing structures or types of the 056 * {@link UnmodifiableMessage}, they will be unmodifiable as well. Copying these message parts into a regular 057 * message should therefore be done using {@link ca.uhn.hl7v2.util.DeepCopy}. 058 * 059 * @param msg message to be wrapped 060 * @return unmodifiable message wrapper 061 */ 062 public static Message unmodifiableMessage(Message msg) { 063 return isUnmodifiable(msg) ? msg : new UnmodifiableMessage(msg); 064 } 065 066 /** 067 * Parses the string to an {@link UnmodifiableMessage} using the specific HapiContext. When accessing structures or types of the 068 * returned message, they will be unmodifiable as well. The returned message caches the original message string, 069 * which is returned when calling {@link Message#encode()} or {@link Message#toString()}. 070 * 071 * @param context HapiContext 072 * @param s message string 073 * @return unmodifiable message wrapper 074 * @throws HL7Exception if parsing fails 075 */ 076 public static Message unmodifiableMessage(HapiContext context, String s) throws HL7Exception { 077 Message msg = context.getGenericParser().parse(s); 078 return new UnmodifiableMessage(msg, s); 079 } 080 081 /** 082 * Returns true if the message instance (or a part thereof) is unmodifiable 083 * 084 * @param o something HAPI-related 085 * @return true if unmodifiable 086 */ 087 public static boolean isUnmodifiable(Object o) { 088 return o instanceof UnmodifiableModel; 089 } 090 091 092 @SuppressWarnings("unchecked") 093 private static <T extends Type> T unmodifiableType(T type) { 094 if (isUnmodifiable(type)) return type; 095 if (type instanceof Primitive) return (T) new UnmodifiablePrimitive((Primitive) type); 096 if (type instanceof Composite) return (T) new UnmodifiableComposite((Composite) type); 097 return (T) new UnmodifiableVaries((Variable) type); 098 } 099 100 @SuppressWarnings("unchecked") 101 private static <T extends Structure> T unmodifiableStructure(T structure) { 102 if (isUnmodifiable(structure)) return structure; 103 if (structure instanceof Message) return (T) new UnmodifiableMessage((Message) structure); 104 if (structure instanceof Group) return (T) new UnmodifiableGroup((Group) structure); 105 return (T) new UnmodifiableSegment((Segment) structure); 106 } 107 108 @SuppressWarnings("unchecked") 109 private static <T extends MessageVisitor> T unmodifiableVisitor(T visitor) { 110 return isUnmodifiable(visitor) ? visitor : (T) new UnmodifiableMessageVisitor(visitor); 111 } 112 113 @SuppressWarnings("unchecked") 114 private static ExtraComponents unmodifiableExtraComponents(ExtraComponents ec) { 115 return isUnmodifiable(ec) ? ec : new UnmodifiableExtraComponents(ec); 116 } 117 118 /** 119 * Marker interface for unmodifiable message (parts) 120 */ 121 private static interface UnmodifiableModel { 122 } 123 124 125 private static class Delegating<S> { 126 private S delegate; 127 128 protected Delegating(S delegate) { 129 this.delegate = delegate; 130 } 131 132 public S getDelegate() { 133 return delegate; 134 } 135 136 @Override 137 public String toString() { 138 return delegate.toString(); 139 } 140 141 /** 142 * Unmodifiable structures should compare against their modifiable 143 * delegate. Otherwise a number of iterators and finders would 144 * not work properly 145 * 146 * @param o 147 * @return 148 */ 149 @Override 150 public boolean equals(Object o) { 151 if (this == o) return true; 152 if (o instanceof Delegating) { 153 Delegating that = (Delegating) o; 154 return delegate.equals(that.delegate); 155 } 156 if (o.getClass().isAssignableFrom(delegate.getClass())) { 157 return delegate.equals(o); 158 } 159 return false; 160 } 161 162 @Override 163 public int hashCode() { 164 return delegate.hashCode(); 165 } 166 } 167 168 private static class UnmodifiableVisitable<S extends Visitable> extends Delegating<S> implements Visitable, UnmodifiableModel { 169 170 public UnmodifiableVisitable(S delegate) { 171 super(delegate); 172 } 173 174 public boolean accept(MessageVisitor visitor, Location currentLocation) throws HL7Exception { 175 return getDelegate().accept(unmodifiableVisitor(visitor), currentLocation); 176 } 177 178 public Location provideLocation(Location parentLocation, int index, int repetition) { 179 return getDelegate().provideLocation(parentLocation, index, repetition); 180 } 181 182 public boolean isEmpty() throws HL7Exception { 183 return getDelegate().isEmpty(); 184 } 185 } 186 187 188 private abstract static class UnmodifiableStructure<S extends Structure> extends UnmodifiableVisitable<S> 189 implements Structure { 190 191 private UnmodifiableStructure(S delegate) { 192 super(delegate); 193 } 194 195 public Message getMessage() { 196 return getDelegate().getMessage(); 197 } 198 199 public String getName() { 200 return getDelegate().getName(); 201 } 202 203 public Group getParent() { 204 return unmodifiableStructure(getDelegate().getParent()); 205 } 206 207 } 208 209 private static class UnmodifiableSegment<S extends Segment> extends UnmodifiableStructure<S> implements Segment { 210 211 public UnmodifiableSegment(S delegate) { 212 super(delegate); 213 } 214 215 public String encode() throws HL7Exception { 216 return getDelegate().encode(); 217 } 218 219 public Type[] getField(int number) throws HL7Exception { 220 if (number < 1 || number > numFields()) { 221 throw new IllegalArgumentException(String.format("Cannot add field with index %d to unmodifiable segment %s " + 222 " - there are currently only %d fields.", number, getName(), numFields())); 223 } 224 Type[] types = getDelegate().getField(number); 225 Type[] unmodifiableTypes = new Type[types.length]; 226 if (types.length > 0) { 227 for (int i = 0; i < types.length; i++) { 228 unmodifiableTypes[i] = unmodifiableType(types[i]); 229 } 230 } 231 return unmodifiableTypes; 232 } 233 234 public Type getField(int number, int rep) throws HL7Exception { 235 Type[] types = getField(number); 236 if (rep >= types.length) { 237 throw new IllegalArgumentException(String.format("Cannot add repetition with index %d to unmodifiable field %d " + 238 " - there are currently only %d fields.", rep, number, types.length)); 239 } 240 return types[rep]; 241 } 242 243 public int getLength(int number) throws HL7Exception { 244 return getDelegate().getLength(number); 245 } 246 247 public int getMaxCardinality(int number) throws HL7Exception { 248 return getDelegate().getMaxCardinality(number); 249 } 250 251 public String[] getNames() { 252 return getDelegate().getNames(); 253 } 254 255 public boolean isRequired(int number) throws HL7Exception { 256 return getDelegate().isRequired(number); 257 } 258 259 public int numFields() { 260 return getDelegate().numFields(); 261 } 262 263 public void parse(String string) throws HL7Exception { 264 throw new UnsupportedOperationException("This segment is unmodifiable"); 265 } 266 } 267 268 private static class UnmodifiableGroup<S extends Group> extends UnmodifiableStructure<S> implements Group { 269 public UnmodifiableGroup(S delegate) { 270 super(delegate); 271 } 272 273 public Structure[] getAll(String name) throws HL7Exception { 274 Structure[] structures = getDelegate().getAll(name); 275 Structure[] unmodifiableStructures = new Structure[structures.length]; 276 if (structures.length > 0) { 277 for (int i = 0; i < structures.length; i++) { 278 unmodifiableStructures[i] = unmodifiableStructure(structures[i]); 279 } 280 } 281 return unmodifiableStructures; 282 } 283 284 public Structure get(String name) throws HL7Exception { 285 return get(name, 0); 286 } 287 288 /** 289 * This method does NOT append a trailing repetition, but will instead throw an {@link IndexOutOfBoundsException} 290 * 291 * @param name name of the structure 292 * @param rep repetition (zero-based) 293 * @return element of the repeating structure 294 * @throws HL7Exception if name does not exist 295 * @throws IndexOutOfBoundsException if repetition does not exist 296 */ 297 public Structure get(String name, int rep) throws HL7Exception { 298 return getAll(name)[rep]; 299 } 300 301 public boolean isRequired(String name) throws HL7Exception { 302 return getDelegate().isRequired(name); 303 } 304 305 public boolean isRepeating(String name) throws HL7Exception { 306 return getDelegate().isRepeating(name); 307 } 308 309 public boolean isChoiceElement(String name) throws HL7Exception { 310 return getDelegate().isChoiceElement(name); 311 } 312 313 public boolean isGroup(String name) throws HL7Exception { 314 return getDelegate().isGroup(name); 315 } 316 317 public String[] getNames() { 318 return getDelegate().getNames(); 319 } 320 321 public Class<? extends Structure> getClass(String name) { 322 return getDelegate().getClass(name); 323 } 324 325 public String addNonstandardSegment(String name) throws HL7Exception { 326 throw new UnsupportedOperationException("This group is unmodifiable"); 327 } 328 329 public String addNonstandardSegment(String name, int theIndex) throws HL7Exception { 330 throw new UnsupportedOperationException("This group is unmodifiable"); 331 } 332 } 333 334 private static class UnmodifiableMessage extends UnmodifiableGroup<Message> implements Message { 335 336 private String originalMessage; 337 338 public UnmodifiableMessage(Message delegate, String originalMessage) { 339 this(delegate); 340 this.originalMessage = originalMessage; 341 } 342 343 public UnmodifiableMessage(Message delegate) { 344 super(delegate); 345 } 346 347 public String getVersion() { 348 return getDelegate().getVersion(); 349 } 350 351 public Character getFieldSeparatorValue() throws HL7Exception { 352 return getDelegate().getFieldSeparatorValue(); 353 } 354 355 public String getEncodingCharactersValue() throws HL7Exception { 356 return getDelegate().getEncodingCharactersValue(); 357 } 358 359 public void setParser(Parser parser) { 360 throw new UnsupportedOperationException("This message is unmodifiable"); 361 } 362 363 public Parser getParser() { 364 return getDelegate().getParser(); 365 } 366 367 public void parse(String string) throws HL7Exception { 368 throw new UnsupportedOperationException("This message is unmodifiable"); 369 } 370 371 public String encode() throws HL7Exception { 372 return originalMessage != null ? originalMessage : getDelegate().encode(); 373 } 374 375 public Message generateACK() throws HL7Exception, IOException { 376 return getDelegate().generateACK(); 377 } 378 379 public Message generateACK(String theAcknowlegementCode, HL7Exception theException) throws HL7Exception, IOException { 380 return getDelegate().generateACK(theAcknowlegementCode, theException); 381 } 382 383 public Message generateACK(AcknowledgmentCode theAcknowlegementCode, HL7Exception theException) throws HL7Exception, IOException { 384 return getDelegate().generateACK(theAcknowlegementCode, theException); 385 } 386 387 public String printStructure() throws HL7Exception { 388 return getDelegate().printStructure(); 389 } 390 } 391 392 private abstract static class UnmodifiableType<T extends Type> extends UnmodifiableVisitable<T> 393 implements Type { 394 395 public UnmodifiableType(T delegate) { 396 super(delegate); 397 } 398 399 public String getName() { 400 return getDelegate().getName(); 401 } 402 403 public ExtraComponents getExtraComponents() { 404 return unmodifiableExtraComponents(getDelegate().getExtraComponents()); 405 } 406 407 public Message getMessage() { 408 return unmodifiableMessage(getDelegate().getMessage()); 409 } 410 411 public void parse(String string) throws HL7Exception { 412 throw new UnsupportedOperationException("This type is unmodifiable"); 413 } 414 415 public String encode() throws HL7Exception { 416 return getDelegate().encode(); 417 } 418 419 public void clear() { 420 throw new UnsupportedOperationException("This type is unmodifiable"); 421 } 422 423 public Location provideLocation(Location parentLocation, int index, int repetition) { 424 return getDelegate().provideLocation(parentLocation, index, repetition); 425 } 426 427 } 428 429 private static class UnmodifiablePrimitive extends UnmodifiableType<Primitive> implements Primitive { 430 431 public UnmodifiablePrimitive(Primitive delegate) { 432 super(delegate); 433 } 434 435 public String getValue() { 436 return getDelegate().getValue(); 437 } 438 439 public void setValue(String value) throws DataTypeException { 440 throw new UnsupportedOperationException("This Primitive is unmodifiable"); 441 } 442 } 443 444 private static class UnmodifiableComposite extends UnmodifiableType<Composite> implements Composite { 445 446 public UnmodifiableComposite(Composite delegate) { 447 super(delegate); 448 } 449 450 public Type[] getComponents() { 451 Type[] types = getDelegate().getComponents(); 452 Type[] unmodifiableTypes = new Type[types.length]; 453 if (types.length > 0) { 454 for (int i = 0; i < types.length; i++) { 455 unmodifiableTypes[i] = unmodifiableType(types[i]); 456 } 457 } 458 return unmodifiableTypes; 459 } 460 461 public Type getComponent(int number) throws DataTypeException { 462 Type type = getDelegate().getComponent(number); 463 return unmodifiableType(type); 464 } 465 466 } 467 468 private static class UnmodifiableVaries extends UnmodifiableType<Variable> implements Variable { 469 470 public UnmodifiableVaries(Variable delegate) { 471 super(delegate); 472 } 473 474 public Type getData() { 475 return unmodifiableType(getDelegate().getData()); 476 } 477 478 public void setData(Type data) throws DataTypeException { 479 throw new UnsupportedOperationException("This Varies is unmodifiable"); 480 } 481 482 } 483 484 private static class UnmodifiableExtraComponents extends ExtraComponents { 485 486 private ExtraComponents delegate; 487 488 public UnmodifiableExtraComponents(ExtraComponents delegate) { 489 super(delegate.getMessage()); 490 this.delegate = delegate; 491 } 492 493 @Override 494 public int numComponents() { 495 return delegate.numComponents(); 496 } 497 498 @Override 499 public boolean isEmpty() throws HL7Exception { 500 return delegate.isEmpty(); 501 } 502 503 @Override 504 public Message getMessage() { 505 return unmodifiableMessage(delegate.getMessage()); 506 } 507 508 @Override 509 public String toString() { 510 return delegate.toString(); 511 } 512 513 @Override 514 public Variable getComponent(int comp) { 515 if (comp >= numComponents()) { 516 throw new IllegalArgumentException(String.format( 517 "Extra Component with index %d is not available and cannot be added to unmodifiable type", comp)); 518 } 519 return unmodifiableType(delegate.getComponent(comp)); 520 } 521 522 @Override 523 void clear() { 524 throw new UnsupportedOperationException("This ExtraComponents is unmodifiable"); 525 } 526 } 527 528 529 private static class UnmodifiableMessageVisitor extends Delegating<MessageVisitor> implements MessageVisitor, UnmodifiableModel { 530 531 532 public UnmodifiableMessageVisitor(MessageVisitor delegate) { 533 super(delegate); 534 } 535 536 public boolean start(Message message) throws HL7Exception { 537 return getDelegate().start(unmodifiableMessage(message)); 538 } 539 540 public boolean end(Message message) throws HL7Exception { 541 return getDelegate().end(unmodifiableMessage(message)); 542 } 543 544 public boolean start(Group group, Location location) throws HL7Exception { 545 return getDelegate().start(unmodifiableStructure(group), location); 546 } 547 548 public boolean end(Group group, Location location) throws HL7Exception { 549 return getDelegate().end(unmodifiableStructure(group), location); 550 } 551 552 public boolean start(Segment segment, Location location) throws HL7Exception { 553 return getDelegate().start(unmodifiableStructure(segment), location); 554 } 555 556 public boolean end(Segment segment, Location location) throws HL7Exception { 557 return getDelegate().end(unmodifiableStructure(segment), location); 558 } 559 560 public boolean start(Field field, Location location) throws HL7Exception { 561 // Field is immutable anyway 562 return getDelegate().start(field, location); 563 } 564 565 public boolean end(Field field, Location location) throws HL7Exception { 566 // Field is immutable anyway 567 return getDelegate().end(field, location); 568 } 569 570 public boolean start(Composite type, Location location) throws HL7Exception { 571 return getDelegate().start(unmodifiableType(type), location); 572 } 573 574 public boolean end(Composite type, Location location) throws HL7Exception { 575 return getDelegate().end(unmodifiableType(type), location); 576 } 577 578 public boolean visit(Primitive type, Location location) throws HL7Exception { 579 return getDelegate().visit(unmodifiableType(type), location); 580 } 581 } 582 583}