Skip to contentPackage: ReadOp$Type$3$1
ReadOp$Type$3$1
Coverage
1: /*
2: * *********************************************************************************************************************
3: *
4: * Mistral: open source imaging engine
5: * http://tidalwave.it/projects/mistral
6: *
7: * Copyright (C) 2003 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *********************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
12: * the License. You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/mistral-src
23: * git clone https://github.com/tidalwave-it/mistral-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.image.op;
28:
29: import javax.annotation.Nonnegative;
30: import javax.annotation.Nonnull;
31: import java.time.Duration;
32: import java.time.Instant;
33: import java.util.ArrayList;
34: import java.util.Arrays;
35: import java.util.HashSet;
36: import java.util.Iterator;
37: import java.util.List;
38: import java.util.Objects;
39: import java.util.Optional;
40: import java.util.Set;
41: import java.util.zip.GZIPInputStream;
42: import java.io.ByteArrayInputStream;
43: import java.io.File;
44: import java.io.FileNotFoundException;
45: import java.io.IOException;
46: import java.io.InputStream;
47: import java.nio.file.Files;
48: import java.nio.file.Path;
49: import javax.imageio.ImageIO;
50: import javax.imageio.ImageReader;
51: import javax.imageio.metadata.IIOMetadata;
52: import javax.imageio.stream.ImageInputStream;
53: import java.net.URL;
54: import java.awt.image.BufferedImage;
55: import it.tidalwave.util.Parameters;
56: import it.tidalwave.image.EditableImage;
57: import it.tidalwave.image.java2d.ImplementationFactoryJ2D;
58: import it.tidalwave.image.java2d.Java2DUtils;
59: import it.tidalwave.image.metadata.Directory;
60: import it.tidalwave.image.metadata.EXIF;
61: import it.tidalwave.image.metadata.IPTC;
62: import it.tidalwave.image.metadata.MakerNote;
63: import it.tidalwave.image.metadata.TIFF;
64: import it.tidalwave.image.metadata.XMP;
65: import it.tidalwave.image.metadata.loader.DirectoryLoader;
66: import it.tidalwave.image.metadata.loader.DrewMetadataLoader;
67: import it.tidalwave.image.metadata.loader.JpegDrewMetadataLoader;
68: import it.tidalwave.image.metadata.loader.MetadataLoader;
69: import it.tidalwave.image.metadata.loader.RAWMetadataLoader;
70: import it.tidalwave.image.metadata.loader.TIFFMetadataLoader;
71: import it.tidalwave.image.op.impl.FileChannelImageInputStream;
72: import lombok.Getter;
73: import lombok.ToString;
74: import lombok.extern.slf4j.Slf4j;
75: import static it.tidalwave.util.FunctionalCheckedExceptionWrappers.*;
76:
77: /***********************************************************************************************************************
78: *
79: * @author Fabrizio Giudici
80: *
81: **********************************************************************************************************************/
82: @ToString(of = {"input", "imageIndex", "thumbnailIndex"}) @Slf4j
83: public class ReadOp extends Operation
84: {
85: /*******************************************************************************************************************
86: *
87: * A marker interface for allowable options for {@link ReadOp} constructor.
88: *
89: ******************************************************************************************************************/
90: public static interface Options
91: {
92: }
93:
94: /*******************************************************************************************************************
95: *
96: * A container of plugin names that should not be used to load an image.
97: *
98: ******************************************************************************************************************/
99: @ToString
100: public static class PluginBlackList implements Options
101: {
102: public static final PluginBlackList DEFAULT = new PluginBlackList(
103: // WRONG! These are the good ones! But keep for compability until you test everything.
104: "com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageReader",
105: "com.sun.media.imageioimpl.plugins.jpeg.CLibJPEGImageWriter",
106: //
107: // Considered harmful on Mac OS X. For instance, can only deal with Mac OS X endianness.
108: //
109: "com.sun.imageio.plugins.tiff.TIFFImageReader"
110: );
111:
112: private final Set<String> plugins;
113:
114: public PluginBlackList (@Nonnull final String... plugins)
115: {
116: this.plugins = new HashSet<>(Arrays.asList(plugins));
117: }
118:
119: public boolean contains (@Nonnull final String pluginName)
120: {
121: return plugins.contains(pluginName);
122: }
123: }
124:
125: @Getter @Nonnull
126: private final Object input;
127:
128: @Getter @Nonnull
129: private final PluginBlackList pluginBlackList;
130:
131: @Getter @Nonnull
132: private final Type type;
133:
134: @Getter @Nonnegative
135: private final int imageIndex;
136:
137: @Getter @Nonnegative
138: private final int thumbnailIndex;
139:
140: /*******************************************************************************************************************
141: *
142: *
143: ******************************************************************************************************************/
144: private abstract static class Reader
145: {
146: public static EditableImage read (@Nonnull final Object input,
147: @Nonnull final Reader reader,
148: @Nonnull final PluginBlackList pluginBlackList)
149: throws IOException
150: {
151: return reader.run(input, pluginBlackList);
152: }
153:
154: @Nonnull
155: private EditableImage run (@Nonnull final Object input,
156: @Nonnull final PluginBlackList pluginBlackList)
157: throws IOException
158: {
159: Objects.requireNonNull(input, "input");
160:
161: if (input instanceof Path)
162: {
163: final var imageReader = createImageReader(((Path)input), pluginBlackList);
164: final var editableImage = read(imageReader);
165: setProperties(editableImage, imageReader);
166: imageReader.dispose();
167: return editableImage;
168: }
169:
170: else if (input instanceof File)
171: {
172: final var imageReader = createImageReader(((File)input).toPath(), pluginBlackList);
173: final var editableImage = read(imageReader);
174: setProperties(editableImage, imageReader);
175: imageReader.dispose();
176: return editableImage;
177: }
178:
179: else if (input instanceof URL)
180: {
181: final var imageReader = createImageReader((URL)input, pluginBlackList);
182: final var editableImage = read(imageReader);
183: setProperties(editableImage, imageReader);
184: imageReader.dispose();
185: return editableImage;
186: }
187:
188: else if (input instanceof InputStream)
189: {
190: final var imageReader = createImageReader((InputStream)input, pluginBlackList);
191: final var editableImage = read(imageReader);
192: setProperties(editableImage, imageReader);
193: imageReader.dispose();
194: return editableImage;
195: }
196:
197: else if (input instanceof byte[])
198: {
199: final var imageReader = createImageReader(new ByteArrayInputStream((byte[])input), pluginBlackList);
200: final var editableImage = read(imageReader);
201: setProperties(editableImage, imageReader);
202: imageReader.dispose();
203: return editableImage;
204: }
205:
206: else if (input instanceof ImageReader)
207: {
208: final var editableImage = read((ImageReader)input);
209: setProperties(editableImage, (ImageReader)input);
210: return editableImage;
211: // don't dispose the ImageReader in this case
212: }
213:
214: else
215: {
216: throw new IllegalArgumentException("Bad input type: " + input.getClass());
217: }
218: }
219:
220: protected abstract EditableImage read (@Nonnull ImageReader imageReader)
221: throws IOException;
222:
223: private void setProperties (@Nonnull final EditableImage image, @Nonnull final ImageReader imageReader)
224: throws IOException
225: {
226: image.setAttribute(EditableImage.PROP_FORMAT, imageReader.getFormatName());
227: image.setAttribute(EditableImage.PROP_MIME_TYPE, imageReader.getOriginatingProvider().getMIMETypes()[0]);
228: }
229: }
230:
231: /*******************************************************************************************************************
232: *
233: *
234: ******************************************************************************************************************/
235: public static enum Type implements Options
236: {
237: /***************************************************************************************************************
238: *
239: *
240: **************************************************************************************************************/
241: IMAGE
242: {
243: @Override @Nonnull
244: protected EditableImage read (@Nonnull final ReadOp readOp)
245: throws IOException
246: {
247: final var input = readOp.getInput();
248: final var imageIndex = readOp.getImageIndex();
249: log.info("read({}, {})", input, imageIndex);
250:
251: return Reader.read(input, new Reader()
252: {
253: @Override
254: protected EditableImage read (final ImageReader imageReader)
255: throws IOException
256: {
257: final var time = Instant.now();
258: final var image = imageReader.read(imageIndex);
259: final var editableImage = create(image);
260: loadMetadata(editableImage, imageReader, imageIndex);
261: Java2DUtils.logImage(log, ">>>> Loaded image: ", image);
262: editableImage.getInnerProperty(AccessorOp.class).setLatestOperationDuration(Duration.between(Instant.now(), time));
263: return editableImage;
264: }
265: }, readOp.getPluginBlackList());
266: }
267: },
268:
269: /***************************************************************************************************************
270: *
271: *
272: **************************************************************************************************************/
273: THUMBNAIL
274: {
275: @Override @Nonnull
276: protected EditableImage read (@Nonnull final ReadOp readOp)
277: throws IOException
278: {
279: final var input = readOp.getInput();
280: final var imageIndex = readOp.getImageIndex();
281: final var thumbnailIndex = readOp.getThumbnailIndex();
282: log.info("read({}, {}, {})", input, imageIndex, thumbnailIndex);
283:
284: return Reader.read(input, new Reader()
285: {
286: @Nonnull
287: @Override
288: protected EditableImage read (@Nonnull final ImageReader imageReader)
289: throws IOException
290: {
291: final var time = Instant.now();
292: return create(imageReader.readThumbnail(imageIndex, thumbnailIndex),
293: Duration.between(Instant.now(), time));
294: }
295: }, readOp.getPluginBlackList());
296: }
297: },
298:
299: /***************************************************************************************************************
300: *
301: *
302: **************************************************************************************************************/
303: METADATA
304: {
305: @Override @Nonnull
306: protected EditableImage read (@Nonnull final ReadOp readOp)
307: throws IOException
308: {
309: final var input = readOp.getInput();
310: final var imageIndex = readOp.getImageIndex();
311: log.info("read({}, {})", input, imageIndex);
312:
313: return Reader.read(input, new Reader()
314: {
315: @Nonnull
316: @Override
317: protected EditableImage read (@Nonnull final ImageReader imageReader)
318: {
319: final var editableImage = new EditableImage(null);
320: loadMetadata(editableImage, imageReader, imageIndex);
321: return editableImage;
322: }
323: }, readOp.getPluginBlackList());
324: }
325: };
326:
327: /***************************************************************************************************************
328: *
329: *
330: **************************************************************************************************************/
331: @Nonnull
332: protected abstract EditableImage read (@Nonnull ReadOp readOp)
333: throws IOException;
334:
335: /***************************************************************************************************************
336: *
337: *
338: **************************************************************************************************************/
339: @Nonnull
340: private static EditableImage create (@Nonnull final BufferedImage image)
341: {
342: return new EditableImage(ImplementationFactoryJ2D.getDefault().createImageModel(image));
343: }
344:
345: /***************************************************************************************************************
346: *
347: *
348: **************************************************************************************************************/
349: @Nonnull
350: private static EditableImage create (@Nonnull final BufferedImage image,
351: @Nonnull final Duration latestOperationDuration)
352: {
353: final var editableImage = create(image);
354: editableImage.getInnerProperty(AccessorOp.class).setLatestOperationDuration(latestOperationDuration);
355: return editableImage;
356: }
357: }
358:
359: /*******************************************************************************************************************
360: *
361: * @param input the input (an ImageReader or a Path)
362: *
363: ******************************************************************************************************************/
364: public ReadOp (@Nonnull final Object input)
365: {
366: this(input, 0, 0);
367: }
368:
369: /*******************************************************************************************************************
370: *
371: * @param input the input (an ImageReader or a Path)
372: * @param type the type of read
373: *
374: ******************************************************************************************************************/
375: public ReadOp (@Nonnull final Object input, @Nonnull final Options... options)
376: {
377: this(input, 0, 0, options);
378: }
379:
380: /*******************************************************************************************************************
381: *
382: * @param input the input (an ImageReader or a Path)
383: * @param type the type of read
384: * @param imageIndex the index of the image to read
385: *
386: ******************************************************************************************************************/
387: public ReadOp (@Nonnull final Object input, @Nonnegative final int imageIndex, @Nonnull final Options... options)
388: {
389: this(input, imageIndex, 0, options);
390: }
391:
392: /*******************************************************************************************************************
393: *
394: * @param input the input (an ImageReader or a Path)
395: * @param type the type of read
396: * @param imageIndex the index of the image to read
397: * @param thumbnailIndex the index of the thumbnail to read
398: *
399: ******************************************************************************************************************/
400: public ReadOp (@Nonnull final Object input,
401: @Nonnegative final int imageIndex,
402: @Nonnegative final int thumbnailIndex,
403: @Nonnull final Options... options)
404: {
405: this.input = input;
406: this.type = Parameters.find(Type.class, Type.IMAGE, options);
407: this.pluginBlackList = Parameters.find(PluginBlackList.class, PluginBlackList.DEFAULT, options);
408: this.imageIndex = imageIndex;
409: this.thumbnailIndex = thumbnailIndex;
410: log.trace("ReadOp({}, {}, {}, {})", input, imageIndex, thumbnailIndex, options);
411: }
412:
413: /*******************************************************************************************************************
414: *
415: *
416: ******************************************************************************************************************/
417: @Nonnull
418: public EditableImage execute()
419: throws IOException
420: {
421: return type.read(this);
422: }
423:
424: /*******************************************************************************************************************
425: *
426: * Creates an ImageReader for the given Path. Using a Path as argument is
427: * important for photos that are stored in multiple files (e.g. Canon .CRW format).
428: * This method supports files GZIP compression (but multiple file formats such
429: * as .CRW aren't supported in this case).
430: *
431: * @throws IOException if it is not possible
432: *
433: ******************************************************************************************************************/
434: @Nonnull
435: public static ImageReader createImageReader (@Nonnull final Path file,
436: @Nonnull final PluginBlackList pluginBlackList)
437: throws IOException
438: {
439: log.trace("createImageReader({}, {})", file, pluginBlackList);
440:
441: if (!Files.exists(file))
442: {
443: throw new FileNotFoundException(file.toAbsolutePath().toString());
444: }
445:
446: if (!Files.isReadable(file))
447: {
448: throw new IOException("Cannot read " + file.toAbsolutePath());
449: }
450:
451: var fileName = file.getFileName().toString();
452: var suffix = "";
453: final var gzipCompression = fileName.toLowerCase().endsWith(".gz");
454:
455: if (gzipCompression)
456: {
457: fileName = fileName.substring(0, fileName.length() - 3);
458: }
459:
460: final var i = fileName.lastIndexOf('.');
461:
462: if (i > 0)
463: {
464: suffix = fileName.substring(i + 1);
465: }
466:
467: log.trace(">>>> suffix is {}", suffix);
468: ImageInputStream imageInputStream = null;
469:
470: //
471: // For reasons stated in the javadoc comment of this method, it's better
472: // to create the ImageInputStream by passing a Path.
473: //
474: if (!gzipCompression)
475: {
476: imageInputStream = new FileChannelImageInputStream(file.toFile());
477: // imageInputStream = ImageIO.createImageInputStream(file);
478: }
479: //
480: // This will not work with multiple-file formats such as Canon .CRW.
481: //
482: else
483: {
484: final InputStream inputStream = new GZIPInputStream(Files.newInputStream(file));
485: imageInputStream = ImageIO.createImageInputStream(inputStream);
486: }
487:
488: return createImageReader(imageInputStream, gzipCompression, suffix, pluginBlackList);
489: }
490:
491: /*******************************************************************************************************************
492: *
493: * Returns a valid <code>ImageReader</code> for the given URL, or throw an
494: * <code>IOException</code> if it's not possible.
495: *
496: * @param url the URL
497: * @throws IOException if it is not possible
498: *
499: ******************************************************************************************************************/
500: @Nonnull
501: public static ImageReader createImageReader (@Nonnull final URL url, @Nonnull final PluginBlackList pluginBlackList)
502: throws IOException
503: {
504: log.trace("createImageReader({})", url);
505:
506: final var fileName = url.getPath();
507: var suffix = "";
508: final var gzipCompression = fileName.toLowerCase().endsWith(".gz");
509:
510: final var i = fileName.lastIndexOf('.');
511:
512: if (i > 0)
513: {
514: suffix = fileName.substring(i + 1);
515: }
516:
517: log.trace(">>>> suffix is {}", suffix);
518: ImageInputStream imageInputStream = null;
519: //
520: // This will not work with multiple-file formats such as Canon .CRW.
521: //
522: final var inputStream = gzipCompression ? new GZIPInputStream(url.openStream()) : url.openStream();
523: imageInputStream = ImageIO.createImageInputStream(inputStream);
524: return createImageReader(imageInputStream, gzipCompression, suffix, pluginBlackList);
525: }
526:
527: /*******************************************************************************************************************
528: *
529: * Returns a valid <code>ImageReader</code> for the given stream, or throw
530: * an <code>IOException</code> if it's not possible.
531: *
532: * @param inputStream the input stream
533: * @throws IOException if it is not possible
534: *
535: ******************************************************************************************************************/
536: @Nonnull
537: private static ImageReader createImageReader (@Nonnull final InputStream inputStream,
538: @Nonnull final PluginBlackList pluginBlackList)
539: throws IOException
540: {
541: log.info("createImageReader({})", inputStream);
542: final var imageInputStream = ImageIO.createImageInputStream(inputStream);
543: final var iterator = ImageIO.getImageReaders(imageInputStream);
544: return createImageReader(imageInputStream, iterator, pluginBlackList);
545: }
546:
547: /*******************************************************************************************************************
548: *
549: * Returns a valid <code>ImageReader</code> for the given stream, or throw
550: * an <code>IOException</code> if it's not possible.
551: *
552: * @param imageInputStream the input stream
553: * @param gzipCompression if the stream is compressed
554: * @param suffix the file format suffix (e.g. jpg, tiff,...)
555: * @throws IOException if it is not possible
556: *
557: ******************************************************************************************************************/
558: private static ImageReader createImageReader (@Nonnull final ImageInputStream imageInputStream,
559: final boolean gzipCompression,
560: @Nonnull final String suffix,
561: @Nonnull final PluginBlackList pluginBlackList)
562: throws IOException
563: {
564: log.info("createImageReader({}, {}, {})", imageInputStream, gzipCompression, suffix);
565: // logger.finest(">>>> Suffixes: " + Arrays.asList(ImageIO.getReaderFileSuffixes()));
566: final var iterator = ImageIO.getImageReaders(imageInputStream);
567: return createImageReader(imageInputStream, iterator, pluginBlackList);
568: }
569:
570: /*******************************************************************************************************************
571: *
572: * Returns a valid <code>ImageReader</code> for the given stream, or throw
573: * an <code>IOException</code> if it's not possible.
574: *
575: * @param imageInputStream the input stream
576: * @throws IOException if it is not possible
577: *
578: ******************************************************************************************************************/
579: @Nonnull
580: private static ImageReader createImageReader (@Nonnull final ImageInputStream imageInputStream,
581: @Nonnull final Iterator<? extends ImageReader> iterator,
582: @Nonnull final PluginBlackList pluginBlackList)
583: throws IOException
584: {
585: log.info("createImageReader({}, {})", imageInputStream, iterator);
586:
587: // See http://bluemarine.tidalwave.it/issues/browse/MST-137
588: final List<ImageReader> readers = new ArrayList<>();
589: final List<ImageReader> tiffReaders = new ArrayList<>();
590:
591: if (!iterator.hasNext())
592: {
593: log.warn("Iterator is empty");
594: }
595:
596: while (iterator.hasNext())
597: {
598: final var reader = iterator.next();
599: final var pluginClassName = reader.getOriginatingProvider().getPluginClassName();
600:
601: if (reader != null)
602: {
603: log.trace(">>>> pre-testing reader: {}, vendor: {}",
604: reader,
605: reader.getOriginatingProvider().getVendorName());
606:
607: if (pluginBlackList.contains(pluginClassName))
608: {
609: log.trace(">>>> {} discarded because it's in the black list", reader);
610: }
611: else if (!pluginClassName.contains("TIFF")) // TODO: maybe is it better to test for supported extension or mime?
612: {
613: readers.add(reader);
614: }
615: else
616: {
617: tiffReaders.add(reader);
618: }
619: }
620: }
621:
622: readers.addAll(tiffReaders);
623:
624: for (final var reader : readers)
625: {
626: log.trace(">>>> testing reader: {}, vendor: {}", reader, reader.getOriginatingProvider().getVendorName());
627:
628: if (!reader.getOriginatingProvider().canDecodeInput(imageInputStream))
629: {
630: log.trace(">>>> discarded because it can't decode the input");
631: continue;
632: }
633:
634: reader.setInput(imageInputStream);
635: log.trace(">>>> returning reader: {}", reader);
636: return reader;
637: }
638:
639: throw new IOException("No ImageReader");
640: }
641:
642: /*******************************************************************************************************************
643: *
644: *
645: ******************************************************************************************************************/
646: private static void loadMetadata (final @Nonnull EditableImage image,
647: final @Nonnull ImageReader reader,
648: final @Nonnegative int imageIndex)
649: {
650: log.trace("loadMetadata({}, {})", reader, imageIndex);
651: var accessor = image.getInnerProperty(AccessorOp.class);
652: var metadataMapByClass = accessor.getMetadataMapByClass();
653: final IIOMetadata iioMetadata;
654:
655: try
656: {
657: iioMetadata = reader.getImageMetadata(imageIndex);
658: }
659: catch (Exception e)
660: {
661: throw new RuntimeException(e);
662: /*
663: if ("ICC APP2 encountered without prior JFIF!".equals(e.getMessage()) && (workaroundBM25 != null))
664: {
665: try
666: {
667: var tiff = new TIFF();
668: var exif = new EXIF();
669: var iptc = new IPTC();
670: var xmp = new XMP();
671: workaroundBM25.loadExifAndIptcFromJpeg(reader, tiff, exif, iptc, xmp);
672: metadataMapByClass.put(TIFF.class, List.of(tiff));
673: metadataMapByClass.put(EXIF.class, List.of(exif));
674: metadataMapByClass.put(IPTC.class, List.of(iptc));
675: metadataMapByClass.put(XMP.class, List.of(xmp));
676: }
677: catch (Exception e1)
678: {
679: log.error("Cannot load EXIF/IPTC metadata: ", e1);
680: }
681: }
682: else
683: {
684: log.error("Cannot load EXIF/IPTC metadata: ", e);
685: }
686:
687: return;
688: */
689: }
690:
691: if (iioMetadata == null)
692: {
693: log.warn(">>>> null imagemetadata");
694: return;
695: }
696:
697: accessor.setIIOMetadata(iioMetadata);
698:
699: var iioMetadataClass = iioMetadata.getClass();
700: final MetadataLoader metadataLoader;
701:
702: if (isSubClass(iioMetadataClass, "com.sun.imageio.plugins.jpeg.JPEGMetadata"))
703: {
704: metadataLoader = new JpegDrewMetadataLoader(reader);
705: }
706: else if (isSubClass(iioMetadataClass, "com.sun.media.imageio.plugins.tiff.TIFFImageMetadata"))
707: {
708: metadataLoader = new TIFFMetadataLoader();
709: }
710: else if (isSubClass(iioMetadataClass, "it.tidalwave.imageio.raw.RAWMetadataSupport"))
711: {
712: metadataLoader = new RAWMetadataLoader();
713: }
714: else
715: {
716: metadataLoader = new DrewMetadataLoader();
717: }
718:
719: log.debug(">>>> iioMetadata class: {}, using metadata loader: {}", iioMetadataClass, metadataLoader.getClass());
720:
721: try
722: {
723: List.of(TIFF.class, EXIF.class, MakerNote.class, IPTC.class, XMP.class)
724: .forEach(_c(t -> metadataMapByClass.put(t, loadDirectories(iioMetadata, metadataLoader, t))));
725: }
726: catch (Exception e)
727: {
728: log.error("loadMetadata()", e);
729: }
730: }
731:
732: /*******************************************************************************************************************
733: *
734: * Loads directories of metadata by means of a loader.
735: *
736: * @param iioMetadata
737: * @param metadataLoader the metadata loader
738: * @param directoryClass the type of the directory to laod
739: * @return the loaded items
740: *
741: ******************************************************************************************************************/
742: private static <T extends Directory> List<Directory> loadDirectories (final @Nonnull IIOMetadata iioMetadata,
743: final @Nonnull MetadataLoader metadataLoader,
744: final @Nonnull Class<T> directoryClass)
745: {
746: log.debug("loadDirectories({}, {}, {})", iioMetadata, metadataLoader, directoryClass);
747: final var items = new ArrayList<Directory>();
748: Optional<DirectoryLoader> loader = Optional.empty();
749:
750: // FIXME: get rid of the if chain
751: if (TIFF.class.equals(directoryClass))
752: {
753: loader = metadataLoader.getTiffLoader(iioMetadata);
754: }
755: else if (EXIF.class.equals(directoryClass))
756: {
757: loader = metadataLoader.getExifLoader(iioMetadata);
758: }
759: else if (IPTC.class.equals(directoryClass))
760: {
761: loader = metadataLoader.getIptcLoader(iioMetadata);
762: }
763: else if (XMP.class.equals(directoryClass))
764: {
765: loader = metadataLoader.getXmpLoader(iioMetadata);
766: }
767: else if (MakerNote.class.equals(directoryClass))
768: {
769: loader = metadataLoader.getMakerNoteLoader(iioMetadata);
770: }
771:
772: loader.ifPresentOrElse(_c(a -> items.addAll(loadDirectories(a, directoryClass))),
773: () -> log.warn("No loader for {}", directoryClass));
774: return items;
775: }
776:
777: /*******************************************************************************************************************
778: *
779: ******************************************************************************************************************/
780: private static List<Directory> loadDirectories (@Nonnull DirectoryLoader loader,
781: final @Nonnull Class<? extends Directory> itemClass)
782: throws InstantiationException, IllegalAccessException
783: {
784: var result = new ArrayList<Directory>();
785:
786: for (; ; loader = loader.next())
787: {
788: final var item = itemClass.newInstance();
789: item.load(loader);
790: result.add(item);
791:
792: if (!loader.hasNext())
793: {
794: break;
795: }
796: }
797:
798: return result;
799: }
800:
801: /*******************************************************************************************************************
802: *
803: *
804: ******************************************************************************************************************/
805: private static boolean isSubClass (@Nonnull Class<?> aClass, final @Nonnull String ancestorClassName)
806: {
807: for (; aClass != null; aClass = aClass.getSuperclass())
808: {
809: if (aClass.getName().equals(ancestorClassName))
810: {
811: return true;
812: }
813: }
814:
815: return false;
816: }
817: }