001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.data;
040
041import org.dcm4che3.data.IOD.DataElement;
042import org.dcm4che3.data.IOD.DataElementType;
043import org.dcm4che3.io.BulkDataDescriptor;
044import org.dcm4che3.io.DicomEncodingOptions;
045import org.dcm4che3.io.DicomInputStream;
046import org.dcm4che3.io.DicomOutputStream;
047import org.dcm4che3.util.*;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051import java.io.IOException;
052import java.io.ObjectInputStream;
053import java.io.ObjectOutputStream;
054import java.io.Serializable;
055import java.util.*;
056import java.util.regex.Pattern;
057
058/**
059 * @author Gunter Zeilinger <gunterze@gmail.com>
060 */
061public class Attributes implements Serializable {
062
063
064
065    public interface Visitor {
066        boolean visit(Attributes attrs, int tag, VR vr, Object value)
067                throws Exception;
068    }
069
070    private static final Logger LOG = 
071            LoggerFactory.getLogger(Attributes.class);
072
073    private static final int INIT_CAPACITY = 16;
074    private static final int TO_STRING_LIMIT = 50;
075    private static final int TO_STRING_WIDTH = 78;
076    private transient Attributes parent;
077    private transient String parentSequencePrivateCreator;
078    private transient int parentSequenceTag;
079    private transient int[] tags;
080    private transient VR[] vrs;
081    private transient Object[] values;
082    private transient int size;
083    private transient SpecificCharacterSet cs;
084    private transient TimeZone tz;
085    private transient int length = -1;
086    private transient int[] groupLengths;
087    private transient int groupLengthIndex0;
088
089    private final boolean bigEndian;
090    private long itemPosition = -1;
091    private boolean containsSpecificCharacterSet;
092    private boolean containsTimezoneOffsetFromUTC;
093    private Map<String, Object> properties;
094    private TimeZone defaultTimeZone;
095
096    public Attributes() {
097        this(false, INIT_CAPACITY);
098    }
099
100    public Attributes(boolean bigEndian) {
101        this(bigEndian, INIT_CAPACITY);
102    }
103
104    public Attributes(int initialCapacity) {
105        this(false, initialCapacity);
106    }
107
108    public Attributes(boolean bigEndian, int initialCapacity) {
109        this.bigEndian = bigEndian;
110        init(initialCapacity);
111    }
112
113    public void clear() {
114        size = 0;
115        Arrays.fill(tags, 0);
116        Arrays.fill(vrs, null);
117        Arrays.fill(values, null);
118    }
119
120    private void init(int initialCapacity) {
121        this.tags = new int[initialCapacity];
122        this.vrs = new VR[initialCapacity];
123        this.values = new Object[initialCapacity];
124    }
125
126    public Attributes(Attributes other) {
127        this(other, other.bigEndian);
128    }
129
130    public Attributes(Attributes other, boolean bigEndian) {
131        this(bigEndian, other.size);
132        if (other.properties != null)
133            properties = new HashMap<String, Object>(other.properties);
134        addAll(other);
135    }
136
137    public Attributes(Attributes other, int... selection) {
138        this(other, other.bigEndian, selection);
139    }
140
141    public Attributes(Attributes other, boolean bigEndian, int... selection) {
142        this(bigEndian, selection.length);
143        if (other.properties != null)
144            properties = new HashMap<String, Object>(other.properties);
145        addSelected(other, selection);
146    }
147
148    public Attributes(Attributes other, Attributes selection) {
149        this(selection.size());
150        if (other.properties != null)
151            properties = new HashMap<String, Object>(other.properties);
152        addSelected(other, selection);
153    }
154
155    public Attributes(Attributes other, boolean bigEndian, Attributes selection) {
156        this(bigEndian, selection.size());
157        if (other.properties != null)
158            properties = new HashMap<String, Object>(other.properties);
159        addSelected(other, selection);
160    }
161
162    public Map<String, Object> getProperties() {
163        return properties;
164    }
165
166    public void setProperties(Map<String, Object> properties) {
167        this.properties = properties;
168    }
169
170    public Object getProperty(String key, Object defVal) {
171        if (properties == null)
172            return defVal;
173
174        Object val = properties.get(key);
175        return val != null ? val : defVal;
176    }
177
178    public Object setProperty(String key, Object value) {
179        if (properties == null)
180            properties = new HashMap<String, Object>();
181        return properties.put(key, value);
182    }
183
184    public Object clearProperty(String key) {
185        return properties != null ? properties.remove(key) : null;
186    }
187
188    public final boolean isRoot() {
189        return parent == null;
190    }
191
192    public Attributes getRoot() {
193        return isRoot() ? this : parent.getRoot();
194    }
195
196    public final int getLevel() {
197        return isRoot() ? 0 : 1 + parent.getLevel();
198    }
199
200    public final boolean bigEndian() {
201        return bigEndian;
202    }
203
204    public final Attributes getParent() {
205        return parent;
206    }
207
208    public String getParentSequencePrivateCreator() {
209        return parentSequencePrivateCreator;
210    }
211
212    public int getParentSequenceTag() {
213        return parentSequenceTag;
214    }
215
216    public final int getLength() {
217        return length;
218    }
219
220    Attributes setParent(Attributes parent, String parentSequencePrivateCreator, int parentSequenceTag) {
221        if (parent != null) {
222            if (parent.bigEndian != bigEndian)
223                throw new IllegalArgumentException(
224                    "Endian of Item must match Endian of parent Data Set");
225            if (this.parent != null)
226                throw new IllegalArgumentException(
227                    "Item already contained by Sequence");
228            if (!containsSpecificCharacterSet)
229                cs = null;
230            if (!containsTimezoneOffsetFromUTC)
231                tz = null;
232        }
233        this.parent = parent;
234        this.parentSequencePrivateCreator = parentSequencePrivateCreator;
235        this.parentSequenceTag = parentSequenceTag;
236        return this;
237    }
238
239    public final long getItemPosition() {
240        return itemPosition;
241    }
242
243    public final void setItemPosition(long itemPosition) {
244        this.itemPosition = itemPosition;
245    }
246
247    public final boolean isEmpty() {
248        return size == 0;
249    }
250
251    public final int size() {
252        return size;
253    }
254
255    public ItemPointer[] itemPointers() {
256        return itemPointers(0);
257    }
258
259    private ItemPointer[] itemPointers(int n) {
260        if (parent == null)
261            return new ItemPointer[n];
262
263        ItemPointer[] itemPointers = parent.itemPointers(n + 1);
264        itemPointers[itemPointers.length - n - 1] =
265                new ItemPointer(parentSequencePrivateCreator, parentSequenceTag, itemIndex());
266        return itemPointers;
267    }
268
269    public int itemIndex() {
270        if (parent == null)
271            return -1;
272
273        Sequence seq = parent.getSequence(parentSequencePrivateCreator, parentSequenceTag);
274        if (seq == null)
275            return -1;
276
277        return seq.indexOf(this);
278    }
279
280    public int[] tags() {
281        return Arrays.copyOf(tags, size);
282    }
283
284    public void trimToSize() {
285        trimToSize(false);
286    }
287
288    public void trimToSize(boolean recursive) {
289        int oldCapacity = tags.length;
290        if (size < oldCapacity) {
291            tags = Arrays.copyOf(tags, size);
292            vrs = Arrays.copyOf(vrs, size);
293            values = Arrays.copyOf(values, size);
294        }
295        if (recursive)
296            for (Object value : values) {
297                if (value instanceof Sequence) {
298                    ((Sequence) value).trimToSize(recursive);
299                } else if (value instanceof Fragments)
300                    ((Fragments) value).trimToSize();
301            }
302    }
303
304    public void internalizeStringValues(boolean decode) {
305        SpecificCharacterSet cs = getSpecificCharacterSet();
306        for (int i = 0; i < values.length; i++) {
307            VR vr = vrs[i];
308            Object value = values[i];
309            if (vr.isStringType()) {
310                if (value instanceof byte[]) {
311                    if (!decode)
312                        continue;
313                    value = vr.toStrings((byte[]) value, bigEndian, cs);
314                }
315                if (value instanceof String)
316                    values[i] = ((String) value).intern();
317                else if (value instanceof String[]) {
318                    String[] ss = (String[]) value;
319                    for (int j = 0; j < ss.length; j++)
320                        ss[j] = ss[j].intern();
321                }
322            } else if (value instanceof Sequence)
323                for (Attributes item : (Sequence) value)
324                    item.internalizeStringValues(decode);
325        }
326    }
327
328    private void decodeStringValuesUsingSpecificCharacterSet() {
329        Object value;
330        VR vr;
331        SpecificCharacterSet cs = getSpecificCharacterSet();
332        for (int i = 0; i < size; i++) {
333            value = values[i];
334            if (value instanceof Sequence) {
335                for (Attributes item : (Sequence) value)
336                    item.decodeStringValuesUsingSpecificCharacterSet();
337            } else if ((vr = vrs[i]).useSpecificCharacterSet())
338                if (value instanceof byte[])
339                    values[i] =
340                        vr.toStrings((byte[]) value, bigEndian, cs);
341        }
342    }
343
344    private void ensureCapacity(int minCapacity) {
345        int oldCapacity = tags.length;
346        if (minCapacity > oldCapacity) {
347            int newCapacity = Math.max(minCapacity, oldCapacity << 1);
348            tags = Arrays.copyOf(tags, newCapacity);
349            vrs = Arrays.copyOf(vrs, newCapacity);
350            values = Arrays.copyOf(values, newCapacity);
351        }
352    }
353
354    public Attributes getNestedDataset(int sequenceTag) {
355        return getNestedDataset(null, sequenceTag, 0);
356    }
357
358    public Attributes getNestedDataset(int sequenceTag, int itemIndex) {
359        return getNestedDataset(null, sequenceTag, itemIndex);
360    }
361
362    public Attributes getNestedDataset(String privateCreator, int sequenceTag) {
363        return getNestedDataset(privateCreator, sequenceTag, 0);
364    }
365
366    public Attributes getNestedDataset(String privateCreator, int sequenceTag, int itemIndex) {
367        Object value = getValue(privateCreator, sequenceTag);
368        if (!(value instanceof Sequence))
369            return null;
370
371        Sequence sq = (Sequence) value;
372        if (itemIndex >= sq.size())
373            return null;
374
375        return sq.get(itemIndex);
376    }
377
378    public Attributes getNestedDataset(ItemPointer... itemPointers) {
379        Attributes item = this;
380        for (ItemPointer ip : itemPointers) {
381            Object value = item.getValue(ip.privateCreator, ip.sequenceTag);
382            if (!(value instanceof Sequence))
383                return null;
384
385            Sequence sq = (Sequence) value;
386            if (ip.itemIndex >= sq.size())
387                return null;
388
389            item = sq.get(ip.itemIndex);
390        }
391        return item;
392    }
393
394    private int indexForInsertOf(int tag) {
395        return size == 0 ? -1
396                : tags[size-1] < tag ? -(size+1)
397                        : indexOf(tag);
398    }
399
400    private int indexOf(int tag) {
401        return Arrays.binarySearch(tags, 0, size, tag);
402    }
403
404    private int indexOf(String privateCreator, int tag) {
405        if (privateCreator != null) {
406            int creatorTag = creatorTagOf(privateCreator, tag, false);
407            if (creatorTag == -1)
408                return -1;
409            tag = TagUtils.toPrivateTag(creatorTag, tag);
410        }
411        return indexOf(tag);
412    }
413
414    /**
415     * resolves to the actual private tag,
416     * given a private tag with placeholers (like 0011,xx13)
417     */
418    public int tagOf(String privateCreator, int tag) {
419        if (privateCreator != null) {
420            int creatorTag = creatorTagOf(privateCreator, tag, false);
421            if (creatorTag == -1)
422                return -1;
423            tag = TagUtils.toPrivateTag(creatorTag, tag);
424        }
425        return tag;
426    }
427
428
429    private int creatorTagOf(String privateCreator, int tag, boolean reserve) {
430        if (!TagUtils.isPrivateGroup(tag))
431            throw new IllegalArgumentException(TagUtils.toString(tag)
432                    + " is not a private Data Element");
433
434        int group = tag & 0xffff0000;
435        int creatorTag = group | 0x10;
436        int index = indexOf(creatorTag);
437        if (index < 0)
438            index = -index-1;
439        while (index < size && (tags[index] & 0xffffff00) == group) {
440            creatorTag = tags[index];
441            if (vrs[index] == VR.LO) {
442                Object creatorID = decodeStringValue(index);
443                if (privateCreator.equals(creatorID))
444                    return creatorTag;
445            }
446            index++;
447            creatorTag++;
448        }
449        if (!reserve)
450            return -1;
451
452        if ((creatorTag & 0xff00) != 0)
453            throw new IllegalStateException("No free block for Private Element "
454                    + TagUtils.toString(tag));
455        setString(creatorTag, VR.LO, privateCreator);
456        return creatorTag;
457    }
458
459    private Object decodeStringValue(int index) {
460        Object value = values[index];
461        if (value instanceof byte[]) {
462            value = vrs[index].toStrings((byte[]) value, bigEndian,
463                    getSpecificCharacterSet(vrs[index]));
464            if (value instanceof String && ((String) value).isEmpty())
465                value = Value.NULL;
466            values[index] = value;
467        }
468        return value;
469    }
470
471    public SpecificCharacterSet getSpecificCharacterSet(VR vr) {
472        return vr.useSpecificCharacterSet()
473                ? getSpecificCharacterSet()
474                : SpecificCharacterSet.ASCII;
475    }
476
477    private double[] decodeDSValue(int index) {
478        Object value = values[index];
479        if (value == Value.NULL)
480            return ByteUtils.EMPTY_DOUBLES;
481
482        if (value instanceof double[])
483            return (double[]) value;
484
485        double[] ds;
486        if (value instanceof byte[])
487            value = vrs[index].toStrings((byte[]) value, bigEndian,
488                    SpecificCharacterSet.ASCII);
489        if (value instanceof String) {
490            String s = (String) value;
491            if (s.isEmpty()) {
492                values[index] = Value.NULL;
493                return ByteUtils.EMPTY_DOUBLES;
494            }
495            ds = new double[] { StringUtils.parseDS(s) };
496        } else { // value instanceof String[]
497            String[] ss = (String[]) value;
498            ds = new double[ss.length];
499            for (int i = 0; i < ds.length; i++) {
500                String s = ss[i];
501                ds[i] = (s != null && !s.isEmpty())
502                        ? StringUtils.parseDS(s)
503                        : Double.NaN;
504            }
505        }
506        values[index] = ds;
507        return ds;
508    }
509
510    private int[] decodeISValue(int index) {
511        Object value = values[index];
512        if (value == Value.NULL)
513            return ByteUtils.EMPTY_INTS;
514
515        if (value instanceof int[])
516            return (int[]) value;
517
518        int[] is;
519        if (value instanceof byte[])
520            value = vrs[index].toStrings((byte[]) value, bigEndian,
521                    SpecificCharacterSet.ASCII);
522        if (value instanceof String) {
523            String s = (String) value;
524            if (s.isEmpty()) {
525                values[index] = Value.NULL;
526                return ByteUtils.EMPTY_INTS;
527            }
528            is = new int[] { StringUtils.parseIS(s) };
529        } else { // value instanceof String[]
530            String[] ss = (String[]) value;
531            is = new int[ss.length];
532            for (int i = 0; i < is.length; i++) {
533                String s = ss[i];
534                is[i] = (s != null && !s.isEmpty())
535                            ? StringUtils.parseIS(s)
536                            : Integer.MIN_VALUE;
537            }
538        }
539        values[index] = is;
540        return is;
541    }
542
543    private void updateVR(int index, VR vr) {
544        VR prev = vrs[index];
545        if (vr == prev)
546            return;
547
548        Object value = values[index];
549        if (!(value == Value.NULL
550                || value instanceof byte[]
551                || vr.isStringType() 
552                    && (value instanceof String 
553                    || value instanceof String[])))
554            throw new IllegalStateException("value instanceof " + value.getClass());
555
556        vrs[index] = vr;
557    }
558
559    private static boolean isEmpty(Object value) {
560        return (value instanceof Value) && ((Value) value).isEmpty();
561    }
562
563    public boolean contains(int tag) {
564        return indexOf(tag) >= 0;
565    }
566
567    public boolean contains(String privateCreator, int tag) {
568        return indexOf(privateCreator, tag) >= 0;
569    }
570
571    public boolean containsValue(int tag) {
572        return containsValue(null, tag);
573    }
574
575    public boolean containsValue(String privateCreator, int tag) {
576        int index = indexOf(privateCreator, tag);
577        return index >= 0 
578                && !isEmpty(vrs[index].isStringType()
579                        ? decodeStringValue(index)
580                        : values[index]);
581    }
582
583    public String privateCreatorOf(int tag) {
584        if (!TagUtils.isPrivateTag(tag))
585            return null;
586
587        int creatorTag = (tag & 0xffff0000) | ((tag >>> 8) & 0xff);
588        int index = indexOf(creatorTag);
589        if (index < 0 || vrs[index] != VR.LO || values[index] == Value.NULL)
590            return null;
591        
592        Object value = decodeStringValue(index);
593        if (value == Value.NULL)
594            return null;
595
596        return VR.LO.toString(value, false, 0, null);
597    }
598
599    public Object getValue(int tag) {
600        return getValue(null, tag, null);
601    }
602
603    public Object getValue(int tag, VR.Holder vr) {
604        return getValue(null, tag, vr);
605    }
606
607    public Object getValue(String privateCreator, int tag) {
608        return getValue(privateCreator, tag, null);
609    }
610
611    public Object getValue(String privateCreator, int tag, VR.Holder vr) {
612        int index = indexOf(privateCreator, tag);
613        if (index < 0)
614            return null;
615        
616        if (vr != null)
617            vr.vr = vrs[index];
618        return values[index];
619    }
620
621    public VR getVR(int tag) {
622        return getVR(null, tag);
623    }
624
625    public VR getVR(String privateCreator, int tag) {
626        int index = indexOf(privateCreator, tag);
627        if (index < 0)
628            return null;
629        
630        return vrs[index];
631    }
632
633    public Sequence getSequence(int tag) {
634        return getSequence(null, tag);
635    }
636
637    public Sequence getSequence(String privateCreator, int tag) {
638        int index = indexOf(privateCreator, tag);
639        if (index < 0)
640            return null;
641        
642        Object value = values[index];
643        if (value == Value.NULL)
644            return (Sequence) (values[index] = new Sequence(this, privateCreator, tag, 0));
645        return value instanceof Sequence ? (Sequence) value : null;
646    }
647
648    public byte[] getBytes(int tag) throws IOException {
649        return getBytes(null, tag);
650    }
651
652    public byte[] getBytes(String privateCreator, int tag) throws IOException {
653        int index = indexOf(privateCreator, tag);
654        if (index < 0)
655            return null;
656        
657        Object value = values[index];
658        VR vr = vrs[index];
659        
660        try {
661            if (value instanceof Value)
662                return ((Value) value).toBytes(vr, bigEndian);
663            
664            return vr.toBytes(value, getSpecificCharacterSet(vr));
665        } catch (UnsupportedOperationException e) {
666            LOG.info("Attempt to access {} {} as bytes", TagUtils.toString(tag), vr);
667            return null;
668        }
669    }
670
671    public byte[] getSafeBytes(int tag) {
672        return getSafeBytes(null, tag);
673    }
674
675    public byte[] getSafeBytes(String privateCreator, int tag) {
676        try {
677            return getBytes(privateCreator, tag);
678        } catch (IOException e) {
679            LOG.info("Access " + TagUtils.toString(tag)
680                    + " throws i/o exception", e);
681            return null;
682        }
683    }
684
685    public String getString(int tag) {
686        return getString(null, tag, null, 0, null);
687    }
688
689    public String getString(int tag, String defVal) {
690        return getString(null, tag, null, 0, defVal);
691    }
692
693    public String getString(int tag, int valueIndex) {
694        return getString(null, tag, null, valueIndex, null);
695    }
696
697    public String getString(int tag, int valueIndex, String defVal) {
698        return getString(null, tag, null, valueIndex, defVal);
699    }
700
701    public String getString(String privateCreator, int tag) {
702        return getString(privateCreator, tag, null, 0, null);
703    }
704
705    public String getString(String privateCreator, int tag, String defVal) {
706        return getString(privateCreator, tag, null, 0, defVal);
707    }
708
709    public String getString(String privateCreator, int tag, VR vr) {
710        return getString(privateCreator, tag, vr, 0, null);
711    }
712
713    public String getString(String privateCreator, int tag, VR vr, String defVal) {
714        return getString(privateCreator, tag, vr, 0, defVal);
715    }
716
717    public String getString(String privateCreator, int tag, int valueIndex) {
718        return getString(privateCreator, tag, null, valueIndex, null);
719    }
720
721    public String getString(String privateCreator, int tag, int valueIndex, String defVal) {
722        return getString(privateCreator, tag, null, valueIndex, defVal);
723    }
724
725    public String getString(String privateCreator, int tag, VR vr, int valueIndex) {
726        return getString(privateCreator, tag, vr, valueIndex, null);
727    }
728
729    public String getString(String privateCreator, int tag, VR vr, int valueIndex, String defVal) {
730        int index = indexOf(privateCreator, tag);
731        if (index < 0)
732            return defVal;
733
734        Object value = values[index];
735        if (value == Value.NULL)
736            return defVal;
737
738        if (vr == null)
739            vr = vrs[index];
740        else
741            updateVR(index, vr);
742        if (vr.isStringType()) {
743            value = decodeStringValue(index);
744            if (value == Value.NULL)
745                return defVal;
746        }
747
748        try {
749            return vr.toString(value, bigEndian, valueIndex, defVal);
750        } catch (UnsupportedOperationException e) {
751            LOG.info("Attempt to access {} {} as string", TagUtils.toString(tag), vr);
752            return defVal;
753        }
754    }
755
756    public String[] getStrings(int tag) {
757        return getStrings(null, tag, null);
758    }
759
760    public String[] getStrings(String privateCreator, int tag) {
761        return getStrings(privateCreator, tag, null);
762    }
763
764    public String[] getStrings(String privateCreator, int tag, VR vr) {
765        int index = indexOf(privateCreator, tag);
766        if (index < 0)
767            return null;
768
769        Object value = values[index];
770        if (value == Value.NULL)
771            return StringUtils.EMPTY_STRING;
772
773        if (vr == null)
774            vr = vrs[index];
775        else
776            updateVR(index, vr);
777        if (vr.isStringType()) {
778            value = decodeStringValue(index);
779            if (value == Value.NULL)
780                return StringUtils.EMPTY_STRING;
781        }
782        try {
783            return toStrings(vr.toStrings(value, bigEndian,
784                    getSpecificCharacterSet(vr)));
785        } catch (UnsupportedOperationException e) {
786            LOG.info("Attempt to access {} {} as string", TagUtils.toString(tag), vr);
787            return null;
788        }
789    }
790
791    private static String[] toStrings(Object val) {
792        return (val instanceof String) 
793                ? new String[] { (String) val } 
794                : (String[]) val;
795    }
796
797    public int getInt(int tag, int defVal) {
798        return getInt(null, tag, null, 0, defVal);
799    }
800
801    public int getInt(int tag, int valueIndex, int defVal) {
802        return getInt(null, tag, null, valueIndex, defVal);
803    }
804
805    public int getInt(String privateCreator, int tag, int defVal) {
806        return getInt(privateCreator, tag, null, 0, defVal);
807    }
808
809    public int getInt(String privateCreator, int tag, VR vr, int defVal) {
810        return getInt(privateCreator, tag, vr, 0, defVal);
811    }
812
813    public int getInt(String privateCreator, int tag, int valueIndex, int defVal) {
814        return getInt(privateCreator, tag, null, valueIndex, defVal);
815    }
816
817    public int getInt(String privateCreator, int tag, VR vr, int valueIndex, int defVal) {
818        int index = indexOf(privateCreator, tag);
819        if (index < 0)
820            return defVal;
821
822        Object value = values[index];
823        if (value == Value.NULL)
824            return defVal;
825
826        if (vr == null)
827            vr = vrs[index];
828        else
829            updateVR(index, vr);
830        if (vr == VR.IS)
831            value = decodeISValue(index);
832
833        try {
834            return vr.toInt(value, bigEndian, valueIndex, defVal);
835        } catch (UnsupportedOperationException e) {
836            LOG.info("Attempt to access {} {} as int", TagUtils.toString(tag), vr);
837            return defVal;
838        } catch (IllegalArgumentException e) {
839            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
840            return defVal;
841        }
842    }
843
844    public int[] getInts(int tag) {
845        return getInts(null, tag, null);
846    }
847
848    public int[] getInts(String privateCreator, int tag) {
849        return getInts(privateCreator, tag, null);
850    }
851
852    public int[] getInts(String privateCreator, int tag, VR vr) {
853        int index = indexOf(privateCreator, tag);
854        if (index < 0)
855            return null;
856
857        Object value = values[index];
858        if (value == Value.NULL)
859            return ByteUtils.EMPTY_INTS;
860
861        if (vr == null)
862            vr = vrs[index];
863        else
864            updateVR(index, vr);
865        if (vr == VR.IS)
866            value = decodeISValue(index);
867
868        try {
869            return vr.toInts(value, bigEndian);
870        } catch (UnsupportedOperationException e) {
871            LOG.info("Attempt to access {} {} as int", TagUtils.toString(tag), vr);
872            return null;
873        } catch (IllegalArgumentException e) {
874            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
875            return null;
876        }
877    }
878
879    public float getFloat(int tag, float defVal) {
880        return getFloat(null, tag, null, 0, defVal);
881    }
882
883    public float getFloat(int tag, int valueIndex, float defVal) {
884        return getFloat(null, tag, null, valueIndex, defVal);
885    }
886
887    public float getFloat(String privateCreator, int tag, float defVal) {
888        return getFloat(privateCreator, tag, null, 0, defVal);
889    }
890
891    public float getFloat(String privateCreator, int tag, VR vr, float defVal) {
892        return getFloat(privateCreator, tag, vr, 0, defVal);
893    }
894
895    public float getFloat(String privateCreator, int tag, int valueIndex, float defVal) {
896        return getFloat(privateCreator, tag, null, valueIndex, defVal);
897    }
898
899    public float getFloat(String privateCreator, int tag, VR vr, int valueIndex, float defVal) {
900        int index = indexOf(privateCreator, tag);
901        if (index < 0)
902            return defVal;
903
904        Object value = values[index];
905        if (value == Value.NULL)
906            return defVal;
907
908        if (vr == null)
909            vr = vrs[index];
910        else
911            updateVR(index, vr);
912        if (vr == VR.DS)
913            value = decodeDSValue(index);
914
915        try {
916            return vr.toFloat(value, bigEndian, valueIndex, defVal);
917        } catch (UnsupportedOperationException e) {
918            LOG.info("Attempt to access {} {} as float", TagUtils.toString(tag), vr);
919            return defVal;
920        } catch (IllegalArgumentException e) {
921            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
922            return defVal;
923        }
924    }
925
926    public float[] getFloats(int tag) {
927        return getFloats(null, tag, null);
928    }
929
930    public float[] getFloats(String privateCreator, int tag) {
931        return getFloats(privateCreator, tag, null);
932    }
933
934    public float[] getFloats(String privateCreator, int tag, VR vr) {
935        int index = indexOf(privateCreator, tag);
936        if (index < 0)
937            return null;
938
939        Object value = values[index];
940        if (value == Value.NULL)
941            return ByteUtils.EMPTY_FLOATS;
942
943        if (vr == null)
944            vr = vrs[index];
945        else
946            updateVR(index, vr);
947        if (vr == VR.DS)
948            value = decodeDSValue(index);
949
950        try {
951            return vr.toFloats(value, bigEndian);
952        } catch (UnsupportedOperationException e) {
953            LOG.info("Attempt to access {} {} as float", TagUtils.toString(tag), vr);
954            return null;
955        } catch (IllegalArgumentException e) {
956            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
957            return null;
958        }
959    }
960
961    public double getDouble(int tag, double defVal) {
962        return getDouble(null, tag, null, 0, defVal);
963    }
964
965    public double getDouble(int tag, int valueIndex, double defVal) {
966        return getDouble(null, tag, null, valueIndex, defVal);
967    }
968
969    public double getDouble(String privateCreator, int tag, double defVal) {
970        return getDouble(privateCreator, tag, null, 0, defVal);
971    }
972
973    public double getDouble(String privateCreator, int tag, VR vr, double defVal) {
974        return getDouble(privateCreator, tag, vr, 0, defVal);
975    }
976
977    public double getDouble(String privateCreator, int tag, int valueIndex, double defVal) {
978        return getDouble(privateCreator, tag, null, valueIndex, defVal);
979    }
980
981    public double getDouble(String privateCreator, int tag, VR vr, int valueIndex, double defVal) {
982        int index = indexOf(privateCreator, tag);
983        if (index < 0)
984            return defVal;
985
986        Object value = values[index];
987        if (value == Value.NULL)
988            return defVal;
989
990        if (vr == null)
991            vr = vrs[index];
992        else
993            updateVR(index, vr);
994        if (vr == VR.DS)
995            value = decodeDSValue(index);
996
997        try {
998            return vr.toDouble(value, bigEndian, valueIndex, defVal);
999        } catch (UnsupportedOperationException e) {
1000            LOG.info("Attempt to access {} {} as double", TagUtils.toString(tag), vr);
1001           return defVal;
1002        } catch (IllegalArgumentException e) {
1003            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
1004            return defVal;
1005        }
1006    }
1007
1008    public double[] getDoubles(int tag) {
1009        return getDoubles(null, tag, null);
1010    }
1011
1012    public double[] getDoubles(String privateCreator, int tag) {
1013        return getDoubles(privateCreator, tag, null);
1014    }
1015
1016    public double[] getDoubles(String privateCreator, int tag, VR vr) {
1017        int index = indexOf(privateCreator, tag);
1018        if (index < 0)
1019            return null;
1020
1021        Object value = values[index];
1022        if (value == Value.NULL)
1023            return ByteUtils.EMPTY_DOUBLES;
1024
1025        if (vr == null)
1026            vr = vrs[index];
1027        else
1028            updateVR(index, vr);
1029        if (vr == VR.DS)
1030            value = decodeDSValue(index);
1031        try {
1032            return vr.toDoubles(value, bigEndian);
1033        } catch (UnsupportedOperationException e) {
1034            LOG.info("Attempt to access {} {} as double", TagUtils.toString(tag), vr);
1035            return null;
1036        } catch (IllegalArgumentException e) {
1037            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
1038            return null;
1039        }
1040    }
1041
1042    public Date getDate(int tag) {
1043        return getDate(null, tag, null, 0, null, new DatePrecision());
1044    }
1045
1046    public Date getDate(int tag, DatePrecision precision) {
1047        return getDate(null, tag, null, 0, null, precision);
1048    }
1049
1050    public Date getDate(int tag, Date defVal) {
1051        return getDate(null, tag, null, 0, defVal, new DatePrecision());
1052    }
1053
1054    public Date getDate(int tag, Date defVal, DatePrecision precision) {
1055        return getDate(null, tag, null, 0, defVal, precision);
1056    }
1057
1058    public Date getDate(int tag, int valueIndex) {
1059        return getDate(null, tag, null, valueIndex, null, new DatePrecision());
1060    }
1061
1062    public Date getDate(int tag, int valueIndex, DatePrecision precision) {
1063        return getDate(null, tag, null, valueIndex, null, precision);
1064    }
1065
1066    public Date getDate(int tag, int valueIndex, Date defVal) {
1067        return getDate(null, tag, null, valueIndex, defVal, new DatePrecision());
1068    }
1069
1070    public Date getDate(int tag, int valueIndex, Date defVal,
1071            DatePrecision precision) {
1072        return getDate(null, tag, null, valueIndex, defVal, precision);
1073    }
1074
1075    public Date getDate(String privateCreator, int tag) {
1076        return getDate(privateCreator, tag, null, 0, null, new DatePrecision());
1077    }
1078
1079    public Date getDate(String privateCreator, int tag, DatePrecision precision) {
1080        return getDate(privateCreator, tag, null, 0, null, precision);
1081    }
1082
1083    public Date getDate(String privateCreator, int tag, Date defVal,
1084            DatePrecision precision) {
1085        return getDate(privateCreator, tag, null, 0, defVal, precision);
1086    }
1087
1088    public Date getDate(String privateCreator, int tag, VR vr) {
1089        return getDate(privateCreator, tag, vr, 0, null, new DatePrecision());
1090    }
1091
1092    public Date getDate(String privateCreator, int tag, VR vr,
1093            DatePrecision precision) {
1094        return getDate(privateCreator, tag, vr, 0, null, precision);
1095    }
1096
1097    public Date getDate(String privateCreator, int tag, VR vr, Date defVal) {
1098        return getDate(privateCreator, tag, vr, 0, defVal, new DatePrecision());
1099    }
1100
1101    public Date getDate(String privateCreator, int tag, VR vr, Date defVal,
1102            DatePrecision precision) {
1103        return getDate(privateCreator, tag, vr, 0, defVal, precision);
1104    }
1105
1106    public Date getDate(String privateCreator, int tag, int valueIndex) {
1107        return getDate(privateCreator, tag, null, valueIndex, null,
1108                new DatePrecision());
1109    }
1110
1111    public Date getDate(String privateCreator, int tag, int valueIndex,
1112            DatePrecision precision) {
1113        return getDate(privateCreator, tag, null, valueIndex, null, precision);
1114    }
1115
1116    public Date getDate(String privateCreator, int tag, int valueIndex,
1117            Date defVal) {
1118        return getDate(privateCreator, tag, null, valueIndex, defVal,
1119                new DatePrecision());
1120    }
1121
1122    public Date getDate(String privateCreator, int tag, int valueIndex,
1123            Date defVal, DatePrecision precision) {
1124        return getDate(privateCreator, tag, null, valueIndex, defVal, precision);
1125    }
1126
1127    public Date getDate(String privateCreator, int tag, VR vr, int valueIndex) {
1128        return getDate(privateCreator, tag, vr, valueIndex, null,
1129                new DatePrecision());
1130    }
1131
1132    public Date getDate(String privateCreator, int tag, VR vr, int valueIndex,
1133            DatePrecision precision) {
1134        return getDate(privateCreator, tag, vr, valueIndex, null, precision);
1135    }
1136
1137    public Date getDate(String privateCreator, int tag, VR vr, int valueIndex,
1138            Date defVal) {
1139        return getDate(privateCreator, tag, vr, valueIndex, defVal,
1140                new DatePrecision());
1141    }
1142
1143    public Date getDate(String privateCreator, int tag, VR vr, int valueIndex,
1144            Date defVal, DatePrecision precision) {
1145        int index = indexOf(privateCreator, tag);
1146        if (index < 0)
1147            return defVal;
1148
1149        Object value = values[index];
1150        if (value == Value.NULL)
1151            return defVal;
1152
1153        if (vr == null)
1154            vr = vrs[index];
1155        else
1156            updateVR(index, vr);
1157        if (!vr.isTemporalType()) {
1158            LOG.info("Attempt to access {} {} as date", TagUtils.toString(tag), vr);
1159            return defVal;
1160        }
1161        try {
1162            value = decodeStringValue(index);
1163            if (value == Value.NULL)
1164                return defVal;
1165
1166            return vr.toDate(value, getTimeZone(), valueIndex, false, defVal, precision);
1167        } catch (IllegalArgumentException e) {
1168            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
1169            return defVal;
1170        }
1171    }
1172
1173    public Date getDate(long tag) {
1174        return getDate(null, tag, null, new DatePrecision());
1175    }
1176
1177    public Date getDate(long tag, DatePrecision precision) {
1178        return getDate(null, tag, null, precision);
1179    }
1180
1181    public Date getDate(long tag, Date defVal) {
1182        return getDate(null, tag, defVal, new DatePrecision());
1183    }
1184
1185    public Date getDate(long tag, Date defVal, DatePrecision precision) {
1186        return getDate(null, tag, defVal, precision);
1187    }
1188
1189    public Date getDate(String privateCreator, long tag) {
1190        return getDate(privateCreator, tag, null, new DatePrecision());
1191    }
1192
1193    public Date getDate(String privateCreator, long tag, DatePrecision precision) {
1194        return getDate(privateCreator, tag, null, precision);
1195    }
1196
1197    public Date getDate(String privateCreator, long tag, Date defVal) {
1198        return getDate(privateCreator, tag, defVal, new DatePrecision());
1199    }
1200
1201    public Date getDate(String privateCreator, long tag, Date defVal,
1202            DatePrecision precision) {
1203        int daTag = (int) (tag >>> 32);
1204        int tmTag = (int) tag;
1205
1206        String tm = getString(privateCreator, tmTag, VR.TM, null);
1207        if (tm == null)
1208            return getDate(daTag, defVal, precision);
1209
1210        String da = getString(privateCreator, daTag, VR.DA, null);
1211        if (da == null)
1212            return defVal;
1213        try {
1214            return VR.DT.toDate(da + tm, getTimeZone(), 0, false, null,
1215                    precision);
1216        } catch (IllegalArgumentException e) {
1217            LOG.info("Invalid value of {} DA or {} TM",
1218                    TagUtils.toString(daTag),
1219                    TagUtils.toString(tmTag));
1220            return defVal;
1221        }
1222    }
1223
1224    public Date[] getDates(int tag) {
1225        return getDates(null, tag, null, new DatePrecisions());
1226    }
1227
1228    public Date[] getDates(int tag, DatePrecisions precisions) {
1229        return getDates(null, tag, null, precisions);
1230    }
1231
1232    public Date[] getDates(String privateCreator, int tag) {
1233        return getDates(privateCreator, tag, null, new DatePrecisions());
1234    }
1235
1236    public Date[] getDates(String privateCreator, int tag,
1237            DatePrecisions precisions) {
1238        return getDates(privateCreator, tag, null, precisions);
1239    }
1240
1241    public Date[] getDates(String privateCreator, int tag, VR vr) {
1242        return getDates(privateCreator, tag, vr, new DatePrecisions());
1243    }
1244
1245    public Date[] getDates(String privateCreator, int tag, VR vr,
1246            DatePrecisions precisions) {
1247        int index = indexOf(privateCreator, tag);
1248        if (index < 0)
1249            return null;
1250
1251        Object value = values[index];
1252        if (value == Value.NULL)
1253            return DateUtils.EMPTY_DATES;
1254
1255        if (vr == null)
1256            vr = vrs[index];
1257        else
1258            updateVR(index, vr);
1259        if (!vr.isTemporalType()) {
1260            LOG.info("Attempt to access {} {} as date", TagUtils.toString(tag), vr);
1261            return DateUtils.EMPTY_DATES;
1262        }
1263        try {
1264            value = decodeStringValue(index);
1265            if (value == Value.NULL)
1266                return DateUtils.EMPTY_DATES;
1267
1268            return vr.toDates(value, getTimeZone(), false, precisions);
1269        } catch (IllegalArgumentException e) {
1270            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
1271            return DateUtils.EMPTY_DATES;
1272        }
1273    }
1274
1275    public Date[] getDates(long tag) {
1276        return getDates(null, tag, new DatePrecisions());
1277    }
1278
1279    public Date[] getDates(long tag, DatePrecisions precisions) {
1280        return getDates(null, tag, precisions);
1281    }
1282
1283    public Date[] getDates(String privateCreator, long tag) {
1284        return getDates(privateCreator, tag, new DatePrecisions());
1285    }
1286
1287    public Date[] getDates(String privateCreator, long tag,
1288            DatePrecisions precisions) {
1289        int daTag = (int) (tag >>> 32);
1290        int tmTag = (int) tag;
1291
1292        String[] tm = getStrings(privateCreator, tmTag);
1293        if (tm == null || tm.length == 0)
1294            return getDates(daTag, precisions);
1295
1296        String[] da = getStrings(privateCreator, daTag);
1297        if (da == null || da.length == 0)
1298            return DateUtils.EMPTY_DATES;
1299        
1300        Date[] dates = new Date[da.length];
1301        precisions.precisions = new DatePrecision[da.length];
1302        int i = 0;
1303        try {
1304            TimeZone tz = getTimeZone();
1305            while (i < tm.length)
1306                dates[i++] = VR.DT.toDate(da[i] + tm[i], tz, 0, false, null,
1307                        precisions.precisions[i] = new DatePrecision());
1308            while (i < da.length)
1309                dates[i++] = VR.DA.toDate(da[i], tz, 0, false, null,
1310                        precisions.precisions[i] = new DatePrecision());
1311        } catch (IllegalArgumentException e) {
1312            LOG.info("Invalid value of {} DA or {} TM",
1313                    TagUtils.toString(daTag),
1314                    TagUtils.toString(tmTag));
1315            dates = Arrays.copyOf(dates, i);
1316        }
1317        return dates;
1318    }
1319
1320    public DateRange getDateRange(int tag) {
1321        return getDateRange(null, tag, null, null);
1322    }
1323
1324    public DateRange getDateRange(int tag, DateRange defVal) {
1325        return getDateRange(null, tag, null, defVal);
1326    }
1327
1328    public DateRange getDateRange(String privateCreator, int tag) {
1329        return getDateRange(privateCreator, tag, null, null);
1330    }
1331
1332    public DateRange getDateRange(String privateCreator, int tag, DateRange defVal) {
1333        return getDateRange(privateCreator, tag, null, defVal);
1334    }
1335
1336    public DateRange getDateRange(String privateCreator, int tag, VR vr) {
1337        return getDateRange(privateCreator, tag, vr, null);
1338    }
1339
1340    public DateRange getDateRange(String privateCreator, int tag, VR vr, DateRange defVal) {
1341        int index = indexOf(privateCreator, tag);
1342        if (index < 0)
1343            return defVal;
1344
1345        Object value = values[index];
1346        if (value == Value.NULL)
1347            return defVal;
1348
1349        if (vr == null)
1350            vr = vrs[index];
1351        else
1352            updateVR(index, vr);
1353        if (!vr.isTemporalType()) {
1354            LOG.info("Attempt to access {} {} as date", TagUtils.toString(tag), vr);
1355            return defVal;
1356        }
1357        value = decodeStringValue(index);
1358        if (value == Value.NULL)
1359            return defVal;
1360
1361        try {
1362            return toDateRange((value instanceof String)
1363                    ? (String) value : ((String[]) value)[0], vr);
1364        } catch (IllegalArgumentException e) {
1365            LOG.info("Invalid value of {} {}", TagUtils.toString(tag), vr);
1366            return defVal;
1367        }
1368    }
1369
1370    private DateRange toDateRange(String s, VR vr) {
1371        String[] range = splitRange(s);
1372        TimeZone tz = getTimeZone();
1373        DatePrecision precision = new DatePrecision();
1374        Date start = range[0] == null ? null
1375                : vr.toDate(range[0], tz, 0, false, null, precision);
1376        Date end = range[1] == null ? null
1377                : vr.toDate(range[1], tz, 0, true, null, precision);
1378        return new DateRange(start, end);
1379    }
1380
1381    private static String[] splitRange(String s) {
1382        String[] range = new String[2];
1383        int delim = s.indexOf('-');
1384        if (delim == -1)
1385            range[0] = range[1] = s;
1386        else {
1387            if (delim > 0)
1388                range[0] =  s.substring(0, delim);
1389            if (delim < s.length() - 1)
1390                range[1] =  s.substring(delim+1);
1391        }
1392        return range;
1393    }
1394
1395    public DateRange getDateRange(long tag) {
1396        return getDateRange(null, tag, null);
1397    }
1398
1399    public DateRange getDateRange(long tag, DateRange defVal) {
1400        return getDateRange(null, tag, defVal);
1401    }
1402
1403    public DateRange getDateRange(String privateCreator, long tag) {
1404        return getDateRange(privateCreator, tag, null);
1405    }
1406
1407    public DateRange getDateRange(String privateCreator, long tag, DateRange defVal) {
1408        int daTag = (int) (tag >>> 32);
1409        int tmTag = (int) tag;
1410
1411        String tm = getString(privateCreator, tmTag, VR.TM, null);
1412        if (tm == null)
1413            return getDateRange(daTag, defVal);
1414
1415        String da = getString(privateCreator, daTag, VR.DA, null);
1416        if (da == null)
1417            return defVal;
1418
1419        try {
1420            return toDateRange(da, tm);
1421        } catch (IllegalArgumentException e) {
1422            LOG.info("Invalid value of {} TM", TagUtils.toString((int) tag));
1423            return defVal;
1424        }
1425    }
1426
1427    private DateRange toDateRange(String da, String tm) {
1428        String[] darange = splitRange(da);
1429        String[] tmrange = splitRange(tm);
1430        DatePrecision precision = new DatePrecision();
1431        TimeZone tz = getTimeZone();
1432        return new DateRange(
1433                darange[0] == null ? null
1434                        : VR.DT.toDate(tmrange[0] == null
1435                                ? darange[0]
1436                                : darange[0] + tmrange[0],
1437                                tz, 0, false, null, precision ),
1438                darange[1] == null ? null
1439                        : VR.DT.toDate(tmrange[1] == null
1440                                ? darange[1]
1441                                : darange[1] + tmrange[1],
1442                                tz, 0, true, null, precision));
1443    }
1444
1445    /**
1446     * Set Specific Character Set (0008,0005) to specified code(s) and
1447     * re-encode contained LO, LT, PN, SH, ST, UT attributes
1448     * accordingly.
1449     * 
1450     * @param codes new value(s) of Specific Character Set (0008,0005) 
1451     */
1452    public void setSpecificCharacterSet(String... codes) {
1453        decodeStringValuesUsingSpecificCharacterSet();
1454        setString(Tag.SpecificCharacterSet, VR.CS, codes);
1455    }
1456
1457    public SpecificCharacterSet getSpecificCharacterSet() {
1458        if (cs != null)
1459            return cs;
1460
1461        if (containsSpecificCharacterSet)
1462            cs = SpecificCharacterSet.valueOf(
1463                    getStrings(null, Tag.SpecificCharacterSet, VR.CS));
1464        else if (parent != null)
1465            return parent.getSpecificCharacterSet();
1466        else
1467            cs = SpecificCharacterSet.getDefaultCharacterSet();
1468
1469        return cs;
1470    }
1471
1472    public boolean containsTimezoneOffsetFromUTC() {
1473        return containsTimezoneOffsetFromUTC;
1474    }
1475
1476    public void setDefaultTimeZone(TimeZone tz) {
1477        defaultTimeZone = tz;
1478    }
1479
1480    public TimeZone getDefaultTimeZone() {
1481        if (defaultTimeZone != null)
1482            return defaultTimeZone;
1483
1484        if (parent != null)
1485            return parent.getDefaultTimeZone();
1486
1487        return TimeZone.getDefault();
1488    }
1489
1490    public TimeZone getTimeZone() {
1491        if (tz != null)
1492            return tz;
1493
1494        if (containsTimezoneOffsetFromUTC) {
1495            String s = getString(Tag.TimezoneOffsetFromUTC);
1496            if (s != null)
1497                try {
1498                    tz = DateUtils.timeZone(s);
1499                } catch (IllegalArgumentException e) {
1500                    LOG.info(e.getMessage());
1501                }
1502        } else if (parent != null)
1503            return parent.getTimeZone();
1504        else
1505            tz = getDefaultTimeZone();
1506
1507        return tz;
1508     }
1509
1510    /**
1511     * Set Timezone Offset From UTC (0008,0201) to specified value and
1512     * adjust contained DA, DT and TM attributs accordingly
1513     * 
1514     * @param utcOffset offset from UTC as (+|-)HHMM 
1515     */
1516    public void setTimezoneOffsetFromUTC(String utcOffset) {
1517        TimeZone tz = DateUtils.timeZone(utcOffset);
1518        updateTimezone(getTimeZone(), tz);
1519        setString(Tag.TimezoneOffsetFromUTC, VR.SH, utcOffset);
1520        this.tz = tz;
1521    }
1522
1523    /**
1524     * Set the Default Time Zone to specified value and adjust contained DA, 
1525     * DT and TM attributs accordingly. If the Time Zone does not use Daylight
1526     * Saving Time, attribute Timezone Offset From UTC (0008,0201) will be also
1527     * set accordingly. If the Time zone uses Daylight Saving Time, a previous
1528     * existing attribute Timezone Offset From UTC (0008,0201) will be removed.
1529     * 
1530     * @param tz Time Zone
1531     *
1532     * @see #setDefaultTimeZone(TimeZone)
1533     * @see #setTimezoneOffsetFromUTC(String)
1534     */
1535    public void setTimezone(TimeZone tz) {
1536        updateTimezone(getTimeZone(), tz);
1537        if (tz.useDaylightTime()) {
1538            remove(Tag.TimezoneOffsetFromUTC);
1539            setDefaultTimeZone(tz);
1540        } else {
1541            setString(Tag.TimezoneOffsetFromUTC, VR.SH,
1542                    DateUtils.formatTimezoneOffsetFromUTC(tz));
1543        }
1544        this.tz = tz;
1545    }
1546
1547    /**
1548     * Updates the time zone of a specific standard or private tag
1549     *
1550     * @param from Time Zone from
1551     * @param to Time Zone to
1552     * @param privateCreator private creator - null otherwise
1553     * @param tag Attribute tag to update time zone
1554     */
1555    public void updateTimeZoneOfSpecificTag(TimeZone from, TimeZone to
1556            , String privateCreator, int tag) {
1557
1558        updateTimezone(from, to,indexOf(privateCreator, tag));
1559    }
1560
1561    private void updateTimezone(TimeZone from, TimeZone to) {
1562        if (from.hasSameRules(to))
1563            return;
1564
1565        for (int i = 0; i < size; i++) {
1566            Object val = values[i];
1567            if (val instanceof Sequence) {
1568                Sequence new_name = (Sequence) val;
1569                for (Attributes item : new_name) {
1570                    item.updateTimezone(item.getTimeZone(), to);
1571                    item.remove(Tag.TimezoneOffsetFromUTC);
1572                }
1573            } else if (vrs[i] == VR.TM && tags[i] != Tag.PatientBirthTime
1574                    || vrs[i] == VR.DT && tags[i] != Tag.ContextGroupVersion
1575                                       && tags[i] != Tag.ContextGroupLocalVersion)
1576                updateTimezone(from, to, i);
1577        }
1578    }
1579
1580    private void updateTimezone(TimeZone from, TimeZone to, int tmIndex) {
1581        Object tm = decodeStringValue(tmIndex);
1582        if (tm == Value.NULL)
1583            return;
1584
1585        int tmTag = tags[tmIndex];
1586        if (vrs[tmIndex] == VR.DT) {
1587            if (tm instanceof String[]) {
1588                String[] tms = (String[]) tm;
1589                for (int i = 0; i < tms.length; i++) {
1590                    tms[i] = updateTimeZoneDT(from, to, tms[i]);
1591                }
1592            } else
1593                values[tmIndex] = updateTimeZoneDT(from, to, (String) tm);
1594        } else {
1595            int daTag = ElementDictionary.getElementDictionary(privateCreatorOf(tmTag)).daTagOf(tmTag);
1596            int daIndex = daTag != 0 ? indexOf(daTag) : -1;
1597            Object da = daIndex >= 0 ? decodeStringValue(daIndex) : Value.NULL;
1598
1599            if (tm instanceof String[]) {
1600                String[] tms = (String[]) tm;
1601                if (da instanceof String[]) {
1602                    String[] das = (String[]) da;
1603                    for (int i = 0; i < tms.length; i++) {
1604                        if (i < das.length) {
1605                            String dt = updateTimeZoneDT(
1606                                    from, to, das[i] + tms[i]);
1607                            das[i] = dt.substring(0,8);
1608                            tms[i] = dt.substring(8);
1609                        } else {
1610                            tms[i] = updateTimeZoneTM(from, to, tms[i]);
1611                        }
1612                    }
1613                } else {
1614                    if (da == Value.NULL) {
1615                        tms[0] = updateTimeZoneTM(from, to, tms[0]);
1616                    } else {
1617                        String dt = updateTimeZoneDT(
1618                                from, to, (String) da + tms[0]);
1619                        values[daIndex] = dt.substring(0,8);
1620                        tms[0] = dt.substring(8);
1621                    }
1622                    for (int i = 1; i < tms.length; i++) {
1623                        tms[i] = updateTimeZoneTM(from, to, tms[i]);
1624                    }
1625                }
1626            } else {
1627                if (da instanceof String[]) {
1628                    String[] das = (String[]) da;
1629                    String dt = updateTimeZoneDT(
1630                           from, to, das[0] + (String) tm);
1631                    das[0] = dt.substring(0,8);
1632                    values[tmIndex] = dt.substring(8);
1633                } else {
1634                    String[] tmRange = null;
1635                    if (isRange((String) tm)) {
1636                        tmRange = splitRange((String) tm);
1637                        if (tmRange[0] == null)
1638                            tmRange[0] = "000000.000";
1639                        if (tmRange[1] == null)
1640                            tmRange[1] = "235959.999";
1641                    }
1642                    if (da == Value.NULL) {
1643                        if (tmRange != null) {
1644                            tmRange[0] = updateTimeZoneTM(
1645                                    from, to, tmRange[0]);
1646                            tmRange[1] = updateTimeZoneTM(
1647                                    from, to, tmRange[1]);
1648                            values[tmIndex] = toDateRangeString(
1649                                    tmRange[0], tmRange[1]);
1650                        } else {
1651                            values[tmIndex] = updateTimeZoneTM(
1652                                    from, to, (String) tm);
1653                        }
1654                    } else {
1655                        if (tmRange != null) {
1656                            String[] daRange = splitRange((String) da);
1657                            if (daRange[0] == null) {
1658                                daRange[0] = "";
1659                                tmRange[0] = updateTimeZoneTM(from, to, tmRange[0]);
1660                            } else {
1661                                String dt = updateTimeZoneDT(
1662                                        from, to, daRange[0] + tmRange[0]);
1663                                daRange[0] = dt.substring(0,8);
1664                                tmRange[0] = dt.substring(8);
1665                            }
1666                            if (daRange[1] == null) {
1667                                daRange[1] = "";
1668                                tmRange[1] = updateTimeZoneTM(from, to, tmRange[1]);
1669                            } else {
1670                                String dt = updateTimeZoneDT(
1671                                        from, to, daRange[1] + tmRange[1]);
1672                                daRange[1] = dt.substring(0,8);
1673                                tmRange[1] = dt.substring(8);
1674                            }
1675                            values[daIndex] = toDateRangeString(
1676                                    daRange[0], daRange[1]);
1677                            values[tmIndex] = toDateRangeString(
1678                                    tmRange[0], tmRange[1]);
1679                        } else {
1680                            String dt = updateTimeZoneDT(
1681                                    from, to, (String) da + (String) tm);
1682                            values[daIndex] = dt.substring(0,8);
1683                            values[tmIndex] = dt.substring(8);
1684                        }
1685                    }
1686                }
1687            }
1688        }
1689    }
1690
1691    private static boolean isRange(String s) {
1692        return s.indexOf('-') >= 0;
1693    }
1694
1695    private String updateTimeZoneDT(TimeZone from, TimeZone to, String dt) {
1696        int dtlen = dt.length();
1697        if (dtlen > 8) {
1698            char ch = dt.charAt(dtlen-5);
1699            if (ch == '+' || ch == '-')
1700                return dt;
1701        }
1702        try {
1703            DatePrecision precision = new DatePrecision();
1704            Date date = DateUtils.parseDT(from, dt, false, precision);
1705            dt = DateUtils.formatDT(to, date, precision);
1706        } catch (IllegalArgumentException e) {
1707            // ignored
1708        }
1709        return dt;
1710    }
1711
1712    private String updateTimeZoneTM(TimeZone from, TimeZone to, String tm) {
1713        try {
1714            DatePrecision precision = new DatePrecision();
1715            Date date = DateUtils.parseTM(from, tm, false, precision);
1716            tm = DateUtils.formatTM(to, date, precision);
1717        } catch (IllegalArgumentException e) {
1718            // ignored
1719        }
1720        return tm;
1721    }
1722
1723    public String getPrivateCreator(int tag) {
1724         return TagUtils.isPrivateTag(tag)
1725                 ? getString(TagUtils.creatorTagOf(tag), null)
1726                 : null;
1727    }
1728
1729    public Object remove(int tag) {
1730        return remove(null, tag);
1731    }
1732
1733    public Object remove(String privateCreator, int tag) {
1734        int index = indexOf(privateCreator, tag);
1735        if (index < 0)
1736            return null;
1737
1738        Object value = values[index];
1739//        if (value instanceof Sequence)
1740//            ((Sequence) value).clear();
1741
1742        int numMoved = size - index - 1;
1743        if (numMoved > 0) {
1744            System.arraycopy(tags, index+1, tags, index, numMoved);
1745            System.arraycopy(vrs, index+1, vrs, index, numMoved);
1746            System.arraycopy(values, index+1, values, index, numMoved);
1747        }
1748        values[--size] = null;
1749
1750        if (tag == Tag.SpecificCharacterSet) {
1751            containsSpecificCharacterSet = false;
1752            cs = null;
1753        } else if (tag == Tag.TimezoneOffsetFromUTC) {
1754            containsTimezoneOffsetFromUTC = false;
1755            tz = null;
1756        }
1757
1758        return value;
1759    }
1760
1761    public Object setNull(int tag, VR vr) {
1762        return setNull(null, tag, vr);
1763    }
1764
1765    public Object setNull(String privateCreator, int tag, VR vr) {
1766        return set(privateCreator, tag, vr, Value.NULL);
1767    }
1768
1769    public Object setBytes(int tag, VR vr, byte[] b) {
1770        return setBytes(null, tag, vr, b);
1771    }
1772
1773    public Object setBytes(String privateCreator, int tag, VR vr, byte[] b) {
1774        return set(privateCreator, tag, vr, vr.toValue(b));
1775    }
1776
1777    public Object setString(int tag, VR vr, String s) {
1778        return setString(null, tag, vr, s);
1779    }
1780
1781    public Object setString(String privateCreator, int tag, VR vr, String s) {
1782        return set(privateCreator, tag, vr, vr.toValue(s, bigEndian));
1783    }
1784
1785    public Object setString(int tag, VR vr, String... ss) {
1786        return setString(null, tag, vr, ss);
1787    }
1788
1789    public Object setString(String privateCreator, int tag, VR vr, String... ss) {
1790        return set(privateCreator, tag, vr, vr.toValue(ss, bigEndian));
1791    }
1792
1793    public Object setInt(int tag, VR vr, int... is) {
1794        return setInt(null, tag, vr, is);
1795    }
1796
1797    public Object setInt(String privateCreator, int tag, VR vr, int... is) {
1798        return set(privateCreator, tag, vr, vr.toValue(is, bigEndian));
1799    }
1800
1801    public Object setFloat(int tag, VR vr, float... fs) {
1802        return setFloat(null, tag, vr, fs);
1803    }
1804
1805    public Object setFloat(String privateCreator, int tag, VR vr, float... fs) {
1806        return set(privateCreator, tag, vr, vr.toValue(fs, bigEndian));
1807    }
1808
1809    public Object setDouble(int tag, VR vr, double... ds) {
1810        return setDouble(null, tag, vr, ds);
1811    }
1812
1813    public Object setDouble(String privateCreator, int tag, VR vr, double... ds) {
1814        return set(privateCreator, tag, vr, vr.toValue(ds, bigEndian));
1815    }
1816
1817    public Object setDate(int tag, VR vr, Date... ds) {
1818        return setDate(null, tag, vr, ds);
1819    }
1820
1821    public Object setDate(int tag, VR vr, DatePrecision precision, Date... ds) {
1822        return setDate(null, tag, vr, precision, ds);
1823    }
1824
1825    public Object setDate(String privateCreator, int tag, VR vr,
1826            Date... ds) {
1827        return setDate(privateCreator, tag, vr, new DatePrecision(), ds);
1828    }
1829
1830    public Object setDate(String privateCreator, int tag, VR vr,
1831            DatePrecision precision, Date... ds) {
1832        return set(privateCreator, tag, vr, vr.toValue(ds, getTimeZone(), precision));
1833    }
1834
1835    public void setDate(long tag, Date dt) {
1836        setDate(null, tag, dt);
1837    }
1838
1839    public void setDate(long tag, DatePrecision precision, Date dt) {
1840        setDate(null, tag, precision, dt);
1841    }
1842
1843    public void setDate(String privateCreator, long tag, Date dt) {
1844        setDate(privateCreator, tag, new DatePrecision(), dt);
1845    }
1846
1847    public void setDate(String privateCreator, long tag,
1848            DatePrecision precision, Date dt) {
1849        int daTag = (int) (tag >>> 32);
1850        int tmTag = (int) tag;
1851        setDate(privateCreator, daTag, VR.DA, precision, dt);
1852        setDate(privateCreator, tmTag, VR.TM, precision, dt);
1853    }
1854
1855    public Object setDateRange(int tag, VR vr, DateRange range) {
1856        return setDateRange(null, tag, vr, range);
1857    }
1858
1859    public Object setDateRange(String privateCreator, int tag, VR vr, DateRange range) {
1860        return set(privateCreator, tag, vr, toString(range, vr, getTimeZone()));
1861    }
1862
1863    private static String toString(DateRange range, VR vr, TimeZone tz) {
1864        DatePrecision precision = new DatePrecision();
1865        String start = range.getStartDate() != null
1866                ? (String) vr.toValue(new Date[]{range.getStartDate()}, tz,
1867                        precision)
1868                : "";
1869        String end = range.getEndDate() != null
1870                ? (String) vr.toValue(new Date[]{range.getEndDate()}, tz,
1871                        precision)
1872                : "";
1873        return toDateRangeString(start, end);
1874    }
1875
1876    private static String toDateRangeString(String start, String end) {
1877        return start.equals(end) ? start : (start + '-' + end);
1878    }
1879
1880    public void setDateRange(long tag, DateRange dr) {
1881        setDateRange(null, tag, dr);
1882    }
1883
1884    public void setDateRange(String privateCreator, long tag, DateRange range) {
1885        int daTag = (int) (tag >>> 32);
1886        int tmTag = (int) tag;
1887        setDateRange(privateCreator, daTag, VR.DA, range);
1888        setDateRange(privateCreator, tmTag, VR.TM, range);
1889    }
1890
1891    public Object setValue(int tag, VR vr, Object value) {
1892        return setValue(null, tag, vr, value);
1893    }
1894
1895    public Object setValue(String privateCreator, int tag, VR vr, Object value) {
1896        return set(privateCreator, tag, vr, value != null ? value : Value.NULL);
1897    }
1898
1899    public Sequence newSequence(int tag, int initialCapacity) {
1900        return newSequence(null, tag, initialCapacity);
1901    }
1902
1903    public Sequence newSequence(String privateCreator, int tag, int initialCapacity) {
1904        Sequence seq = new Sequence(this, privateCreator, tag, initialCapacity);
1905        set(privateCreator, tag, VR.SQ, seq);
1906        return seq;
1907    }
1908
1909    public Sequence ensureSequence(int tag, int initialCapacity) {
1910        return ensureSequence(null, tag, initialCapacity);
1911    }
1912
1913    public Sequence ensureSequence(String privateCreator, int tag, int initialCapacity) {
1914        if (privateCreator != null) {
1915            int creatorTag = creatorTagOf(privateCreator, tag, true);
1916            tag = TagUtils.toPrivateTag(creatorTag, tag);
1917        }
1918
1919        Sequence seq;
1920        int index = indexOf(tag);
1921        if (index >= 0) {
1922            Object oldValue = values[index];
1923            if (oldValue instanceof Sequence)
1924                seq = (Sequence) oldValue;
1925            else
1926                values[index] = seq = new Sequence(this, privateCreator, tag, initialCapacity);
1927        } else {
1928            seq = new Sequence(this, privateCreator, tag, initialCapacity);
1929            insert(-index-1, tag, VR.SQ, seq);
1930        }
1931        return seq;
1932    }
1933
1934
1935    public Fragments newFragments(int tag, VR vr, int initialCapacity) {
1936        return newFragments(null, tag, vr, initialCapacity);
1937    }
1938
1939    public Fragments newFragments(String privateCreator, int tag, VR vr,
1940            int initialCapacity) {
1941        Fragments frags = new Fragments(privateCreator, tag, vr, bigEndian, initialCapacity);
1942        set(privateCreator, tag, vr, frags);
1943        return frags;
1944    }
1945
1946    private Object set(String privateCreator, int tag, VR vr, Object value) {
1947        if (vr == null)
1948            throw new NullPointerException("vr");
1949
1950        if (privateCreator != null) {
1951            int creatorTag = creatorTagOf(privateCreator, tag, true);
1952            tag = TagUtils.toPrivateTag(creatorTag, tag);
1953        }
1954
1955        if (TagUtils.isGroupLength(tag))
1956            return null;
1957
1958        Object oldValue = set(tag, vr, value);
1959
1960        if (tag == Tag.SpecificCharacterSet) {
1961            containsSpecificCharacterSet = true;
1962            cs = null;
1963        } else if (tag == Tag.TimezoneOffsetFromUTC) {
1964            containsTimezoneOffsetFromUTC = value != Value.NULL;
1965            tz = null;
1966        }
1967
1968        return oldValue;
1969    }
1970
1971    public void addBulkDataReference(String privateCreator, int tag, VR vr, BulkData bulkData,
1972                                ItemPointer... itemPointers) {
1973        Sequence seq = ensureSequence(Tag.ReferencedBulkDataSequence, 8);
1974        Attributes item = new Attributes(bigEndian, 7);
1975        seq.add(item);
1976        item.setString(Tag.RetrieveURL, VR.UR, bulkData.uri);
1977        item.setInt(Tag.SelectorAttribute, VR.AT, privateCreator != null ? (tag & 0xffff00ff) : tag);
1978        item.setString(Tag.SelectorAttributeVR, VR.CS, vr.name());
1979        if (privateCreator != null)
1980            item.setString(Tag.SelectorAttributePrivateCreator, VR.LO, privateCreator);
1981        if (itemPointers.length > 0) {
1982            int[] seqTags = new int[itemPointers.length];
1983            int[] itemNumbers = new int[itemPointers.length];
1984            String[] privateCreators = null;
1985            for (int i = 0; i < itemPointers.length; i++) {
1986                ItemPointer ip = itemPointers[i];
1987                seqTags[i] = ip.privateCreator != null ? (ip.sequenceTag & 0xffff00ff) : ip.sequenceTag;
1988                itemNumbers[i] = ip.itemIndex + 1;
1989                if (ip.privateCreator != null) {
1990                    if (privateCreators == null)
1991                        privateCreators = new String[itemPointers.length];
1992                    privateCreators[i] = ip.privateCreator;
1993                }
1994            }
1995            item.setInt(Tag.SelectorSequencePointer, VR.AT, seqTags);
1996            if (privateCreators != null)
1997                item.setString(Tag.SelectorSequencePointerPrivateCreator, VR.LO, privateCreators);
1998            item.setInt(Tag.SelectorSequencePointerItems, VR.IS, itemNumbers);
1999        }
2000        item.trimToSize();
2001    }
2002
2003    private Object set(int tag, VR vr, Object value) {
2004        int index = indexForInsertOf(tag);
2005        if (index >= 0) {
2006            Object oldValue = values[index];
2007            vrs[index] = vr;
2008            values[index] = value;
2009            return oldValue;
2010        }
2011        insert(-index - 1, tag, vr, value);
2012        return null;
2013    }
2014
2015    private void insert(int index, int tag, VR vr, Object value) {
2016        ensureCapacity(size+1);
2017        int numMoved = size - index;
2018        if (numMoved > 0) {
2019            System.arraycopy(tags, index, tags, index+1, numMoved);
2020            System.arraycopy(vrs, index, vrs, index+1, numMoved);
2021            System.arraycopy(values, index, values, index+1, numMoved);
2022        }
2023        tags[index] = tag;
2024        vrs[index] = vr;
2025        values[index] = value;
2026        size++;
2027    }
2028
2029
2030    public boolean addAll(Attributes other) {
2031        return add(other, null, null, 0, 0, null, false, false, false, null);
2032    }
2033
2034    /**
2035     * Updates this Attributes object with all the attributes of
2036     * the "other" object, applying the same behaviour recursively
2037     * to the items of the Sequences (if the "other" attributes has
2038     * a sequence with only a part of the attributes, only those will
2039     * be updated in the original Sequence).
2040     *
2041     * Note: recursion will be applied only with Sequences containing one
2042     * item and having the original item not null. If this condition
2043     * is not supported, the complete sequence of the "other" Attributes
2044     * will be set to this one, as in addAll(Attributes other)
2045     *
2046     * @param other the other Attributes object
2047     * @return <tt>true</tt> if one ore more attribute are added or
2048     *          overwritten with a different value
2049     */
2050    public boolean updateRecursive (Attributes other) {
2051
2052        boolean toggleEndian = bigEndian != other.bigEndian;
2053        final int otherSize = other.size;
2054        int numAdd = 0;
2055        String privateCreator = null;
2056        int creatorTag = 0;
2057        for (int i = 0; i < otherSize; i++) {
2058
2059            int tag = other.tags[i];
2060            VR vr = other.vrs[i];
2061            Object value = other.values[i];
2062
2063            if (TagUtils.isPrivateCreator(tag)) {
2064                continue; // private creators will be automatically added with the private tags
2065            }
2066
2067            if (TagUtils.isPrivateTag(tag)) {
2068                int tmp = TagUtils.creatorTagOf(tag);
2069                if (creatorTag != tmp) {
2070                    creatorTag = tmp;
2071                    privateCreator = other.privateCreatorOf(tag);
2072                }
2073            } else {
2074                creatorTag = 0;
2075                privateCreator = null;
2076            }
2077
2078            if (value instanceof Sequence) {
2079
2080                int indexOfOriginalSequence = indexOf(tag);
2081
2082                if (indexOfOriginalSequence < 0 ) {
2083                    //Trying to recursively update an empty sequence, fallback to whole copy
2084                    set(privateCreator, tag, (Sequence) value, null);
2085                } else {
2086                    Sequence original = (Sequence) values[indexOfOriginalSequence];
2087
2088                    if (original.size() == 0) {
2089                        //as above, fallback to whole copy
2090                        set(privateCreator, tag, (Sequence) value, null);
2091                    }
2092                    else {
2093                        Sequence updated = ((Sequence) value);
2094
2095                        if (updated==null || updated.size() == 0)
2096                            continue;
2097
2098                        if (original.size() > 1 || updated.size()>1)
2099                            //Trying to recursively update a sequence with more than 1 item: fallback to whole copy
2100                            set(privateCreator, tag, updated, null);
2101                        else
2102                            //both original and updated sequences have 1 item
2103                            original.get(0).updateRecursive(updated.get(0));
2104                    }
2105                }
2106            } else if (value instanceof Fragments) {
2107                set(privateCreator, tag, (Fragments) value);
2108            } else {
2109                set(privateCreator, tag, vr,
2110                        toggleEndian(vr, value, toggleEndian));
2111            }
2112            numAdd++;
2113        }
2114        return numAdd != 0;
2115    }
2116
2117    /**
2118     * Filters this Attributes object returning an Attributes containing
2119     * all the properties found in the selection object and the relative
2120     * ancestors, if any.
2121     *
2122     * Example:
2123     *
2124     * original:
2125     * (0010,0020) LO [PatientID] PatientID
2126     * (0010,0021) LO [IssuerOfPatientID] IssuerOfPatientID
2127     * (0010,1002) SQ [1 Items] OtherPatientIDsSequence
2128     * >Item #1
2129     * >(0010,0020) LO [OtherPatientID] PatientID
2130     * >(0010,0021) LO [OtherIssuerOfPatientID] IssuerOfPatientID
2131     *
2132     * selection:
2133     * (0010,0020) LO [OtherPatientID] PatientID
2134     *
2135     * result:
2136     * (0010,1002) SQ [1 Items] OtherPatientIDsSequence
2137     * >Item #1
2138     * >(0010,0020) LO [OtherPatientID] PatientID
2139     *
2140     * @param selection selection filter
2141     * @return filtered Attributes
2142     */
2143    public Attributes filter (Attributes selection) {
2144
2145        Attributes filtered = new Attributes();
2146        for (int tag : tags()) {
2147            if (selection.contains(getPrivateCreator(tag),tag)) {
2148                if (equalValues(selection, indexOf(getPrivateCreator(tag),tag),
2149                        selection.indexOf(getPrivateCreator(tag),tag)))
2150                            filtered.setValue(getPrivateCreator(tag),tag, getVR(tag), getValue(tag));
2151            }
2152            Attributes nested;
2153            if (getVR(getPrivateCreator(tag),tag) == VR.SQ &&
2154                    (nested = getNestedDataset(getPrivateCreator(tag),tag))!=null) {
2155                Attributes seq = nested.filter(selection);
2156                if (seq.size()>0) {
2157                    Sequence sequence = filtered.newSequence(getPrivateCreator(tag),tag,seq.size());
2158                    sequence.add(0,seq);
2159                }
2160            }
2161        }
2162        return filtered;
2163    }
2164
2165    public boolean merge(Attributes other) {
2166        return add(other, null, null, 0, 0, null, true, false, false, null);
2167    }
2168
2169    public boolean testMerge(Attributes other) {
2170        return add(other, null, null, 0, 0, null, true, false, true, null);
2171    }
2172
2173    public boolean addSelected(Attributes other, Attributes selection) {
2174        return add(other, null, null, 0, 0, selection, false, false, false, null);
2175    }
2176
2177    public boolean addSelected(Attributes other, String privateCreator, int tag) {
2178        int index = other.indexOf(privateCreator, tag);
2179        if (index < 0)
2180            return false;
2181        Object value = other.values[index];
2182        if (value instanceof Sequence) {
2183            set(privateCreator, tag, (Sequence) value, null);
2184        } else if (value instanceof Fragments) {
2185            set(privateCreator, tag, (Fragments) value);
2186        } else {
2187            VR vr = other.vrs[index];
2188            set(privateCreator, tag, vr,
2189                    toggleEndian(vr, value, bigEndian != other.bigEndian));
2190        }
2191        return true;
2192    }
2193
2194    public boolean addWithoutBulkData(Attributes other, BulkDataDescriptor descriptor) {
2195        final boolean toggleEndian = bigEndian != other.bigEndian;
2196        final int[] tags = other.tags;
2197        final VR[] srcVRs = other.vrs;
2198        final Object[] srcValues = other.values;
2199        final int otherSize = other.size;
2200        int numAdd = 0;
2201        String privateCreator = null;
2202        int creatorTag = 0;
2203        ItemPointer[] itemPointer = itemPointers();
2204        for (int i = 0; i < otherSize; i++) {
2205            int tag = tags[i];
2206            VR vr = srcVRs[i];
2207            Object value = srcValues[i];
2208            if (TagUtils.isPrivateCreator(tag)) {
2209                if (contains(tag))
2210                    continue; // do not overwrite private creator IDs
2211
2212                if (vr == VR.LO) {
2213                    value = other.decodeStringValue(i);
2214                    if ((value instanceof String)
2215                            && creatorTagOf((String) value, tag, false) != -1)
2216                        continue; // do not add duplicate private creator ID
2217                }
2218            }
2219            if (TagUtils.isPrivateTag(tag)) {
2220                int tmp = TagUtils.creatorTagOf(tag);
2221                if (creatorTag != tmp) {
2222                    creatorTag = tmp;
2223                    privateCreator = other.privateCreatorOf(tag);
2224                }
2225            } else {
2226                creatorTag = 0;
2227                privateCreator = null;
2228            }
2229            int vallen = (value instanceof byte[])
2230                    ? ((byte[])value).length
2231                    : -1;
2232            if (descriptor.isBulkData(privateCreator, tag, vr, vallen, itemPointer))
2233                continue;
2234
2235            if (value instanceof Sequence) {
2236                Sequence src = (Sequence) value;
2237                setWithoutBulkData(privateCreator, tag, src, descriptor);
2238            } else if (value instanceof Fragments) {
2239                set(privateCreator, tag, (Fragments) value);
2240            } else {
2241                set(privateCreator, tag, vr,
2242                        toggleEndian(vr, value, toggleEndian));
2243            }
2244            numAdd++;
2245        }
2246        return numAdd != 0;
2247    }
2248
2249    private void setWithoutBulkData(String privateCreator, int tag, Sequence seq,
2250                                    BulkDataDescriptor descriptor) {
2251        Sequence newSequence = newSequence(privateCreator, tag, seq.size());
2252        for (Attributes item : seq) {
2253            Attributes newItem = new Attributes(bigEndian, item.size());
2254            newSequence.add(newItem);
2255            newItem.addWithoutBulkData(item, descriptor);
2256        }
2257    }
2258
2259    /**
2260     * Add selected attributes from another Attributes object to this.
2261     * The specified array of tag values must be sorted (as by the
2262     * {@link java.util.Arrays#sort(int[])} method) prior to making this call.
2263     * 
2264     * @param other the other Attributes object
2265     * @param selection sorted tag values
2266     * @return <tt>true</tt> if one ore more attributes were added
2267     */
2268    public boolean addSelected(Attributes other, int... selection) {
2269        return addSelected(other, selection, 0, selection.length);
2270    }
2271
2272    /**
2273     * Add selected attributes from another Attributes object to this.
2274     * The specified array of tag values must be sorted (as by the
2275     * {@link java.util.Arrays#sort(int[], int, int)} method) prior to making this call.
2276     * 
2277     * @param other the other Attributes object
2278     * @param selection sorted tag values
2279     * @param fromIndex the index of the first tag (inclusive)
2280     * @param toIndex the index of the last tag (exclusive)
2281     * @return <tt>true</tt> if one ore more attributes were added
2282     */
2283    public boolean addSelected(Attributes other, int[] selection,
2284            int fromIndex, int toIndex) {
2285        return add(other, selection, null, fromIndex, toIndex, null, false, false, false, null);
2286    }
2287
2288    /**
2289     * Merge selected attributes from another Attributes object into this.
2290     * Does not overwrite existing non-empty attributes.
2291     * The specified array of tag values must be sorted (as by the
2292     * {@link java.util.Arrays#sort(int[])} method) prior to making this call.
2293     * 
2294     * @param other the other Attributes object
2295     * @param selection sorted tag values
2296     * @return <tt>true</tt> if one ore more attributes were added
2297     */
2298    public boolean mergeSelected(Attributes other, int... selection) {
2299        return add(other, selection, null, 0, selection.length, null, true, false, false, null);
2300    }
2301
2302    /**
2303     * Tests if {@link #mergeSelected} would modify attributes, without actually
2304     * modifying this attributes
2305     * 
2306     * @param other the other Attributes object
2307     * @param selection sorted tag values
2308     * @return <tt>true</tt> if one ore more attributes would have been added
2309     */
2310    public boolean testMergeSelected(Attributes other, int... selection) {
2311        return add(other, selection, null, 0, selection.length, null, true, false, true, null);
2312    }
2313
2314    /**
2315     * Add not selected attributes from another Attributes object to this.
2316     * The specified array of tag values must be sorted (as by the
2317     * {@link java.util.Arrays#sort(int[])} method) prior to making this call.
2318     * 
2319     * @param other the other Attributes object
2320     * @param selection sorted tag values
2321     * @return <tt>true</tt> if one ore more attributes were added
2322     */
2323    public boolean addNotSelected(Attributes other, int... selection) {
2324        return addNotSelected(other, selection, 0, selection.length);
2325    }
2326
2327    /**
2328     * Add not selected attributes from another Attributes object to this.
2329     * The specified array of tag values must be sorted (as by the
2330     * {@link java.util.Arrays#sort(int[])} method) prior to making this call.
2331     * 
2332     * @param other the other Attributes object
2333     * @param selection sorted tag values
2334     * @param fromIndex the index of the first tag (inclusive)
2335     * @param toIndex the index of the last tag (exclusive)
2336     * @return <tt>true</tt> if one ore more attributes were added
2337     */
2338    public boolean addNotSelected(Attributes other, int[] selection,
2339            int fromIndex, int toIndex) {
2340        return add(other, null, selection, fromIndex, toIndex, null, false, false, false, null);
2341    }
2342
2343    private boolean add(Attributes other, int[] include, int[] exclude,
2344            int fromIndex, int toIndex, Attributes selection, boolean merge,
2345            boolean update, boolean simulate, Attributes modified) {
2346        boolean toggleEndian = bigEndian != other.bigEndian;
2347        boolean modifiedToggleEndian = modified != null
2348                && bigEndian != modified.bigEndian;
2349        final int[] otherTags = other.tags;
2350        final VR[] srcVRs = other.vrs;
2351        final Object[] srcValues = other.values;
2352        final int otherSize = other.size;
2353        int numAdd = 0;
2354        String privateCreator = null;
2355        int creatorTag = 0;
2356        for (int i = 0; i < otherSize; i++) {
2357            int tag = otherTags[i];
2358            VR vr = srcVRs[i];
2359            Object value = srcValues[i];
2360            if (TagUtils.isPrivateCreator(tag)) {
2361                if (vr != VR.LO) {
2362                    LOG.info("Private Creator Element with wrong VR corrected! tag:{}, vr:{}, value:{}",
2363                            TagUtils.toString(tag), vr, value);
2364                    srcVRs[i] = VR.LO;
2365                }
2366                continue; // private creators will be automatically added with the private tags
2367            }
2368
2369            if (include != null && Arrays.binarySearch(include, fromIndex, toIndex, tag) < 0)
2370                continue;
2371            if (exclude != null && Arrays.binarySearch(exclude, fromIndex, toIndex, tag) >= 0)
2372                continue;
2373
2374            if (TagUtils.isPrivateTag(tag)) {
2375                int tmp = TagUtils.creatorTagOf(tag);
2376                if (creatorTag != tmp) {
2377                    creatorTag = tmp;
2378                    privateCreator = other.privateCreatorOf(tag);
2379                }
2380            } else {
2381                creatorTag = 0;
2382                privateCreator = null;
2383            }
2384
2385            if (selection != null && !selection.contains(privateCreator, tag))
2386                continue;
2387
2388            if (merge || update) {
2389                int j = indexOf(tag);
2390                if (j >= 0) {
2391                    if (update && equalValues(other, j, i)) {
2392                        continue;
2393                    }
2394                    Object origValue = vrs[j].isStringType()
2395                            ? decodeStringValue(j)
2396                            : values[j];
2397                    if (!isEmpty(origValue)) {
2398                        if (merge) {
2399                            continue;
2400                        }
2401                        if (modified != null) {
2402                            if (origValue instanceof Sequence) {
2403                                modified.set(privateCreator, tag, (Sequence) origValue, null);
2404                            } else if (origValue instanceof Fragments) {
2405                                modified.set(privateCreator, tag, (Fragments) origValue);
2406                            } else {
2407                                modified.set(privateCreator, tag, vr,
2408                                        toggleEndian(vr, origValue, modifiedToggleEndian));
2409                            }
2410                        }
2411                    }
2412                }
2413            }
2414            if (!simulate) {
2415                if (value instanceof Sequence) {
2416                    set(privateCreator, tag, (Sequence) value,
2417                            selection != null 
2418                                ? selection.getNestedDataset(privateCreator, tag)
2419                                : null);
2420                } else if (value instanceof Fragments) {
2421                    set(privateCreator, tag, (Fragments) value);
2422                } else {
2423                    set(privateCreator, tag, vr,
2424                            toggleEndian(vr, value, toggleEndian));
2425                }
2426            }
2427            numAdd++;
2428       }
2429        return numAdd != 0;
2430    }
2431
2432    public boolean update(Attributes newAttrs, Attributes modified) {
2433        return add(newAttrs, null, null, 0, 0, null, false, true, false, modified);
2434    }
2435
2436    public boolean testUpdate(Attributes newAttrs, Attributes modified) {
2437        return add(newAttrs, null, null, 0, 0, null, false, true, true, modified);
2438    }
2439
2440    /**
2441     * Add selected attributes from another Attributes object to this.
2442     * Optionally, the original values of overwritten existing non-empty
2443     * attributes are preserved in another Attributes object. 
2444     * The specified array of tag values must be sorted (as by the
2445     * {@link java.util.Arrays#sort(int[])} method) prior to making this call.
2446     * 
2447     * @param newAttrs the other Attributes object
2448     * @param modified Attributes object to collect overwritten non-empty
2449     *          attributes with original values or <tt>null</tt>
2450     * @param selection sorted tag values
2451     * @return <tt>true</tt> if one ore more attribute were added or
2452     *          overwritten with a different value
2453     */
2454    public boolean updateSelected(Attributes newAttrs,
2455            Attributes modified, int... selection) {
2456        return add(newAttrs, selection, null, 0, selection.length, null, false, true,
2457                false, modified);
2458    }
2459
2460    /**
2461     * Tests if {@link #updateSelected} would modify attributes, without actually
2462     * modifying this attributes
2463     * 
2464     * @param newAttrs the other Attributes object
2465     * @param modified Attributes object to collect overwritten non-empty
2466     *          attributes with original values or <tt>null</tt>
2467     * @param selection sorted tag values
2468     * @return <tt>true</tt> if one ore more attribute would be added or
2469     *          overwritten with a different value
2470     */
2471    public boolean testUpdateSelected(Attributes newAttrs, Attributes modified,
2472            int... selection) {
2473        return add(newAttrs, selection, null, 0, selection.length, null,
2474                false, true, true, modified);
2475    }
2476
2477    private static Object toggleEndian(VR vr, Object value, boolean toggleEndian) {
2478        return (toggleEndian && value instanceof byte[])
2479                ? vr.toggleEndian((byte[]) value, true)
2480                : value;
2481    }
2482
2483    @Override
2484    public boolean equals(Object o) {
2485        if (o == this)
2486            return true;
2487
2488        if (!(o instanceof Attributes))
2489            return false;
2490
2491        final Attributes other = (Attributes) o;
2492        if (size != other.size)
2493            return false;
2494
2495        int creatorTag = 0;
2496        int otherCreatorTag = 0;
2497        for (int i = 0; i < size; i++) {
2498            int tag = tags[i];
2499            if (!TagUtils.isPrivateGroup(tag)) {
2500                if (tag != other.tags[i] || !equalValues(other, i, i))
2501                    return false;
2502            } else if (TagUtils.isPrivateTag(tag)) {
2503                int tmp = TagUtils.creatorTagOf(tag);
2504                if (creatorTag != tmp) {
2505                    creatorTag = tmp;
2506                    otherCreatorTag = other.creatorTagOf(privateCreatorOf(tag), tag, false);
2507                    if (otherCreatorTag == -1)
2508                        return false;
2509                }
2510                int j = other.indexOf(TagUtils.toPrivateTag(otherCreatorTag, tag));
2511                if (j < 0 || !equalValues(other, i, j))
2512                    return false;
2513            }
2514        }
2515        return true;
2516   }
2517
2518    private boolean equalValues(Attributes other, int index, int otherIndex) {
2519        VR vr = vrs[index];
2520        if (vr != other.vrs[otherIndex])
2521            return false;
2522        if (vr.isStringType())
2523            if (vr == VR.IS)
2524                return equalISValues(other, index, otherIndex);
2525            else if (vr == VR.DS)
2526                return equalDSValues(other, index, otherIndex);
2527            else
2528                return equalStringValues(other, index, otherIndex);
2529        Object v1 = values[index];
2530        Object v2 = other.values[otherIndex];
2531        if (v1 instanceof byte[]) {
2532            if (v2 instanceof byte[] && ((byte[]) v1).length == ((byte[]) v2).length) {
2533                if (bigEndian != other.bigEndian)
2534                    v2 = vr.toggleEndian((byte[]) v2, true);
2535                return Arrays.equals((byte[]) v1, (byte[]) v2);
2536            }
2537        } else
2538            return v1.equals(v2);
2539        return false;
2540    }
2541
2542    private boolean equalISValues(Attributes other, int index, int otherIndex) {
2543        try {
2544            return Arrays.equals(decodeISValue(index), other.decodeISValue(otherIndex));
2545        } catch (NumberFormatException e) {
2546            return equalStringValues(other, index, otherIndex);
2547        }
2548    }
2549
2550    private boolean equalDSValues(Attributes other, int index, int otherIndex) {
2551        try {
2552            return Arrays.equals(decodeDSValue(index), other.decodeDSValue(otherIndex));
2553        } catch (NumberFormatException e) {
2554            return equalStringValues(other, index, otherIndex);
2555        }
2556    }
2557
2558    private boolean equalStringValues(Attributes other, int index, int otherIndex) {
2559        Object v1 = decodeStringValue(index);
2560        Object v2 = other.decodeStringValue(otherIndex);
2561        if (v1 instanceof String[]) {
2562            if (v2 instanceof String[])
2563                return Arrays.equals((String[]) v1, (String[]) v2);
2564        } else
2565            return v1.equals(v2);
2566        return false;
2567    }
2568
2569    @Override
2570    public int hashCode() {
2571        int h = 0;
2572        for (int i = 0; i < size; i++) {
2573            int tag = tags[i];
2574            if (!TagUtils.isPrivateGroup(tag))
2575                h = 31*h + tag;
2576        }
2577        return h;
2578    }
2579
2580    private void set(String privateCreator, int tag, Sequence src,
2581            Attributes selection) {
2582        Sequence dst = newSequence(privateCreator, tag, src.size());
2583        for (Attributes item : src)
2584            dst.add(selection != null && !selection.isEmpty()
2585                ? new Attributes(item, bigEndian, selection)
2586                : new Attributes(item, bigEndian));
2587    }
2588
2589    private void set(String privateCreator, int tag, Fragments src) {
2590        boolean toogleEndian = src.bigEndian() != bigEndian;
2591        VR vr = src.vr();
2592        Fragments dst = newFragments(privateCreator, tag, vr, src.size());
2593        for (Object frag : src)
2594            dst.add(toggleEndian(vr, frag, toogleEndian));
2595    }
2596
2597    @Override
2598    public String toString() {
2599        return toString(TO_STRING_LIMIT, TO_STRING_WIDTH);
2600    }
2601
2602    public String toString(Deidentifier deidentifier) {
2603        return toString(TO_STRING_LIMIT, TO_STRING_WIDTH, deidentifier);
2604    }
2605
2606    public String toString(int limit, int maxWidth) {
2607        return toStringBuilder(limit, maxWidth, new StringBuilder(1024), null)
2608                .toString();
2609    }
2610
2611    public String toString(int limit, int maxWidth, Deidentifier deidentifier) {
2612        return toStringBuilder(limit, maxWidth, new StringBuilder(1024), deidentifier)
2613                .toString();
2614    }
2615
2616    public StringBuilder toStringBuilder(StringBuilder sb) {
2617        return toStringBuilder(TO_STRING_LIMIT, TO_STRING_WIDTH, sb, null);
2618    }
2619
2620    public StringBuilder toStringBuilder(int limit, int maxWidth, StringBuilder sb, Deidentifier deidentifier) {
2621        if (appendAttributes(limit, maxWidth, sb, "", deidentifier) > limit)
2622            sb.append("...\n");
2623        return sb;
2624    }
2625
2626    private int appendAttributes(int limit, int maxWidth, StringBuilder sb, String prefix, Deidentifier deidentifier) {
2627        int lines = 0;
2628        int creatorTag = 0;
2629        String privateCreator = null;
2630        for (int i = 0; i < size; i++) {
2631            if (++lines > limit)
2632                break;
2633            int tag = tags[i];
2634            if (TagUtils.isPrivateTag(tag)) {
2635                int tmp = TagUtils.creatorTagOf(tag);
2636                if (creatorTag != tmp) {
2637                    creatorTag = tmp;
2638                    privateCreator = getString(creatorTag, null);
2639                }
2640            } else {
2641                creatorTag = 0;
2642                privateCreator = null;
2643            }
2644            Object value = values[i];
2645            appendAttribute(privateCreator, tag, vrs[i], value, sb.length() + maxWidth, sb, prefix, deidentifier);
2646            if (value instanceof Sequence)
2647                lines += appendItems((Sequence) value, limit - lines, maxWidth, sb, prefix + '>', deidentifier);
2648        }
2649        return lines;
2650    }
2651
2652    private int appendItems(Sequence sq, int limit, int maxWidth, StringBuilder sb,
2653            String prefix, Deidentifier deidentifier) {
2654        int lines = 0;
2655        int itemNo = 0;
2656        for (Attributes item : sq) {
2657            if (++lines > limit)
2658                break;
2659            sb.append(prefix).append("Item #").append(++itemNo).append('\n');
2660            lines += item.appendAttributes(limit - lines, maxWidth, sb, prefix, deidentifier);
2661        }
2662        return lines ;
2663    }
2664
2665    private StringBuilder appendAttribute(String privateCreator, int tag, VR vr, Object value,
2666            int maxLength, StringBuilder sb, String prefix, Deidentifier deidentifier) {
2667        sb.append(prefix).append(TagUtils.toString(tag)).append(' ').append(vr).append(" [");
2668        if (vr.prompt((deidentifier!=null ? deidentifier.deidentify(tag, vr, value) :value), bigEndian,
2669                getSpecificCharacterSet(vr),
2670                maxLength - sb.length() - 1, sb)) {
2671            sb.append("] ").append(ElementDictionary.keywordOf(tag, privateCreator));
2672            if (sb.length() > maxLength)
2673                sb.setLength(maxLength);
2674        }
2675        sb.append('\n');
2676        return sb;
2677    }
2678
2679    public int calcLength(DicomEncodingOptions encOpts, boolean explicitVR) {
2680        if (isEmpty())
2681            return 0;
2682
2683        this.groupLengths = encOpts.groupLength 
2684                ? new int[countGroups()]
2685                : null;
2686        this.length = calcLength(encOpts, explicitVR, 
2687                getSpecificCharacterSet(), groupLengths);
2688        return this.length;
2689    }
2690
2691    private int calcLength(DicomEncodingOptions encOpts, boolean explicitVR,
2692            SpecificCharacterSet cs, int[] groupLengths) {
2693        int len, totlen = 0;
2694        int groupLengthTag = -1;
2695        int groupLengthIndex = -1;
2696        VR vr;
2697        Object val;
2698        for (int i = 0; i < size; i++) {
2699            vr = vrs[i];
2700            val = values[i];
2701            len = explicitVR ? vr.headerLength() : 8;
2702            if (val instanceof Value)
2703                len += ((Value) val).calcLength(encOpts, explicitVR, vr);
2704            else {
2705                if (!(val instanceof byte[]))
2706                    values[i] = val = vr.toBytes(val, cs);
2707                len += (((byte[]) val).length + 1) & ~1;
2708            }
2709            totlen += len;
2710            if (groupLengths != null) {
2711                int tmp = TagUtils.groupLengthTagOf(tags[i]);
2712                if (groupLengthTag != tmp) {
2713                    groupLengthTag = tmp;
2714                    groupLengthIndex++;
2715                    totlen += 12;
2716                }
2717                groupLengths[groupLengthIndex] += len;
2718            }
2719        }
2720        return totlen;
2721    }
2722
2723    private int countGroups() {
2724        int groupLengthTag = -1;
2725        int count = 0;
2726        for (int i = 0; i < size; i++) {
2727            int tmp = TagUtils.groupLengthTagOf(tags[i]);
2728            if (groupLengthTag != tmp) {
2729                if (groupLengthTag < 0)
2730                    this.groupLengthIndex0 = count;
2731                groupLengthTag = tmp;
2732                count++;
2733            }
2734        }
2735        return count;
2736    }
2737
2738    public void writeTo(DicomOutputStream out)
2739            throws IOException {
2740        if (isEmpty())
2741            return;
2742
2743        if (groupLengths == null && out.getEncodingOptions().groupLength)
2744            throw new IllegalStateException(
2745                    "groupLengths not initialized by calcLength()");
2746
2747        SpecificCharacterSet cs = getSpecificCharacterSet();
2748        if (tags[0] < 0) {
2749            int index0 = -(1 + indexOf(0));
2750            writeTo(out, cs, index0, size, groupLengthIndex0);
2751            writeTo(out, cs, 0, index0, 0);
2752        } else {
2753            writeTo(out, cs, 0, size, 0);
2754        }
2755    }
2756
2757     public void writeItemTo(DicomOutputStream out) throws IOException {
2758         DicomEncodingOptions encOpts = out.getEncodingOptions();
2759         int len = getEncodedItemLength(encOpts, out.isExplicitVR());
2760         out.writeHeader(Tag.Item, null, len);
2761         writeTo(out);
2762         if (len == -1)
2763             out.writeHeader(Tag.ItemDelimitationItem, null, 0);
2764     }
2765
2766    private int getEncodedItemLength(DicomEncodingOptions encOpts,
2767            boolean explicitVR) {
2768        if (isEmpty())
2769            return encOpts.undefEmptyItemLength ? -1 : 0;
2770
2771        if (encOpts.undefItemLength)
2772            return -1;
2773
2774        if (length == -1)
2775            calcLength(encOpts, explicitVR);
2776
2777        return length;
2778    }
2779
2780    private void writeTo(DicomOutputStream out, SpecificCharacterSet cs,
2781            int start, int end, int groupLengthIndex) throws IOException {
2782        boolean groupLength = groupLengths != null;
2783        int groupLengthTag = -1;
2784        for (int i = start; i < end; i++) {
2785            int tag = tags[i];
2786            if (groupLength) {
2787                int tmp = TagUtils.groupLengthTagOf(tag);
2788                if (groupLengthTag != tmp) {
2789                    groupLengthTag = tmp;
2790                    out.writeGroupLength(groupLengthTag,
2791                            groupLengths[groupLengthIndex++]);
2792                }
2793            }
2794            out.writeAttribute(tag, vrs[i], values[i], cs);
2795        }
2796    }
2797
2798    /**
2799     * Invokes {@link Visitor#visit} for each attribute in this instance. The
2800     * operation will be aborted if <code>visitor.visit()</code> returns
2801     * <code>false</code> or throws an exception.
2802     * 
2803     * @param visitor
2804     * @param visitNestedDatasets
2805     *            controls if <code>visitor.visit()</code> is also invoked for
2806     *            attributes in nested datasets
2807     * @return <code>true</code> if the operation was not aborted.
2808     * @throws Exception
2809     *             exception thrown by {@link Visitor#visit}
2810     */
2811    public boolean accept(Visitor visitor, boolean visitNestedDatasets)
2812            throws Exception{
2813        if (isEmpty())
2814            return true;
2815
2816        if (tags[0] < 0) {
2817            int index0 = -(1 + indexOf(0));
2818            return accept(visitor, visitNestedDatasets, index0, size)
2819                && accept(visitor, visitNestedDatasets, 0, index0);
2820        } else {
2821            return accept(visitor, visitNestedDatasets, 0, size);
2822        }
2823    }
2824
2825    private boolean accept(Visitor visitor, boolean visitNestedDatasets,
2826            int start, int end) throws Exception {
2827        for (int i = start; i < end; i++) {
2828            if (!visitor.visit(this, tags[i], vrs[i], values[i]))
2829                return false;
2830            if (visitNestedDatasets && (values[i] instanceof Sequence)) {
2831                for (Attributes item : (Sequence) values[i]) {
2832                    if (!item.accept(visitor, true))
2833                        return false;
2834                }
2835            }
2836        }
2837        return true;
2838    }
2839
2840    public void writeGroupTo(DicomOutputStream out, int groupLengthTag)
2841            throws IOException {
2842        if (isEmpty())
2843            throw new IllegalStateException("No attributes");
2844        
2845        checkInGroup(0, groupLengthTag);
2846        checkInGroup(size-1, groupLengthTag);
2847        SpecificCharacterSet cs = getSpecificCharacterSet();
2848        out.writeGroupLength(groupLengthTag,
2849                calcLength(out.getEncodingOptions(), out.isExplicitVR(), cs, null));
2850        writeTo(out, cs, 0, size, 0);
2851    }
2852
2853
2854    private void checkInGroup(int i, int groupLengthTag) {
2855        int tag = tags[i];
2856        if (TagUtils.groupLengthTagOf(tag) != groupLengthTag)
2857            throw new IllegalStateException(TagUtils.toString(tag)
2858                    + " does not belong to group (" 
2859                    + TagUtils.shortToHexString(
2860                            TagUtils.groupNumber(groupLengthTag))
2861                    + ",eeee).");
2862        
2863    }
2864
2865    public Attributes createFileMetaInformation(String tsuid) {
2866        return createFileMetaInformation(
2867                getString(Tag.SOPInstanceUID, null),
2868                getString(Tag.SOPClassUID, null),
2869                tsuid);
2870    }
2871
2872    public static Attributes createFileMetaInformation(String iuid,
2873            String cuid, String tsuid) {
2874        if (iuid == null || iuid.isEmpty())
2875            throw new IllegalArgumentException("Missing SOP Instance UID");
2876        if (cuid == null || cuid.isEmpty())
2877            throw new IllegalArgumentException("Missing SOP Class UID");
2878        if (tsuid == null || tsuid.isEmpty())
2879            throw new IllegalArgumentException("Missing Transfer Syntax UID");
2880
2881        Attributes fmi = new Attributes(6);
2882        fmi.setBytes(Tag.FileMetaInformationVersion, VR.OB,
2883                new byte[]{ 0, 1 });
2884        fmi.setString(Tag.MediaStorageSOPClassUID, VR.UI, cuid);
2885        fmi.setString(Tag.MediaStorageSOPInstanceUID, VR.UI, iuid);
2886        fmi.setString(Tag.TransferSyntaxUID, VR.UI, tsuid);
2887        fmi.setString(Tag.ImplementationClassUID, VR.UI,
2888                Implementation.getClassUID());
2889        fmi.setString(Tag.ImplementationVersionName, VR.SH,
2890                Implementation.getVersionName());
2891        return fmi;
2892    }
2893
2894    public boolean matches(Attributes keys, boolean ignorePNCase,
2895            boolean matchNoValue) {
2896        int[] keyTags = keys.tags;
2897        VR[] keyVrs = keys.vrs;
2898        Object[] keyValues = keys.values;
2899        int keysSize = keys.size;
2900        String privateCreator = null;
2901        int creatorTag = 0;
2902        for (int i = 0; i < keysSize; i++) {
2903            int tag = keyTags[i];
2904            if (TagUtils.isPrivateCreator(tag))
2905                continue;
2906
2907            if (TagUtils.isPrivateGroup(tag)) {
2908                int tmp = TagUtils.creatorTagOf(tag);
2909                if (creatorTag != tmp) {
2910                    creatorTag = tmp;
2911                    privateCreator = keys.getString(creatorTag, null);
2912                }
2913            } else {
2914                creatorTag = 0;
2915                privateCreator = null;
2916            }
2917
2918            Object keyValue = keyValues[i];
2919            if (isEmpty(keyValue))
2920                continue;
2921
2922            if (keyVrs[i].isStringType()) {
2923                if (!matches(privateCreator, tag, keyVrs[i], ignorePNCase,
2924                        matchNoValue, keys.getStrings(privateCreator, tag, null)))
2925                    return false;
2926            } else if (keyValue instanceof Sequence) {
2927                if (!matches(privateCreator, tag, ignorePNCase, matchNoValue,
2928                        (Sequence) keyValue))
2929                    return false;
2930            } else {
2931                throw new UnsupportedOperationException("Keys with VR: "
2932                        + keyVrs[i] + " not supported");
2933            }
2934        }
2935        return true;
2936    }
2937
2938    private boolean matches(String privateCreator, int tag, VR vr,
2939            boolean ignorePNCase, boolean matchNoValue, String[] keyVals) {
2940        String[] vals = getStrings(privateCreator, tag, null);
2941        if (vals == null || vals.length == 0)
2942            return matchNoValue;
2943
2944        boolean ignoreCase = ignorePNCase && vr == VR.PN;
2945        for (String keyVal : keyVals) {
2946            if (vr == VR.PN)
2947                keyVal = new PersonName(keyVals[0]).toString();
2948    
2949            if (StringUtils.containsWildCard(keyVal)) {
2950                Pattern pattern = StringUtils.compilePattern(keyVal, ignoreCase);
2951                for (String val : vals) {
2952                    if (val == null)
2953                        if (matchNoValue)
2954                            return true;
2955                        else
2956                            continue;
2957                    if (vr == VR.PN)
2958                        val = new PersonName(val).toString();
2959                    if (pattern.matcher(val).matches())
2960                        return true;
2961                }
2962            } else {
2963                for (String val : vals) {
2964                    if (val == null)
2965                        if (matchNoValue)
2966                            return true;
2967                        else
2968                            continue;
2969                    if (vr == VR.PN)
2970                        val = new PersonName(val).toString();
2971                    if (ignoreCase ? keyVal.equalsIgnoreCase(val)
2972                                   : keyVal.equals(val))
2973                        return true;
2974                }
2975            }
2976        }
2977        return false;
2978    }
2979
2980    private boolean matches(String privateCreator, int tag, boolean ignorePNCase, 
2981            boolean matchNoValue, Sequence keySeq) {
2982        int n = keySeq.size();
2983        if (n > 1)
2984            throw new IllegalArgumentException("Keys contain Sequence "
2985                    + TagUtils.toString(tag) + " with " + n + " Items");
2986
2987        Attributes keys = keySeq.get(0);
2988        if (keys.isEmpty())
2989            return true;
2990
2991        Object value = getValue(privateCreator, tag);
2992        if (value == null || isEmpty(value))
2993            return matchNoValue;
2994
2995        if (value instanceof Sequence) {
2996            Sequence sq = (Sequence) value;
2997            for (Attributes item : sq)
2998                if (item.matches(keys, ignorePNCase, matchNoValue))
2999                    return true;
3000        }
3001        return false;
3002    }
3003
3004    private static final long serialVersionUID = 7868714416968825241L;
3005
3006    private void writeObject(ObjectOutputStream out) throws IOException {
3007        out.defaultWriteObject();
3008        out.writeInt(size);
3009        @SuppressWarnings("resource")
3010        DicomOutputStream dout = new DicomOutputStream(out,
3011                bigEndian ? UID.ExplicitVRBigEndianRetired
3012                          : UID.ExplicitVRLittleEndian);
3013        dout.writeDataset(null, this);
3014        dout.writeHeader(Tag.ItemDelimitationItem, null, 0);
3015    }
3016
3017    private void readObject(ObjectInputStream in)
3018            throws IOException, ClassNotFoundException {
3019        in.defaultReadObject();
3020        init(in.readInt());
3021        @SuppressWarnings("resource")
3022        DicomInputStream din = new DicomInputStream(in, 
3023                bigEndian ? UID.ExplicitVRBigEndianRetired
3024                          : UID.ExplicitVRLittleEndian);
3025        din.readAttributes(this, -1, Tag.ItemDelimitationItem);
3026    }
3027
3028    public ValidationResult validate(IOD iod) {
3029        ValidationResult result = new ValidationResult();
3030        HashMap<String,Boolean> resolvedConditions = new HashMap<String,Boolean>();
3031        for (IOD.DataElement el : iod) {
3032            validate(el, result, resolvedConditions);
3033        }
3034        return result;
3035    }
3036
3037    public void validate(DataElement el, ValidationResult result) {
3038        validate(el, result, null);
3039    }
3040
3041    private void validate(DataElement el, ValidationResult result,
3042            Map<String, Boolean> processedConditions) {
3043        IOD.Condition condition = el.getCondition();
3044        if (condition != null) {
3045            String id = condition.id();
3046            Boolean match = id != null ? processedConditions.get(id) : null;
3047            if (match == null) {
3048                match = condition.match(this);
3049                if (id != null)
3050                    processedConditions.put(id, match);
3051            }
3052            if (!match)
3053                return;
3054        }
3055        int index = indexOf(el.tag);
3056        if (index < 0) {
3057            if (el.type == IOD.DataElementType.TYPE_1 
3058                    || el.type == IOD.DataElementType.TYPE_2) {
3059                result.addMissingAttribute(el);
3060            }
3061            return;
3062        }
3063        Object value = values[index];
3064        if (isEmpty(value)) {
3065            if (el.type == IOD.DataElementType.TYPE_1) {
3066                result.addMissingAttributeValue(el);
3067            }
3068            return;
3069        }
3070        if (el.type == IOD.DataElementType.TYPE_0) {
3071            result.addNotAllowedAttribute(el);
3072            return;
3073        }
3074        VR vr = vrs[index];
3075        if (vr.isStringType()) {
3076            value = decodeStringValue(index);
3077        }
3078
3079        Object validVals = el.getValues();
3080        if (el.vr == VR.SQ) {
3081            if (!(value instanceof Sequence)) {
3082                result.addInvalidAttributeValue(el, ValidationResult.Invalid.VR);
3083                return;
3084            }
3085            Sequence seq = (Sequence) value;
3086            int seqSize = seq.size();
3087            if (el.maxVM > 0 && seqSize > el.maxVM) {
3088                result.addInvalidAttributeValue(el, 
3089                        ValidationResult.Invalid.MultipleItems);
3090                return;
3091            }
3092            if (validVals instanceof Code[]) {
3093                boolean invalidItem = false;
3094                ValidationResult[] itemValidationResults = new ValidationResult[seqSize];
3095                for (int i = 0; i < seqSize; i++) {
3096                    ValidationResult itemValidationResult =
3097                            validateCode(seq.get(i), (Code[]) validVals);
3098                    invalidItem = invalidItem || !itemValidationResult.isValid();
3099                    itemValidationResults[i] = itemValidationResult;
3100                }
3101                if (invalidItem) {
3102                    result.addInvalidAttributeValue(el, 
3103                            ValidationResult.Invalid.Code, itemValidationResults, null);
3104                }
3105            } else if (validVals instanceof IOD[]) {
3106                IOD[] itemIODs = (IOD[]) validVals;
3107                int[] matchingItems = new int[itemIODs.length];
3108                boolean invalidItem = false;
3109                ValidationResult[] itemValidationResults = new ValidationResult[seqSize];
3110                for (int i = 0; i < seqSize; i++) {
3111                    ValidationResult itemValidationResult = new ValidationResult();
3112                    HashMap<String,Boolean> resolvedItemConditions =
3113                            new HashMap<String,Boolean>();
3114                    Attributes item = seq.get(i);
3115                    for (int j = 0; j < itemIODs.length; j++) {
3116                        IOD itemIOD = itemIODs[j];
3117                        IOD.Condition itemCondition = itemIOD.getCondition();
3118                        if (itemCondition != null) {
3119                            String id = itemCondition.id();
3120                            Boolean match = id != null ? resolvedItemConditions.get(id) : null;
3121                            if (match == null) {
3122                                match = itemCondition.match(item);
3123                                if (id != null)
3124                                    resolvedItemConditions.put(id, match);
3125                            }
3126                            if (!match)
3127                                continue;
3128                        }
3129                        matchingItems[j]++;
3130                        for (IOD.DataElement itemEl : itemIOD) {
3131                            item.validate(itemEl, itemValidationResult, resolvedItemConditions);
3132                        }
3133                    }
3134                    invalidItem = invalidItem || !itemValidationResult.isValid();
3135                    itemValidationResults[i] = itemValidationResult;
3136                }
3137                IOD[] missingItems = checkforMissingItems(matchingItems, itemIODs);
3138                if (invalidItem || missingItems != null) {
3139                    result.addInvalidAttributeValue(el,
3140                            ValidationResult.Invalid.Item, 
3141                            itemValidationResults, missingItems);
3142                }
3143            }
3144            return;
3145        }
3146
3147        if (el.maxVM > 0 || el.minVM > 1) {
3148            int vm = vr.vmOf(value);
3149            if (el.maxVM > 0 && vm > el.maxVM
3150             || el.minVM > 1 && vm < el.minVM) {
3151                result.addInvalidAttributeValue(el, ValidationResult.Invalid.VM);
3152                return;
3153            }
3154        }
3155        if (validVals == null)
3156            return;
3157        
3158        if (validVals instanceof String[]) {
3159            if (!vr.isStringType()) {
3160                result.addInvalidAttributeValue(el, ValidationResult.Invalid.VR);
3161                return;
3162            }
3163            if (!isValidValue(toStrings(value), el.valueNumber, (String[]) validVals)) {
3164                result.addInvalidAttributeValue(el, ValidationResult.Invalid.Value);
3165            }
3166        } else if (validVals instanceof int[]) {
3167            if (vr == VR.IS)
3168                value = decodeISValue(index);
3169            else if (!vr.isIntType()) {
3170                result.addInvalidAttributeValue(el, ValidationResult.Invalid.VR);
3171                return;
3172            }
3173            if (!isValidValue(vr.toInts(value, bigEndian), el.valueNumber, (int[]) validVals)) {
3174                result.addInvalidAttributeValue(el, ValidationResult.Invalid.Value);
3175            }
3176        }
3177    }
3178
3179    private IOD[] checkforMissingItems(int[] matchingItems, IOD[] itemIODs) {
3180        IOD[] missingItems = new IOD[matchingItems.length];
3181        int n = 0;
3182        for (int i = 0; i < matchingItems.length; i++) {
3183            IOD itemIOD = itemIODs[i];
3184            if (matchingItems[i] == 0
3185                    && itemIOD.getType() == DataElementType.TYPE_1)
3186                missingItems[n++] = itemIOD;
3187        }
3188        return n > 0 ? Arrays.copyOf(missingItems, n) : null;
3189    }
3190
3191    private ValidationResult validateCode(Attributes item, Code[] validVals) {
3192        ValidationResult result = null;
3193        for (Code code : validVals) {
3194            result = item.validate(IOD.valueOf(code));
3195            if (result.isValid())
3196                break;
3197        }
3198        return result;
3199    }
3200
3201    private boolean isValidValue(String[] val, int valueNumber, String[] validVals) {
3202        if (valueNumber != 0)
3203            return val.length < valueNumber || isOneOf(val[valueNumber-1], validVals);
3204
3205        for (int i = 0; i < val.length; i++)
3206            if (!isOneOf(val[i], validVals))
3207                return false;
3208        return true;
3209    }
3210
3211    private <T> boolean isOneOf(Object val, T[] ss) {
3212        if (ss == null)
3213            return true;
3214        for (T s : ss)
3215            if (val.equals(s))
3216                return true;
3217        return false;
3218    }
3219
3220    private boolean isValidValue(int[] val, int valueNumber, int[] validVals) {
3221        if (valueNumber != 0)
3222            return val.length < valueNumber || isOneOf(val[valueNumber-1], validVals);
3223
3224        for (int i = 0; i < val.length; i++)
3225            if (!isOneOf(val[i], validVals))
3226                return false;
3227        return true;
3228    }
3229
3230    private boolean isOneOf(int val, int[] is) {
3231        if (is == null)
3232            return true;
3233        for (int i : is)
3234            if (val == i)
3235                return true;
3236        return false;
3237    }
3238
3239    /**
3240     * Add attributes of this data set which were replaced in
3241     * the specified other data set into the result data set.
3242     * If no result data set is passed, a new result set will be instantiated.
3243     * 
3244     * @param other data set
3245     * @param result data set or {@code null} 
3246     *
3247     * @return result data set.
3248     */
3249    public Attributes getModified(Attributes other, Attributes result) {
3250        if (result == null)
3251            result = new Attributes(other.size);
3252        int creatorTag = -1;
3253        int prevOtherCreatorTag = -1;
3254        int otherCreatorTag = -1;
3255        String privateCreator = null;
3256        for (int i = 0; i < other.size; i++) {
3257            int tag = other.tags[i];
3258            if ((tag & 0x00010000) != 0) { // private group
3259                if ((tag & 0x0000ff00) == 0)
3260                    continue; // skip private creator
3261
3262                otherCreatorTag = TagUtils.creatorTagOf(tag);
3263                if (prevOtherCreatorTag != otherCreatorTag) {
3264                    prevOtherCreatorTag = otherCreatorTag;
3265                    creatorTag = -1;
3266                    int k = other.indexOf(otherCreatorTag);
3267                    if (k >= 0) {
3268                        Object o = other.decodeStringValue(k);
3269                        if (o instanceof String) {
3270                            privateCreator = (String) o;
3271                            creatorTag = creatorTagOf(
3272                                    privateCreator, tag, false);
3273                        }
3274                    }
3275                }
3276                if (creatorTag == -1)
3277                    continue; // no matching Private Creator
3278
3279                tag = TagUtils.toPrivateTag(creatorTag, tag);
3280            } else {
3281                privateCreator = null;
3282            }
3283
3284            int j = indexOf(tag);
3285            if (j < 0)
3286                continue;
3287
3288            Object origValue = values[j];
3289            if (origValue instanceof Value && ((Value) origValue).isEmpty())
3290                continue;
3291
3292            if (equalValues(other, j, i))
3293                continue;
3294
3295            if (origValue instanceof Sequence) {
3296                result.set(privateCreator, tag, (Sequence) origValue, null);
3297            } else if (origValue instanceof Fragments) {
3298                result.set(privateCreator, tag, (Fragments) origValue);
3299            } else {
3300                result.set(privateCreator, tag, vrs[j], origValue);
3301            }
3302        }
3303        return result;
3304    }
3305
3306    /**
3307     * Returns attributes of this data set which were removed or replaced in
3308     * the specified other data set.
3309     * 
3310     * @param other data set
3311     * @return attributes of this data set which were removed or replaced in
3312     *         the specified other data set.
3313     */
3314    public Attributes getRemovedOrModified(Attributes other) {
3315        Attributes modified = new Attributes(size);
3316        int creatorTag = -1;
3317        int prevCreatorTag = -1;
3318        int otherCreatorTag = 0;
3319        String privateCreator = null;
3320        for (int i = 0; i < size; i++) {
3321            int tag = tags[i];
3322            if ((tag & 0x00010000) != 0) { // private group
3323                if ((tag & 0x0000ff00) == 0)
3324                    continue; // skip private creator
3325
3326                creatorTag = TagUtils.creatorTagOf(tag);
3327                if (prevCreatorTag != creatorTag) {
3328                    prevCreatorTag = creatorTag;
3329                    otherCreatorTag = -1;
3330                    privateCreator = null;
3331                    int k = indexOf(creatorTag);
3332                    if (k >= 0) {
3333                        Object o = decodeStringValue(k);
3334                        if (o instanceof String) {
3335                            privateCreator = (String) o;
3336                            otherCreatorTag = other.creatorTagOf(
3337                                    privateCreator, tag, false);
3338                        }
3339                    }
3340                }
3341                if (privateCreator == null)
3342                    continue; // no Private Creator
3343
3344                if (otherCreatorTag != -1)
3345                    tag = TagUtils.toPrivateTag(otherCreatorTag, tag);
3346            } else {
3347                otherCreatorTag = 0;
3348                privateCreator = null;
3349            }
3350
3351            Object origValue = values[i];
3352            if (origValue instanceof Value && ((Value) origValue).isEmpty())
3353                continue;
3354
3355            if (otherCreatorTag >= 0) {
3356                int j = other.indexOf(tag);
3357                if (j >= 0 && equalValues(other, i, j))
3358                    continue;
3359            }
3360
3361            if (origValue instanceof Sequence) {
3362                modified.set(privateCreator, tag, (Sequence) origValue, null);
3363            } else if (origValue instanceof Fragments) {
3364                modified.set(privateCreator, tag, (Fragments) origValue);
3365            } else {
3366                modified.set(privateCreator, tag, vrs[i], origValue);
3367            }
3368        }
3369        return modified;
3370    }
3371
3372    private int creatorIndexOf(String privateCreator, int groupNumber) {
3373        if ((groupNumber & 1) == 0)
3374            throw new IllegalArgumentException(
3375                    "(" + TagUtils.shortToHexString(groupNumber) + ",xxxx) is not a private Group");
3376
3377        int group = groupNumber << 16;
3378        int creatorTag = group | 0x10;
3379        int index = indexOf(creatorTag);
3380        if (index < 0)
3381            index = -index-1;
3382        while (index < size && (tags[index] & 0xffffff00) == group) {
3383            if (vrs[index] == VR.LO) {
3384                Object creatorID = decodeStringValue(index);
3385                if (privateCreator.equals(creatorID))
3386                    return index;
3387            }
3388            index++;
3389            creatorTag++;
3390        }
3391        return -1;
3392    }
3393
3394    public int removePrivateAttributes(String privateCreator, int groupNumber) {
3395        int privateCreatorIndex = creatorIndexOf(privateCreator, groupNumber);
3396        if (privateCreatorIndex < 0)
3397            return 0;
3398
3399        int creatorTag = tags[privateCreatorIndex];
3400        int privateTag = (creatorTag & 0xffff0000) | ((creatorTag & 0xff) << 8);
3401        int srcPos = privateCreatorIndex + 1;
3402        int start = srcPos;
3403        while (start < size && tags[start] < privateTag)
3404            start++;
3405
3406        int end = start;
3407        while (end < size && (tags[end] & 0xffffff00) == privateTag)
3408            end++;
3409
3410        int len1 = start - srcPos;
3411        if (len1 > 0) {
3412            System.arraycopy(tags, srcPos, tags, privateCreatorIndex, len1);
3413            System.arraycopy(vrs, srcPos, vrs, privateCreatorIndex, len1);
3414            System.arraycopy(values, srcPos, values, privateCreatorIndex, len1);
3415        }
3416
3417        int len2 = size - end;
3418        if (len2 > 0) {
3419            int destPos = start - 1;
3420            System.arraycopy(tags, end, tags, destPos, len2);
3421            System.arraycopy(vrs, end, vrs, destPos, len2);
3422            System.arraycopy(values, end, values, destPos, len2);
3423        }
3424        int removed = end - start;
3425        int size1 = size - removed - 1;
3426        Arrays.fill(tags, size1, size, 0);
3427        Arrays.fill(vrs, size1, size, null);
3428        Arrays.fill(values, size1, size, null);
3429        size = size1;
3430        return removed;
3431    }
3432
3433    public int removePrivateAttributes() {
3434        int size1 = size;
3435        for (int i = 0; i < size1; i++) {
3436            int j = i;
3437            while (TagUtils.isPrivateGroup(tags[j]) && j < size1)
3438                j++;
3439            if (j > i) {
3440                int len = size1 - j;
3441                if (len > 0) {
3442                    System.arraycopy(tags, j, tags, i, len);
3443                    System.arraycopy(vrs, j, vrs, i, len);
3444                    System.arraycopy(values, j, values, i, len);
3445                }
3446                size1 -= j - i;
3447            }
3448        }
3449        int removed = size - size1;
3450        if (removed > 0) {
3451            Arrays.fill(tags, size1, size, 0);
3452            Arrays.fill(vrs, size1, size, null);
3453            Arrays.fill(values, size1, size, null);
3454            size = size1;
3455        }
3456        return removed;
3457    }
3458}