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.media;
040
041import java.io.IOException;
042import java.util.Arrays;
043import java.util.EnumMap;
044import java.util.EnumSet;
045import java.util.HashMap;
046
047import javax.xml.parsers.ParserConfigurationException;
048import javax.xml.parsers.SAXParser;
049import javax.xml.parsers.SAXParserFactory;
050
051import org.dcm4che3.data.Tag;
052import org.dcm4che3.data.Attributes;
053import org.dcm4che3.data.Sequence;
054import org.dcm4che3.data.VR;
055import org.dcm4che3.io.ContentHandlerAdapter;
056import org.dcm4che3.util.ResourceLocator;
057import org.xml.sax.SAXException;
058
059/**
060 * @author Gunter Zeilinger <gunterze@gmail.com>
061 */
062public class RecordFactory {
063
064    private static final int IN_USE = 0xffff;
065
066    private EnumMap<RecordType, int[]> recordKeys;
067
068    private HashMap<String, RecordType> recordTypes;
069
070    private HashMap<String, String> privateRecordUIDs;
071
072    private HashMap<String, int[]> privateRecordKeys;
073
074    private void lazyLoadDefaultConfiguration() {
075        if (recordTypes == null)
076            loadDefaultConfiguration();
077    }
078
079    public void loadDefaultConfiguration() {
080        try {
081            loadConfiguration(ResourceLocator.getResource(
082                    "org/dcm4che3/media/RecordFactory.xml", this.getClass()));
083        } catch (Exception e) {
084            throw new RuntimeException(e);
085        }
086    }
087
088    public void loadConfiguration(String uri)
089            throws ParserConfigurationException, SAXException, IOException {
090        Attributes attrs = parseXML(uri);
091        Sequence sq = attrs.getSequence(Tag.DirectoryRecordSequence);
092        if (sq == null)
093            throw new IllegalArgumentException(
094                    "Missing Directory Record Sequence in " + uri);
095
096        EnumMap<RecordType, int[]> recordKeys = new EnumMap<RecordType, int[]>(
097                RecordType.class);
098        HashMap<String, RecordType> recordTypes = new HashMap<String, RecordType>(
099                134);
100        HashMap<String, String> privateRecordUIDs = new HashMap<String, String>();
101        HashMap<String, int[]> privateRecordKeys = new HashMap<String, int[]>();
102        for (Attributes item : sq) {
103            RecordType type = RecordType.forCode(item.getString(
104                    Tag.DirectoryRecordType, null));
105            String privuid = type == RecordType.PRIVATE ? item.getString(
106                    Tag.PrivateRecordUID, null) : null;
107            String[] cuids = item.getStrings(Tag.ReferencedSOPClassUIDInFile);
108            if (cuids != null) {
109                if (type != RecordType.PRIVATE) {
110                    for (String cuid : cuids) {
111                        recordTypes.put(cuid, type);
112                    }
113                } else if (privuid != null) {
114                    for (String cuid : cuids) {
115                        privateRecordUIDs.put(cuid, privuid);
116                    }
117                }
118            }
119            item.remove(Tag.DirectoryRecordType);
120            item.remove(Tag.PrivateRecordUID);
121            item.remove(Tag.ReferencedSOPClassUIDInFile);
122            int[] keys = item.tags();
123            if (privuid != null) {
124                if (privateRecordKeys.put(privuid, keys) != null)
125                    throw new IllegalArgumentException(
126                            "Duplicate Private Record UID: " + privuid);
127            } else {
128                if (recordKeys.put(type, keys) != null)
129                    throw new IllegalArgumentException(
130                            "Duplicate Record Type: " + type);
131            }
132        }
133        EnumSet<RecordType> missingTypes = EnumSet.allOf(RecordType.class);
134        missingTypes.removeAll(recordKeys.keySet());
135        if (!missingTypes.isEmpty())
136            throw new IllegalArgumentException("Missing Record Types: "
137                    + missingTypes);
138        this.recordTypes = recordTypes;
139        this.recordKeys = recordKeys;
140        this.privateRecordUIDs = privateRecordUIDs;
141        this.privateRecordKeys = privateRecordKeys;
142    }
143
144    private Attributes parseXML(String uri)
145            throws ParserConfigurationException, SAXException, IOException {
146        Attributes attrs = new Attributes();
147        SAXParserFactory f = SAXParserFactory.newInstance();
148        SAXParser parser = f.newSAXParser();
149        parser.parse(uri, new ContentHandlerAdapter(attrs));
150        return attrs;
151    }
152
153    public RecordType getRecordType(String cuid) {
154        if (cuid == null)
155            throw new NullPointerException();
156        lazyLoadDefaultConfiguration();
157        RecordType recordType = recordTypes.get(cuid);
158        return recordType != null ? recordType : RecordType.PRIVATE;
159    }
160
161    public RecordType setRecordType(String cuid, RecordType type) {
162        if (cuid == null || type == null)
163            throw new NullPointerException();
164        lazyLoadDefaultConfiguration();
165        return recordTypes.put(cuid, type);
166    }
167
168    public void setRecordKeys(RecordType type, int[] keys) {
169        if (type == null)
170            throw new NullPointerException();
171        int[] tmp = keys.clone();
172        Arrays.sort(tmp);
173        lazyLoadDefaultConfiguration();
174        recordKeys.put(type, keys);
175    }
176
177    public String getPrivateRecordUID(String cuid) {
178        if (cuid == null)
179            throw new NullPointerException();
180
181        lazyLoadDefaultConfiguration();
182        String uid = privateRecordUIDs.get(cuid);
183        return uid != null ? uid : cuid;
184    }
185
186    public String setPrivateRecordUID(String cuid, String uid) {
187        if (cuid == null || uid == null)
188            throw new NullPointerException();
189
190        lazyLoadDefaultConfiguration();
191        return privateRecordUIDs.put(cuid, uid);
192    }
193
194    public int[] setPrivateRecordKeys(String uid, int[] keys) {
195        if (uid == null)
196            throw new NullPointerException();
197
198        int[] tmp = keys.clone();
199        Arrays.sort(tmp);
200        lazyLoadDefaultConfiguration();
201        return privateRecordKeys.put(uid, tmp);
202    }
203
204    public Attributes createRecord(Attributes dataset, Attributes fmi,
205            String[] fileIDs) {
206        String cuid = fmi.getString(Tag.MediaStorageSOPClassUID, null);
207        RecordType type = getRecordType(cuid);
208        return createRecord(type,
209                type == RecordType.PRIVATE ? getPrivateRecordUID(cuid) : null,
210                dataset, fmi, fileIDs);
211    }
212
213    public Attributes createRecord(RecordType type, String privRecUID,
214            Attributes dataset, Attributes fmi, String[] fileIDs) {
215        if (type == null)
216            throw new NullPointerException("type");
217        if (dataset == null)
218            throw new NullPointerException("dataset");
219
220        lazyLoadDefaultConfiguration();
221        int[] keys = null;
222        if (type == RecordType.PRIVATE) {
223            if (privRecUID == null)
224                throw new NullPointerException(
225                        "privRecUID must not be null for type = PRIVATE");
226            keys = privateRecordKeys.get(privRecUID);
227        } else {
228            if (privRecUID != null)
229                throw new IllegalArgumentException(
230                        "privRecUID must be null for type != PRIVATE");
231        }
232        if (keys == null)
233            keys = recordKeys.get(type);
234        Attributes rec = new Attributes(keys.length + (fileIDs != null ? 9 : 5));
235        rec.setInt(Tag.OffsetOfTheNextDirectoryRecord, VR.UL, 0);
236        rec.setInt(Tag.RecordInUseFlag, VR.US, IN_USE);
237        rec.setInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, VR.UL, 0);
238        rec.setString(Tag.DirectoryRecordType, VR.CS, type.code());
239        if (privRecUID != null)
240            rec.setString(Tag.PrivateRecordUID, VR.UI, privRecUID);
241        if (fileIDs != null) {
242            rec.setString(Tag.ReferencedFileID, VR.CS, fileIDs);
243            rec.setString(Tag.ReferencedSOPClassUIDInFile, VR.UI,
244                    fmi.getString(Tag.MediaStorageSOPClassUID, null));
245            rec.setString(Tag.ReferencedSOPInstanceUIDInFile, VR.UI,
246                    fmi.getString(Tag.MediaStorageSOPInstanceUID, null));
247            rec.setString(Tag.ReferencedTransferSyntaxUIDInFile, VR.UI,
248                    fmi.getString(Tag.TransferSyntaxUID, null));
249        }
250        rec.addSelected(dataset, keys, 0, keys.length);
251        Sequence contentSeq = dataset.getSequence(Tag.ContentSequence);
252        if (contentSeq != null)
253            copyConceptMod(contentSeq, rec);
254        return rec;
255    }
256
257    private void copyConceptMod(Sequence srcSeq, Attributes rec) {
258        Sequence dstSeq = null;
259        for (Attributes item : srcSeq) {
260            if ("HAS CONCEPT MOD".equals(item.getString(Tag.RelationshipType,
261                    null))) {
262                if (dstSeq == null)
263                    dstSeq = rec.newSequence(Tag.ContentSequence, 1);
264                dstSeq.add(new Attributes(item, false));
265            }
266        }
267    }
268}