/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4che3.media; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import org.dcm4che3.data.Tag; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.VR; import org.dcm4che3.io.DicomInputStream; import org.dcm4che3.io.RAFInputStreamAdapter; import org.dcm4che3.util.IntHashMap; import org.dcm4che3.util.SafeClose; import org.dcm4che3.util.StringUtils; /** * @author Gunter Zeilinger <gunterze@gmail.com> */ public class DicomDirReader implements Closeable { protected final File file; protected final RandomAccessFile raf; protected final DicomInputStream in; protected final Attributes fmi; protected final Attributes fsInfo; protected final IntHashMap<Attributes> cache = new IntHashMap<Attributes>(); public DicomDirReader(File file) throws IOException { this(file, "r"); } protected DicomDirReader(File file, String mode) throws IOException { this.file = file; this.raf = new RandomAccessFile(file, mode); try { this.in = new DicomInputStream(new RAFInputStreamAdapter(raf)); this.fmi = in.readFileMetaInformation(); this.fsInfo = in.readDataset(-1, Tag.DirectoryRecordSequence); if (in.tag() != Tag.DirectoryRecordSequence) throw new IOException("Missing Directory Record Sequence"); } catch (IOException e) { SafeClose.close(raf); throw e; } } public final File getFile() { return file; } public final Attributes getFileMetaInformation() { return fmi; } public final Attributes getFileSetInformation() { return fsInfo; } public void close() throws IOException { raf.close(); } public String getFileSetUID() { return fmi.getString(Tag.MediaStorageSOPInstanceUID, null); } public String getTransferSyntaxUID() { return fmi.getString(Tag.TransferSyntaxUID, null); } public String getFileSetID() { return fsInfo.getString(Tag.FileSetID, null); } public File getDescriptorFile() { return toFile(fsInfo.getStrings(Tag.FileSetDescriptorFileID)); } public File toFile(String[] fileIDs) { if (fileIDs == null || fileIDs.length == 0) return null; return new File(file.getParent(), StringUtils.concat(fileIDs, File.separatorChar)); } public String getDescriptorFileCharacterSet() { return fsInfo.getString( Tag.SpecificCharacterSetOfFileSetDescriptorFile, null); } public int getFileSetConsistencyFlag() { return fsInfo.getInt(Tag.FileSetConsistencyFlag, 0); } protected void setFileSetConsistencyFlag(int i) { fsInfo.setInt(Tag.FileSetConsistencyFlag, VR.US, i); } public boolean knownInconsistencies() { return getFileSetConsistencyFlag() != 0; } public int getOffsetOfFirstRootDirectoryRecord() { return fsInfo.getInt( Tag.OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity, 0); } protected void setOffsetOfFirstRootDirectoryRecord(int i) { fsInfo.setInt( Tag.OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity, VR.UL, i); } public int getOffsetOfLastRootDirectoryRecord() { return fsInfo.getInt( Tag.OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity, 0); } protected void setOffsetOfLastRootDirectoryRecord(int i) { fsInfo.setInt( Tag.OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity, VR.UL, i); } public boolean isEmpty() { return getOffsetOfFirstRootDirectoryRecord() == 0; } public void clearCache() { cache.clear(); } public Attributes readFirstRootDirectoryRecord() throws IOException { return readRecord(getOffsetOfFirstRootDirectoryRecord()); } public Attributes readLastRootDirectoryRecord() throws IOException { return readRecord(getOffsetOfLastRootDirectoryRecord()); } public Attributes readNextDirectoryRecord(Attributes rec) throws IOException { return readRecord( rec.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0)); } public Attributes readLowerDirectoryRecord(Attributes rec) throws IOException { return readRecord( rec.getInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, 0)); } protected Attributes findLastLowerDirectoryRecord(Attributes rec) throws IOException { Attributes lower = readLowerDirectoryRecord(rec); if (lower == null) return null; Attributes next; while ((next = readNextDirectoryRecord(lower)) != null) lower = next; return lower; } public Attributes findFirstRootDirectoryRecordInUse(boolean ignorePrivate) throws IOException { return findRootDirectoryRecord(ignorePrivate, null, false, false); } public Attributes findRootDirectoryRecord(Attributes keys, boolean ignorePrivate, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { return findRecordInUse(getOffsetOfFirstRootDirectoryRecord(), ignorePrivate, keys, ignoreCaseOfPN, matchNoValue); } public Attributes findRootDirectoryRecord(boolean ignorePrivate, Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { return findRootDirectoryRecord(keys, ignorePrivate, ignoreCaseOfPN, matchNoValue); } public Attributes findNextDirectoryRecordInUse(Attributes rec, boolean ignorePrivate) throws IOException { return findNextDirectoryRecord(rec, ignorePrivate, null, false, false); } public Attributes findNextDirectoryRecord(Attributes rec, boolean ignorePrivate, Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { return findRecordInUse( rec.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0), ignorePrivate, keys, ignoreCaseOfPN, matchNoValue); } public Attributes findLowerDirectoryRecordInUse(Attributes rec, boolean ignorePrivate) throws IOException { return findLowerDirectoryRecord(rec, ignorePrivate, null, false, false); } public Attributes findLowerDirectoryRecord(Attributes rec, boolean ignorePrivate, Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { return findRecordInUse( rec.getInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, 0), ignorePrivate, keys, ignoreCaseOfPN, matchNoValue); } public Attributes findPatientRecord(String... ids) throws IOException { return findRootDirectoryRecord(false, pk("PATIENT", Tag.PatientID, VR.LO, ids), false, false); } public Attributes findNextPatientRecord(Attributes patRec, String... ids) throws IOException { return findNextDirectoryRecord(patRec, false, pk("PATIENT", Tag.PatientID, VR.LO, ids), false, false); } public Attributes findStudyRecord(Attributes patRec, String... iuids) throws IOException { return findLowerDirectoryRecord(patRec, false, pk("STUDY", Tag.StudyInstanceUID, VR.UI, iuids), false, false); } public Attributes findNextStudyRecord(Attributes studyRec, String... iuids) throws IOException { return findNextDirectoryRecord(studyRec, false, pk("STUDY", Tag.StudyInstanceUID, VR.UI, iuids), false, false); } public Attributes findSeriesRecord(Attributes studyRec, String... iuids) throws IOException { return findLowerDirectoryRecord(studyRec, false, pk("SERIES", Tag.SeriesInstanceUID, VR.UI, iuids), false, false); } public Attributes findNextSeriesRecord(Attributes seriesRec, String... iuids) throws IOException { return findNextDirectoryRecord(seriesRec, false, pk("SERIES", Tag.SeriesInstanceUID, VR.UI, iuids), false, false); } public Attributes findLowerInstanceRecord(Attributes seriesRec, boolean ignorePrivate, String... iuids) throws IOException { return findLowerDirectoryRecord(seriesRec, ignorePrivate, pk(iuids), false, false); } public Attributes findNextInstanceRecord(Attributes instRec, boolean ignorePrivate, String... iuids) throws IOException { return findNextDirectoryRecord(instRec, ignorePrivate, pk(iuids), false, false); } public Attributes findRootInstanceRecord(boolean ignorePrivate, String... iuids) throws IOException { return findRootDirectoryRecord(ignorePrivate, pk(iuids), false, false); } private Attributes pk(String type, int tag, VR vr, String... ids) { Attributes pk = new Attributes(2); pk.setString(Tag.DirectoryRecordType, VR.CS, type); if (ids != null && ids.length != 0) pk.setString(tag, vr, ids); return pk; } private Attributes pk(String... iuids) { if (iuids == null || iuids.length == 0) return null; Attributes pk = new Attributes(1); pk.setString(Tag.ReferencedSOPInstanceUIDInFile, VR.UI, iuids); return pk; } private Attributes findRecordInUse(int offset, boolean ignorePrivate, Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { while (offset != 0) { Attributes item = readRecord(offset); if (inUse(item) && !(ignorePrivate && isPrivate(item)) && (keys == null || item.matches(keys, ignoreCaseOfPN, matchNoValue))) return item; offset = item.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0); } return null; } private synchronized Attributes readRecord(int offset) throws IOException { if (offset == 0) return null; Attributes item = cache.get(offset); if (item == null) { long off = offset & 0xffffffffL; raf.seek(off); in.setPosition(off); item = in.readItem(); cache.put(offset, item); } return item; } public static boolean inUse(Attributes rec) { return rec.getInt(Tag.RecordInUseFlag, 0) != 0; } public static boolean isPrivate(Attributes rec) { return "PRIVATE".equals(rec.getString(Tag.DirectoryRecordType)); } }