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}