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.Closeable; 042import java.io.File; 043import java.io.IOException; 044import java.io.RandomAccessFile; 045 046import org.dcm4che3.data.Tag; 047import org.dcm4che3.data.Attributes; 048import org.dcm4che3.data.VR; 049import org.dcm4che3.io.DicomInputStream; 050import org.dcm4che3.io.RAFInputStreamAdapter; 051import org.dcm4che3.util.IntHashMap; 052import org.dcm4che3.util.SafeClose; 053import org.dcm4che3.util.StringUtils; 054 055/** 056 * @author Gunter Zeilinger <gunterze@gmail.com> 057 */ 058public class DicomDirReader implements Closeable { 059 060 protected final File file; 061 protected final RandomAccessFile raf; 062 protected final DicomInputStream in; 063 protected final Attributes fmi; 064 protected final Attributes fsInfo; 065 protected final IntHashMap<Attributes> cache = new IntHashMap<Attributes>(); 066 067 public DicomDirReader(File file) throws IOException { 068 this(file, "r"); 069 } 070 071 protected DicomDirReader(File file, String mode) throws IOException { 072 this.file = file; 073 this.raf = new RandomAccessFile(file, mode); 074 try { 075 this.in = new DicomInputStream(new RAFInputStreamAdapter(raf)); 076 this.fmi = in.readFileMetaInformation(); 077 this.fsInfo = in.readDataset(-1, Tag.DirectoryRecordSequence); 078 if (in.tag() != Tag.DirectoryRecordSequence) 079 throw new IOException("Missing Directory Record Sequence"); 080 } catch (IOException e) { 081 SafeClose.close(raf); 082 throw e; 083 } 084 } 085 086 public final File getFile() { 087 return file; 088 } 089 090 public final Attributes getFileMetaInformation() { 091 return fmi; 092 } 093 094 public final Attributes getFileSetInformation() { 095 return fsInfo; 096 } 097 098 public void close() throws IOException { 099 raf.close(); 100 } 101 102 public String getFileSetUID() { 103 return fmi.getString(Tag.MediaStorageSOPInstanceUID, null); 104 } 105 106 public String getTransferSyntaxUID() { 107 return fmi.getString(Tag.TransferSyntaxUID, null); 108 } 109 110 public String getFileSetID() { 111 return fsInfo.getString(Tag.FileSetID, null); 112 } 113 114 public File getDescriptorFile() { 115 return toFile(fsInfo.getStrings(Tag.FileSetDescriptorFileID)); 116 } 117 118 public File toFile(String[] fileIDs) { 119 if (fileIDs == null || fileIDs.length == 0) 120 return null; 121 122 return new File(file.getParent(), 123 StringUtils.concat(fileIDs, File.separatorChar)); 124 } 125 126 public String getDescriptorFileCharacterSet() { 127 return fsInfo.getString( 128 Tag.SpecificCharacterSetOfFileSetDescriptorFile, null); 129 } 130 131 public int getFileSetConsistencyFlag() { 132 return fsInfo.getInt(Tag.FileSetConsistencyFlag, 0); 133 } 134 135 protected void setFileSetConsistencyFlag(int i) { 136 fsInfo.setInt(Tag.FileSetConsistencyFlag, VR.US, i); 137 } 138 139 public boolean knownInconsistencies() { 140 return getFileSetConsistencyFlag() != 0; 141 } 142 143 public int getOffsetOfFirstRootDirectoryRecord() { 144 return fsInfo.getInt( 145 Tag.OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity, 0); 146 } 147 148 protected void setOffsetOfFirstRootDirectoryRecord(int i) { 149 fsInfo.setInt( 150 Tag.OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity, 151 VR.UL, i); 152 } 153 154 public int getOffsetOfLastRootDirectoryRecord() { 155 return fsInfo.getInt( 156 Tag.OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity, 0); 157 } 158 159 protected void setOffsetOfLastRootDirectoryRecord(int i) { 160 fsInfo.setInt( 161 Tag.OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity, 162 VR.UL, i); 163 } 164 165 public boolean isEmpty() { 166 return getOffsetOfFirstRootDirectoryRecord() == 0; 167 } 168 169 public void clearCache() { 170 cache.clear(); 171 } 172 173 public Attributes readFirstRootDirectoryRecord() throws IOException { 174 return readRecord(getOffsetOfFirstRootDirectoryRecord()); 175 } 176 177 public Attributes readLastRootDirectoryRecord() throws IOException { 178 return readRecord(getOffsetOfLastRootDirectoryRecord()); 179 } 180 181 public Attributes readNextDirectoryRecord(Attributes rec) 182 throws IOException { 183 return readRecord( 184 rec.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0)); 185 } 186 187 public Attributes readLowerDirectoryRecord(Attributes rec) 188 throws IOException { 189 return readRecord( 190 rec.getInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, 0)); 191 } 192 193 protected Attributes findLastLowerDirectoryRecord(Attributes rec) 194 throws IOException { 195 Attributes lower = readLowerDirectoryRecord(rec); 196 if (lower == null) 197 return null; 198 199 Attributes next; 200 while ((next = readNextDirectoryRecord(lower)) != null) 201 lower = next; 202 return lower; 203 } 204 205 public Attributes findFirstRootDirectoryRecordInUse(boolean ignorePrivate) throws IOException { 206 return findRootDirectoryRecord(ignorePrivate, null, false, false); 207 } 208 209 public Attributes findRootDirectoryRecord(Attributes keys, boolean ignorePrivate, 210 boolean ignoreCaseOfPN, boolean matchNoValue) 211 throws IOException { 212 return findRecordInUse(getOffsetOfFirstRootDirectoryRecord(), ignorePrivate, 213 keys, ignoreCaseOfPN, matchNoValue); 214 } 215 216 public Attributes findRootDirectoryRecord(boolean ignorePrivate, Attributes keys, 217 boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { 218 return findRootDirectoryRecord(keys, ignorePrivate, ignoreCaseOfPN, matchNoValue); 219 } 220 221 public Attributes findNextDirectoryRecordInUse(Attributes rec, boolean ignorePrivate) 222 throws IOException { 223 return findNextDirectoryRecord(rec, ignorePrivate, null, false, false); 224 } 225 226 public Attributes findNextDirectoryRecord(Attributes rec, boolean ignorePrivate, 227 Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) throws IOException { 228 return findRecordInUse( 229 rec.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0), ignorePrivate, 230 keys, ignoreCaseOfPN, matchNoValue); 231 } 232 233 public Attributes findLowerDirectoryRecordInUse(Attributes rec, boolean ignorePrivate) 234 throws IOException { 235 return findLowerDirectoryRecord(rec, ignorePrivate, null, false, false); 236 } 237 238 public Attributes findLowerDirectoryRecord(Attributes rec, boolean ignorePrivate, 239 Attributes keys, boolean ignoreCaseOfPN, boolean matchNoValue) 240 throws IOException { 241 return findRecordInUse( 242 rec.getInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, 0), ignorePrivate, 243 keys, ignoreCaseOfPN, matchNoValue); 244 } 245 246 public Attributes findPatientRecord(String... ids) throws IOException { 247 return findRootDirectoryRecord(false, 248 pk("PATIENT", Tag.PatientID, VR.LO, ids), false, false); 249 } 250 251 public Attributes findNextPatientRecord(Attributes patRec, String... ids) throws IOException { 252 return findNextDirectoryRecord(patRec, false, 253 pk("PATIENT", Tag.PatientID, VR.LO, ids), false, false); 254 } 255 256 public Attributes findStudyRecord(Attributes patRec, String... iuids) 257 throws IOException { 258 return findLowerDirectoryRecord(patRec, false, 259 pk("STUDY", Tag.StudyInstanceUID, VR.UI, iuids), 260 false, false); 261 } 262 263 public Attributes findNextStudyRecord(Attributes studyRec, String... iuids) 264 throws IOException { 265 return findNextDirectoryRecord(studyRec, false, 266 pk("STUDY", Tag.StudyInstanceUID, VR.UI, iuids), 267 false, false); 268 } 269 270 public Attributes findSeriesRecord(Attributes studyRec, String... iuids) 271 throws IOException { 272 return findLowerDirectoryRecord(studyRec, false, 273 pk("SERIES", Tag.SeriesInstanceUID, VR.UI, iuids), 274 false, false); 275 } 276 277 public Attributes findNextSeriesRecord(Attributes seriesRec, String... iuids) 278 throws IOException { 279 return findNextDirectoryRecord(seriesRec, false, 280 pk("SERIES", Tag.SeriesInstanceUID, VR.UI, iuids), 281 false, false); 282 } 283 284 public Attributes findLowerInstanceRecord(Attributes seriesRec, boolean ignorePrivate, 285 String... iuids) throws IOException { 286 return findLowerDirectoryRecord(seriesRec, ignorePrivate, pk(iuids), false, false); 287 } 288 289 public Attributes findNextInstanceRecord(Attributes instRec, boolean ignorePrivate, 290 String... iuids) throws IOException { 291 return findNextDirectoryRecord(instRec, ignorePrivate, pk(iuids), false, false); 292 } 293 294 public Attributes findRootInstanceRecord(boolean ignorePrivate, String... iuids) 295 throws IOException { 296 return findRootDirectoryRecord(ignorePrivate, pk(iuids), false, false); 297 } 298 299 private Attributes pk(String type, int tag, VR vr, String... ids) { 300 Attributes pk = new Attributes(2); 301 pk.setString(Tag.DirectoryRecordType, VR.CS, type); 302 if (ids != null && ids.length != 0) 303 pk.setString(tag, vr, ids); 304 return pk; 305 } 306 307 private Attributes pk(String... iuids) { 308 if (iuids == null || iuids.length == 0) 309 return null; 310 311 Attributes pk = new Attributes(1); 312 pk.setString(Tag.ReferencedSOPInstanceUIDInFile, VR.UI, iuids); 313 return pk; 314 } 315 316 private Attributes findRecordInUse(int offset, boolean ignorePrivate, Attributes keys, 317 boolean ignoreCaseOfPN, boolean matchNoValue) 318 throws IOException { 319 while (offset != 0) { 320 Attributes item = readRecord(offset); 321 if (inUse(item) && !(ignorePrivate && isPrivate(item)) 322 && (keys == null || item.matches(keys, ignoreCaseOfPN, matchNoValue))) 323 return item; 324 offset = item.getInt(Tag.OffsetOfTheNextDirectoryRecord, 0); 325 } 326 return null; 327 } 328 329 private synchronized Attributes readRecord(int offset) throws IOException { 330 if (offset == 0) 331 return null; 332 333 Attributes item = cache.get(offset); 334 if (item == null) { 335 long off = offset & 0xffffffffL; 336 raf.seek(off); 337 in.setPosition(off); 338 item = in.readItem(); 339 cache.put(offset, item); 340 } 341 return item; 342 } 343 344 public static boolean inUse(Attributes rec) { 345 return rec.getInt(Tag.RecordInUseFlag, 0) != 0; 346 } 347 348 public static boolean isPrivate(Attributes rec) { 349 return "PRIVATE".equals(rec.getString(Tag.DirectoryRecordType)); 350 } 351 352}