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) 2012
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 java.util.ArrayList;
042import java.util.List;
043
044import org.dcm4che3.data.IOD.DataElement;
045import org.dcm4che3.util.ByteUtils;
046import org.dcm4che3.util.StringUtils;
047import org.dcm4che3.util.TagUtils;
048
049/**
050 * @author Gunter Zeilinger <gunterze@gmail.com>
051 *
052 */
053public class ValidationResult {
054
055    public enum Invalid {
056        VR,
057        VM,
058        Value,
059        Item,
060        MultipleItems,
061        Code
062    }
063
064    public class InvalidAttributeValue {
065        public final IOD.DataElement dataElement;
066        public final Invalid reason;
067        public final ValidationResult[] itemValidationResults;
068        public final IOD[] missingItems;
069        public InvalidAttributeValue(DataElement dataElement, Invalid reason,
070                ValidationResult[] itemValidationResults, IOD[] missingItems) {
071            this.dataElement = dataElement;
072            this.reason = reason;
073            this.itemValidationResults = itemValidationResults;
074            this.missingItems = missingItems;
075        }
076    }
077
078    private ArrayList<IOD.DataElement> missingAttributes;
079    private ArrayList<IOD.DataElement> missingAttributeValues;
080    private ArrayList<IOD.DataElement> notAllowedAttributes;
081    private ArrayList<InvalidAttributeValue> invalidAttributeValues;
082
083    public boolean hasMissingAttributes() {
084        return missingAttributes != null;
085    }
086
087    public boolean hasMissingAttributeValues() {
088        return missingAttributeValues != null;
089    }
090
091    public boolean hasInvalidAttributeValues() {
092        return invalidAttributeValues != null;
093    }
094
095    public boolean hasNotAllowedAttributes() {
096        return notAllowedAttributes != null;
097    }
098
099    public boolean isValid() {
100        return !hasMissingAttributes()
101            && !hasMissingAttributeValues()
102            && !hasInvalidAttributeValues()
103            && !hasNotAllowedAttributes();
104    }
105
106    public void addMissingAttribute(IOD.DataElement dataElement) {
107        if (missingAttributes == null)
108            missingAttributes = new ArrayList<IOD.DataElement>();
109        missingAttributes.add(dataElement);
110    }
111
112    public void addMissingAttributeValue(IOD.DataElement dataElement) {
113        if (missingAttributeValues == null)
114            missingAttributeValues = new ArrayList<IOD.DataElement>();
115        missingAttributeValues.add(dataElement);
116    }
117
118    public void addInvalidAttributeValue(IOD.DataElement dataElement, Invalid reason) {
119        addInvalidAttributeValue(dataElement, reason, null, null);
120    }
121
122    public void addInvalidAttributeValue(IOD.DataElement dataElement,
123            Invalid reason, ValidationResult[] itemValidationResult, IOD[] missingItems) {
124        if (invalidAttributeValues == null)
125            invalidAttributeValues = new ArrayList<InvalidAttributeValue>();
126        invalidAttributeValues.add(
127                new InvalidAttributeValue(dataElement, reason, 
128                        itemValidationResult, missingItems));
129    }
130
131    public void addNotAllowedAttribute(DataElement el) {
132        if (notAllowedAttributes == null)
133            notAllowedAttributes = new ArrayList<IOD.DataElement>();
134        notAllowedAttributes.add(el);
135    }
136
137    public int[] tagsOfNotAllowedAttributes() {
138        return tagsOf(notAllowedAttributes);
139    }
140
141    public int[] tagsOfMissingAttributeValues() {
142        return tagsOf(missingAttributeValues);
143    }
144
145    public int[] tagsOfMissingAttributes() {
146        return tagsOf(missingAttributes);
147    }
148
149    public int[] tagsOfInvalidAttributeValues() {
150        ArrayList<InvalidAttributeValue> list = invalidAttributeValues;
151        if (list == null)
152            return ByteUtils.EMPTY_INTS;
153
154        int[] tags = new int[list.size()];
155        for (int i = 0; i < tags.length; i++)
156            tags[i] = list.get(i).dataElement.tag;
157        return tags;
158    }
159
160    public int[] getOffendingElements() {
161        return cat(tagsOfMissingAttributes(),
162                tagsOfMissingAttributeValues(),
163                tagsOfInvalidAttributeValues(),
164                tagsOfNotAllowedAttributes());
165    }
166
167    private int[] cat(int[]... iss) {
168        int length = 0;
169        for (int[] is : iss)
170            length += is.length;
171        int[] tags = new int[length];
172        int off = 0;
173        for (int[] is : iss) {
174            System.arraycopy(is, 0, tags, off, is.length);
175            off += is.length;
176        }
177        return tags;
178    }
179
180    private int[] tagsOf(List<DataElement> list) {
181        if (list == null)
182            return ByteUtils.EMPTY_INTS;
183
184        int[] tags = new int[list.size()];
185        for (int i = 0; i < tags.length; i++)
186            tags[i] = list.get(i).tag;
187        return tags;
188    }
189
190    public String getErrorComment() {
191        StringBuilder sb = new StringBuilder();
192        if (notAllowedAttributes != null)
193            return errorComment(sb, "Not allowed Attribute",
194                    tagsOfNotAllowedAttributes()).toString();
195        if (missingAttributes != null)
196            return errorComment(sb, "Missing Attribute",
197                    tagsOfMissingAttributes()).toString();
198        if (missingAttributeValues != null)
199            return errorComment(sb, "Missing Value of Attribute",
200                    tagsOfMissingAttributeValues()).toString();
201        if (invalidAttributeValues != null)
202            return errorComment(sb, "Invalid Attribute",
203                    tagsOfInvalidAttributeValues()).toString();
204        return null;
205    }
206
207    private static StringBuilder errorComment(StringBuilder sb, String prompt,
208            int[] tags) {
209        sb.append(prompt);
210        String prefix = tags.length > 1 ? "s: " : ": ";
211        for (int tag : tags) {
212            sb.append(prefix).append(TagUtils.toString(tag));
213            prefix = ", ";
214        }
215        return sb;
216    }
217
218    @Override
219    public String toString() {
220        if (isValid())
221            return "VALID";
222
223        StringBuilder sb = new StringBuilder();
224        if (notAllowedAttributes != null)
225            errorComment(sb, "Not allowed Attribute",
226                    tagsOfNotAllowedAttributes()).append(StringUtils.LINE_SEPARATOR);
227        if (missingAttributes != null)
228            errorComment(sb, "Missing Attribute",
229                    tagsOfMissingAttributes()).append(StringUtils.LINE_SEPARATOR);
230        if (missingAttributeValues != null)
231            errorComment(sb, "Missing Value of Attribute",
232                    tagsOfMissingAttributeValues()).append(StringUtils.LINE_SEPARATOR);
233        if (invalidAttributeValues != null)
234            errorComment(sb, "Invalid Attribute",
235                    tagsOfInvalidAttributeValues()).append(StringUtils.LINE_SEPARATOR);
236
237        return sb.substring(0, sb.length()-1);
238    }
239
240    public String asText(Attributes attrs) {
241        if (isValid())
242            return "VALID";
243
244        StringBuilder sb = new StringBuilder();
245        appendTextTo(0, attrs, sb);
246        return sb.substring(0, sb.length()-1);
247    }
248
249    private void appendTextTo(int level, Attributes attrs, StringBuilder sb) {
250        if (notAllowedAttributes != null)
251            appendTextTo(level, attrs, "Not allowed Attributes:", notAllowedAttributes, sb);
252        if (missingAttributes != null)
253            appendTextTo(level, attrs, "Missing Attributes:", missingAttributes, sb);
254        if (missingAttributeValues != null)
255            appendTextTo(level, attrs, "Missing Attribute Values:", missingAttributeValues, sb);
256        if (invalidAttributeValues != null)
257            appendInvalidAttributeValues(level, attrs, "Invalid Attribute Values:", sb);
258    }
259
260    private void appendTextTo(int level, Attributes attrs, String title, 
261            List<DataElement> list, StringBuilder sb) {
262        appendPrefixTo(level, sb);
263        sb.append(title).append(StringUtils.LINE_SEPARATOR);
264        for (DataElement el : list) {
265            appendAttribute(level, el.tag, sb);
266            appendIODRef(el.getLineNumber(), sb);
267            sb.append(StringUtils.LINE_SEPARATOR);
268        }
269    }
270
271    private void appendIODRef(int lineNumber, StringBuilder sb) {
272        if (lineNumber > 0)
273            sb.append(" // IOD line #").append(lineNumber);
274    }
275
276    private void appendInvalidAttributeValues(int level, Attributes attrs,
277            String title, StringBuilder sb) {
278        appendPrefixTo(level, sb);
279        sb.append(title);
280        sb.append(StringUtils.LINE_SEPARATOR);
281        for (InvalidAttributeValue iav : invalidAttributeValues) {
282            int tag = iav.dataElement.tag;
283            appendAttribute(level, tag, sb);
284            VR.Holder vr = new VR.Holder();
285            Object value = attrs.getValue(tag, vr);
286            sb.append(' ').append(vr.vr);
287            sb.append(" [");
288            vr.vr.prompt(value,
289                    attrs.bigEndian(), 
290                    attrs.getSpecificCharacterSet(vr.vr), 200, sb);
291            sb.append(']');
292            if (iav.reason != Invalid.Item) {
293                sb.append(" Invalid ").append(iav.reason);
294                appendIODRef(iav.dataElement.getLineNumber(), sb);
295            }
296            sb.append(StringUtils.LINE_SEPARATOR);
297            if (iav.missingItems != null) {
298                for (IOD iod : iav.missingItems) {
299                    appendPrefixTo(level+1, sb);
300                    sb.append("Missing Item");
301                    appendIODRef(iod.getLineNumber(), sb);
302                    sb.append(StringUtils.LINE_SEPARATOR);
303                }
304            }
305            if (iav.itemValidationResults != null) {
306                Sequence seq = (Sequence) value;
307                for (int i = 0; i < iav.itemValidationResults.length; i++) {
308                    ValidationResult itemResult = iav.itemValidationResults[i];
309                    if (!itemResult.isValid()) {
310                        appendPrefixTo(level+1, sb);
311                        sb.append("Invalid Item ").append(i+1).append(':')
312                          .append(StringUtils.LINE_SEPARATOR);
313                        itemResult.appendTextTo(level+1, seq.get(i), sb);
314                    }
315                }
316            }
317        }
318    }
319
320    private void appendAttribute(int level, int tag, StringBuilder sb) {
321        appendPrefixTo(level, sb);
322        sb.append(TagUtils.toString(tag))
323          .append(' ')
324          .append(ElementDictionary.keywordOf(tag, null));
325    }
326
327    private void appendPrefixTo(int level, StringBuilder sb) {
328        while (level-- > 0)
329            sb.append('>');
330    }
331
332}