View Javadoc
1   /*
2    * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.imageio.plugins.jpeg;
27  
28  import javax.imageio.ImageTypeSpecifier;
29  import javax.imageio.ImageWriteParam;
30  import javax.imageio.IIOException;
31  import javax.imageio.stream.ImageInputStream;
32  import javax.imageio.stream.ImageOutputStream;
33  import javax.imageio.metadata.IIOMetadata;
34  import javax.imageio.metadata.IIOMetadataNode;
35  import javax.imageio.metadata.IIOMetadataFormat;
36  import javax.imageio.metadata.IIOMetadataFormatImpl;
37  import javax.imageio.metadata.IIOInvalidTreeException;
38  import javax.imageio.plugins.jpeg.JPEGQTable;
39  import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
40  import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
41  
42  import org.w3c.dom.Node;
43  import org.w3c.dom.NodeList;
44  import org.w3c.dom.NamedNodeMap;
45  
46  import java.util.List;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.Iterator;
50  import java.util.ListIterator;
51  import java.io.IOException;
52  import java.awt.color.ICC_Profile;
53  import java.awt.color.ICC_ColorSpace;
54  import java.awt.color.ColorSpace;
55  import java.awt.image.ColorModel;
56  import java.awt.Point;
57  
58  /**
59   * Metadata for the JPEG plug-in.
60   */
61  public class JPEGMetadata extends IIOMetadata implements Cloneable {
62  
63      //////// Private variables
64  
65      private static final boolean debug = false;
66  
67      /**
68       * A copy of <code>markerSequence</code>, created the first time the
69       * <code>markerSequence</code> is modified.  This is used by reset
70       * to restore the original state.
71       */
72      private List resetSequence = null;
73  
74      /**
75       * Set to <code>true</code> when reading a thumbnail stored as
76       * JPEG.  This is used to enforce the prohibition of JFIF thumbnails
77       * containing any JFIF marker segments, and to ensure generation of
78       * a correct native subtree during <code>getAsTree</code>.
79       */
80      private boolean inThumb = false;
81  
82      /**
83       * Set by the chroma node construction method to signal the
84       * presence or absence of an alpha channel to the transparency
85       * node construction method.  Used only when constructing a
86       * standard metadata tree.
87       */
88      private boolean hasAlpha;
89  
90      //////// end of private variables
91  
92      /////// Package-access variables
93  
94      /**
95       * All data is a list of <code>MarkerSegment</code> objects.
96       * When accessing the list, use the tag to identify the particular
97       * subclass.  Any JFIF marker segment must be the first element
98       * of the list if it is present, and any JFXX or APP2ICC marker
99       * segments are subordinate to the JFIF marker segment.  This
100      * list is package visible so that the writer can access it.
101      * @see #MarkerSegment
102      */
103     List markerSequence = new ArrayList();
104 
105     /**
106      * Indicates whether this object represents stream or image
107      * metadata.  Package-visible so the writer can see it.
108      */
109     final boolean isStream;
110 
111     /////// End of package-access variables
112 
113     /////// Constructors
114 
115     /**
116      * Constructor containing code shared by other constructors.
117      */
118     JPEGMetadata(boolean isStream, boolean inThumb) {
119         super(true,  // Supports standard format
120               JPEG.nativeImageMetadataFormatName,  // and a native format
121               JPEG.nativeImageMetadataFormatClassName,
122               null, null);  // No other formats
123         this.inThumb = inThumb;
124         // But if we are stream metadata, adjust the variables
125         this.isStream = isStream;
126         if (isStream) {
127             nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
128             nativeMetadataFormatClassName =
129                 JPEG.nativeStreamMetadataFormatClassName;
130         }
131     }
132 
133     /*
134      * Constructs a <code>JPEGMetadata</code> object by reading the
135      * contents of an <code>ImageInputStream</code>.  Has package-only
136      * access.
137      *
138      * @param isStream A boolean indicating whether this object will be
139      * stream or image metadata.
140      * @param isThumb A boolean indicating whether this metadata object
141      * is for an image or for a thumbnail stored as JPEG.
142      * @param iis An <code>ImageInputStream</code> from which to read
143      * the metadata.
144      * @param reader The <code>JPEGImageReader</code> calling this
145      * constructor, to which warnings should be sent.
146      */
147     JPEGMetadata(boolean isStream,
148                  boolean isThumb,
149                  ImageInputStream iis,
150                  JPEGImageReader reader) throws IOException {
151         this(isStream, isThumb);
152 
153         JPEGBuffer buffer = new JPEGBuffer(iis);
154 
155         buffer.loadBuf(0);
156 
157         // The first three bytes should be FF, SOI, FF
158         if (((buffer.buf[0] & 0xff) != 0xff)
159             || ((buffer.buf[1] & 0xff) != JPEG.SOI)
160             || ((buffer.buf[2] & 0xff) != 0xff)) {
161             throw new IIOException ("Image format error");
162         }
163 
164         boolean done = false;
165         buffer.bufAvail -=2;  // Next byte should be the ff before a marker
166         buffer.bufPtr = 2;
167         MarkerSegment newGuy = null;
168         while (!done) {
169             byte [] buf;
170             int ptr;
171             buffer.loadBuf(1);
172             if (debug) {
173                 System.out.println("top of loop");
174                 buffer.print(10);
175             }
176             buffer.scanForFF(reader);
177             switch (buffer.buf[buffer.bufPtr] & 0xff) {
178             case 0:
179                 if (debug) {
180                     System.out.println("Skipping 0");
181                 }
182                 buffer.bufAvail--;
183                 buffer.bufPtr++;
184                 break;
185             case JPEG.SOF0:
186             case JPEG.SOF1:
187             case JPEG.SOF2:
188                 if (isStream) {
189                     throw new IIOException
190                         ("SOF not permitted in stream metadata");
191                 }
192                 newGuy = new SOFMarkerSegment(buffer);
193                 break;
194             case JPEG.DQT:
195                 newGuy = new DQTMarkerSegment(buffer);
196                 break;
197             case JPEG.DHT:
198                 newGuy = new DHTMarkerSegment(buffer);
199                 break;
200             case JPEG.DRI:
201                 newGuy = new DRIMarkerSegment(buffer);
202                 break;
203             case JPEG.APP0:
204                 // Either JFIF, JFXX, or unknown APP0
205                 buffer.loadBuf(8); // tag, length, id
206                 buf = buffer.buf;
207                 ptr = buffer.bufPtr;
208                 if ((buf[ptr+3] == 'J')
209                     && (buf[ptr+4] == 'F')
210                     && (buf[ptr+5] == 'I')
211                     && (buf[ptr+6] == 'F')
212                     && (buf[ptr+7] == 0)) {
213                     if (inThumb) {
214                         reader.warningOccurred
215                             (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);
216                         // Leave newGuy null
217                         // Read a dummy to skip the segment
218                         JFIFMarkerSegment dummy =
219                             new JFIFMarkerSegment(buffer);
220                     } else if (isStream) {
221                         throw new IIOException
222                             ("JFIF not permitted in stream metadata");
223                     } else if (markerSequence.isEmpty() == false) {
224                         throw new IIOException
225                             ("JFIF APP0 must be first marker after SOI");
226                     } else {
227                         newGuy = new JFIFMarkerSegment(buffer);
228                     }
229                 } else if ((buf[ptr+3] == 'J')
230                            && (buf[ptr+4] == 'F')
231                            && (buf[ptr+5] == 'X')
232                            && (buf[ptr+6] == 'X')
233                            && (buf[ptr+7] == 0)) {
234                     if (isStream) {
235                         throw new IIOException
236                             ("JFXX not permitted in stream metadata");
237                     }
238                     if (inThumb) {
239                         throw new IIOException
240                           ("JFXX markers not allowed in JFIF JPEG thumbnail");
241                     }
242                     JFIFMarkerSegment jfif =
243                         (JFIFMarkerSegment) findMarkerSegment
244                                (JFIFMarkerSegment.class, true);
245                     if (jfif == null) {
246                         throw new IIOException
247                             ("JFXX encountered without prior JFIF!");
248                     }
249                     jfif.addJFXX(buffer, reader);
250                     // newGuy remains null
251                 } else {
252                     newGuy = new MarkerSegment(buffer);
253                     newGuy.loadData(buffer);
254                 }
255                 break;
256             case JPEG.APP2:
257                 // Either an ICC profile or unknown APP2
258                 buffer.loadBuf(15); // tag, length, id
259                 if ((buffer.buf[buffer.bufPtr+3] == 'I')
260                     && (buffer.buf[buffer.bufPtr+4] == 'C')
261                     && (buffer.buf[buffer.bufPtr+5] == 'C')
262                     && (buffer.buf[buffer.bufPtr+6] == '_')
263                     && (buffer.buf[buffer.bufPtr+7] == 'P')
264                     && (buffer.buf[buffer.bufPtr+8] == 'R')
265                     && (buffer.buf[buffer.bufPtr+9] == 'O')
266                     && (buffer.buf[buffer.bufPtr+10] == 'F')
267                     && (buffer.buf[buffer.bufPtr+11] == 'I')
268                     && (buffer.buf[buffer.bufPtr+12] == 'L')
269                     && (buffer.buf[buffer.bufPtr+13] == 'E')
270                     && (buffer.buf[buffer.bufPtr+14] == 0)
271                     ) {
272                     if (isStream) {
273                         throw new IIOException
274                             ("ICC profiles not permitted in stream metadata");
275                     }
276 
277                     JFIFMarkerSegment jfif =
278                         (JFIFMarkerSegment) findMarkerSegment
279                         (JFIFMarkerSegment.class, true);
280                     if (jfif == null) {
281                         newGuy = new MarkerSegment(buffer);
282                         newGuy.loadData(buffer);
283                     } else {
284                         jfif.addICC(buffer);
285                     }
286                     // newGuy remains null
287                 } else {
288                     newGuy = new MarkerSegment(buffer);
289                     newGuy.loadData(buffer);
290                 }
291                 break;
292             case JPEG.APP14:
293                 // Either Adobe or unknown APP14
294                 buffer.loadBuf(8); // tag, length, id
295                 if ((buffer.buf[buffer.bufPtr+3] == 'A')
296                     && (buffer.buf[buffer.bufPtr+4] == 'd')
297                     && (buffer.buf[buffer.bufPtr+5] == 'o')
298                     && (buffer.buf[buffer.bufPtr+6] == 'b')
299                     && (buffer.buf[buffer.bufPtr+7] == 'e')) {
300                     if (isStream) {
301                         throw new IIOException
302                       ("Adobe APP14 markers not permitted in stream metadata");
303                     }
304                     newGuy = new AdobeMarkerSegment(buffer);
305                 } else {
306                     newGuy = new MarkerSegment(buffer);
307                     newGuy.loadData(buffer);
308                 }
309 
310                 break;
311             case JPEG.COM:
312                 newGuy = new COMMarkerSegment(buffer);
313                 break;
314             case JPEG.SOS:
315                 if (isStream) {
316                     throw new IIOException
317                         ("SOS not permitted in stream metadata");
318                 }
319                 newGuy = new SOSMarkerSegment(buffer);
320                 break;
321             case JPEG.RST0:
322             case JPEG.RST1:
323             case JPEG.RST2:
324             case JPEG.RST3:
325             case JPEG.RST4:
326             case JPEG.RST5:
327             case JPEG.RST6:
328             case JPEG.RST7:
329                 if (debug) {
330                     System.out.println("Restart Marker");
331                 }
332                 buffer.bufPtr++; // Just skip it
333                 buffer.bufAvail--;
334                 break;
335             case JPEG.EOI:
336                 done = true;
337                 buffer.bufPtr++;
338                 buffer.bufAvail--;
339                 break;
340             default:
341                 newGuy = new MarkerSegment(buffer);
342                 newGuy.loadData(buffer);
343                 newGuy.unknown = true;
344                 break;
345             }
346             if (newGuy != null) {
347                 markerSequence.add(newGuy);
348                 if (debug) {
349                     newGuy.print();
350                 }
351                 newGuy = null;
352             }
353         }
354 
355         // Now that we've read up to the EOI, we need to push back
356         // whatever is left in the buffer, so that the next read
357         // in the native code will work.
358 
359         buffer.pushBack();
360 
361         if (!isConsistent()) {
362             throw new IIOException("Inconsistent metadata read from stream");
363         }
364     }
365 
366     /**
367      * Constructs a default stream <code>JPEGMetadata</code> object appropriate
368      * for the given write parameters.
369      */
370     JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {
371         this(true, false);
372 
373         JPEGImageWriteParam jparam = null;
374 
375         if ((param != null) && (param instanceof JPEGImageWriteParam)) {
376             jparam = (JPEGImageWriteParam) param;
377             if (!jparam.areTablesSet()) {
378                 jparam = null;
379             }
380         }
381         if (jparam != null) {
382             markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
383             markerSequence.add
384                 (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
385                                       jparam.getACHuffmanTables()));
386         } else {
387             // default tables.
388             markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
389             markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
390                                                     JPEG.getDefaultHuffmanTables(false)));
391         }
392 
393         // Defensive programming
394         if (!isConsistent()) {
395             throw new InternalError("Default stream metadata is inconsistent");
396         }
397     }
398 
399     /**
400      * Constructs a default image <code>JPEGMetadata</code> object appropriate
401      * for the given image type and write parameters.
402      */
403     JPEGMetadata(ImageTypeSpecifier imageType,
404                  ImageWriteParam param,
405                  JPEGImageWriter writer) {
406         this(false, false);
407 
408         boolean wantJFIF = true;
409         boolean wantAdobe = false;
410         int transform = JPEG.ADOBE_UNKNOWN;
411         boolean willSubsample = true;
412         boolean wantICC = false;
413         boolean wantProg = false;
414         boolean wantOptimized = false;
415         boolean wantExtended = false;
416         boolean wantQTables = true;
417         boolean wantHTables = true;
418         float quality = JPEG.DEFAULT_QUALITY;
419         byte[] componentIDs = { 1, 2, 3, 4};
420         int numComponents = 0;
421 
422         ImageTypeSpecifier destType = null;
423 
424         if (param != null) {
425             destType = param.getDestinationType();
426             if (destType != null) {
427                 if (imageType != null) {
428                     // Ignore the destination type.
429                     writer.warningOccurred
430                         (JPEGImageWriter.WARNING_DEST_IGNORED);
431                     destType = null;
432                 }
433             }
434             // The only progressive mode that makes sense here is MODE_DEFAULT
435             if (param.canWriteProgressive()) {
436                 // the param may not be one of ours, so it may return false.
437                 // If so, the following would throw an exception
438                 if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
439                     wantProg = true;
440                     wantOptimized = true;
441                     wantHTables = false;
442                 }
443             }
444 
445             if (param instanceof JPEGImageWriteParam) {
446                 JPEGImageWriteParam jparam = (JPEGImageWriteParam) param;
447                 if (jparam.areTablesSet()) {
448                     wantQTables = false;  // If the param has them, metadata shouldn't
449                     wantHTables = false;
450                     if ((jparam.getDCHuffmanTables().length > 2)
451                             || (jparam.getACHuffmanTables().length > 2)) {
452                         wantExtended = true;
453                     }
454                 }
455                 // Progressive forces optimized, regardless of param setting
456                 // so consult the param re optimized only if not progressive
457                 if (!wantProg) {
458                     wantOptimized = jparam.getOptimizeHuffmanTables();
459                     if (wantOptimized) {
460                         wantHTables = false;
461                     }
462                 }
463             }
464 
465             // compression quality should determine the q tables.  Note that this
466             // will be ignored if we already decided not to create any.
467             // Again, the param may not be one of ours, so we must check that it
468             // supports compression settings
469             if (param.canWriteCompressed()) {
470                 if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
471                     quality = param.getCompressionQuality();
472                 }
473             }
474         }
475 
476         // We are done with the param, now for the image types
477 
478         ColorSpace cs = null;
479         if (destType != null) {
480             ColorModel cm = destType.getColorModel();
481             numComponents = cm.getNumComponents();
482             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
483             boolean hasAlpha = cm.hasAlpha();
484             cs = cm.getColorSpace();
485             int type = cs.getType();
486             switch(type) {
487             case ColorSpace.TYPE_GRAY:
488                 willSubsample = false;
489                 if (hasExtraComponents) {  // e.g. alpha
490                     wantJFIF = false;
491                 }
492                 break;
493             case ColorSpace.TYPE_3CLR:
494                 if (cs == JPEG.JCS.getYCC()) {
495                     wantJFIF = false;
496                     componentIDs[0] = (byte) 'Y';
497                     componentIDs[1] = (byte) 'C';
498                     componentIDs[2] = (byte) 'c';
499                     if (hasAlpha) {
500                         componentIDs[3] = (byte) 'A';
501                     }
502                 }
503                 break;
504             case ColorSpace.TYPE_YCbCr:
505                 if (hasExtraComponents) { // e.g. K or alpha
506                     wantJFIF = false;
507                     if (!hasAlpha) { // Not alpha, so must be K
508                         wantAdobe = true;
509                         transform = JPEG.ADOBE_YCCK;
510                     }
511                 }
512                 break;
513             case ColorSpace.TYPE_RGB:  // with or without alpha
514                 wantJFIF = false;
515                 wantAdobe = true;
516                 willSubsample = false;
517                 componentIDs[0] = (byte) 'R';
518                 componentIDs[1] = (byte) 'G';
519                 componentIDs[2] = (byte) 'B';
520                 if (hasAlpha) {
521                     componentIDs[3] = (byte) 'A';
522                 }
523                 break;
524             default:
525                 // Everything else is not subsampled, gets no special marker,
526                 // and component ids are 1 - N
527                 wantJFIF = false;
528                 willSubsample = false;
529             }
530         } else if (imageType != null) {
531             ColorModel cm = imageType.getColorModel();
532             numComponents = cm.getNumComponents();
533             boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
534             boolean hasAlpha = cm.hasAlpha();
535             cs = cm.getColorSpace();
536             int type = cs.getType();
537             switch(type) {
538             case ColorSpace.TYPE_GRAY:
539                 willSubsample = false;
540                 if (hasExtraComponents) {  // e.g. alpha
541                     wantJFIF = false;
542                 }
543                 break;
544             case ColorSpace.TYPE_RGB:  // with or without alpha
545                 // without alpha we just accept the JFIF defaults
546                 if (hasAlpha) {
547                     wantJFIF = false;
548                 }
549                 break;
550             case ColorSpace.TYPE_3CLR:
551                 wantJFIF = false;
552                 willSubsample = false;
553                 if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) {
554                     willSubsample = true;
555                     wantAdobe = true;
556                     componentIDs[0] = (byte) 'Y';
557                     componentIDs[1] = (byte) 'C';
558                     componentIDs[2] = (byte) 'c';
559                     if (hasAlpha) {
560                         componentIDs[3] = (byte) 'A';
561                     }
562                 }
563                 break;
564             case ColorSpace.TYPE_YCbCr:
565                 if (hasExtraComponents) { // e.g. K or alpha
566                     wantJFIF = false;
567                     if (!hasAlpha) {  // then it must be K
568                         wantAdobe = true;
569                         transform = JPEG.ADOBE_YCCK;
570                     }
571                 }
572                 break;
573             case ColorSpace.TYPE_CMYK:
574                 wantJFIF = false;
575                 wantAdobe = true;
576                 transform = JPEG.ADOBE_YCCK;
577                 break;
578 
579             default:
580                 // Everything else is not subsampled, gets no special marker,
581                 // and component ids are 0 - N
582                 wantJFIF = false;
583                 willSubsample = false;
584             }
585 
586         }
587 
588         // do we want an ICC profile?
589         if (wantJFIF && JPEG.isNonStandardICC(cs)) {
590             wantICC = true;
591         }
592 
593         // Now step through the markers, consulting our variables.
594         if (wantJFIF) {
595             JFIFMarkerSegment jfif = new JFIFMarkerSegment();
596             markerSequence.add(jfif);
597             if (wantICC) {
598                 try {
599                     jfif.addICC((ICC_ColorSpace)cs);
600                 } catch (IOException e) {} // Can't happen here
601             }
602         }
603         // Adobe
604         if (wantAdobe) {
605             markerSequence.add(new AdobeMarkerSegment(transform));
606         }
607 
608         // dqt
609         if (wantQTables) {
610             markerSequence.add(new DQTMarkerSegment(quality, willSubsample));
611         }
612 
613         // dht
614         if (wantHTables) {
615             markerSequence.add(new DHTMarkerSegment(willSubsample));
616         }
617 
618         // sof
619         markerSequence.add(new SOFMarkerSegment(wantProg,
620                                                 wantExtended,
621                                                 willSubsample,
622                                                 componentIDs,
623                                                 numComponents));
624 
625         // sos
626         if (!wantProg) {  // Default progression scans are done in the writer
627             markerSequence.add(new SOSMarkerSegment(willSubsample,
628                                                     componentIDs,
629                                                     numComponents));
630         }
631 
632         // Defensive programming
633         if (!isConsistent()) {
634             throw new InternalError("Default image metadata is inconsistent");
635         }
636     }
637 
638     ////// End of constructors
639 
640     // Utilities for dealing with the marker sequence.
641     // The first ones have package access for access from the writer.
642 
643     /**
644      * Returns the first MarkerSegment object in the list
645      * with the given tag, or null if none is found.
646      */
647     MarkerSegment findMarkerSegment(int tag) {
648         Iterator iter = markerSequence.iterator();
649         while (iter.hasNext()) {
650             MarkerSegment seg = (MarkerSegment)iter.next();
651             if (seg.tag == tag) {
652                 return seg;
653             }
654         }
655         return null;
656     }
657 
658     /**
659      * Returns the first or last MarkerSegment object in the list
660      * of the given class, or null if none is found.
661      */
662     MarkerSegment findMarkerSegment(Class cls, boolean first) {
663         if (first) {
664             Iterator iter = markerSequence.iterator();
665             while (iter.hasNext()) {
666                 MarkerSegment seg = (MarkerSegment)iter.next();
667                 if (cls.isInstance(seg)) {
668                     return seg;
669                 }
670             }
671         } else {
672             ListIterator iter = markerSequence.listIterator(markerSequence.size());
673             while (iter.hasPrevious()) {
674                 MarkerSegment seg = (MarkerSegment)iter.previous();
675                 if (cls.isInstance(seg)) {
676                     return seg;
677                 }
678             }
679         }
680         return null;
681     }
682 
683     /**
684      * Returns the index of the first or last MarkerSegment in the list
685      * of the given class, or -1 if none is found.
686      */
687     private int findMarkerSegmentPosition(Class cls, boolean first) {
688         if (first) {
689             ListIterator iter = markerSequence.listIterator();
690             for (int i = 0; iter.hasNext(); i++) {
691                 MarkerSegment seg = (MarkerSegment)iter.next();
692                 if (cls.isInstance(seg)) {
693                     return i;
694                 }
695             }
696         } else {
697             ListIterator iter = markerSequence.listIterator(markerSequence.size());
698             for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
699                 MarkerSegment seg = (MarkerSegment)iter.previous();
700                 if (cls.isInstance(seg)) {
701                     return i;
702                 }
703             }
704         }
705         return -1;
706     }
707 
708     private int findLastUnknownMarkerSegmentPosition() {
709         ListIterator iter = markerSequence.listIterator(markerSequence.size());
710         for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
711             MarkerSegment seg = (MarkerSegment)iter.previous();
712             if (seg.unknown == true) {
713                 return i;
714             }
715         }
716         return -1;
717     }
718 
719     // Implement Cloneable, but restrict access
720 
721     protected Object clone() {
722         JPEGMetadata newGuy = null;
723         try {
724             newGuy = (JPEGMetadata) super.clone();
725         } catch (CloneNotSupportedException e) {} // won't happen
726         if (markerSequence != null) {
727             newGuy.markerSequence = (List) cloneSequence();
728         }
729         newGuy.resetSequence = null;
730         return newGuy;
731     }
732 
733     /**
734      * Returns a deep copy of the current marker sequence.
735      */
736     private List cloneSequence() {
737         if (markerSequence == null) {
738             return null;
739         }
740         List retval = new ArrayList(markerSequence.size());
741         Iterator iter = markerSequence.iterator();
742         while(iter.hasNext()) {
743             MarkerSegment seg = (MarkerSegment)iter.next();
744             retval.add(seg.clone());
745         }
746 
747         return retval;
748     }
749 
750 
751     // Tree methods
752 
753     public Node getAsTree(String formatName) {
754         if (formatName == null) {
755             throw new IllegalArgumentException("null formatName!");
756         }
757         if (isStream) {
758             if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
759                 return getNativeTree();
760             }
761         } else {
762             if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
763                 return getNativeTree();
764             }
765             if (formatName.equals
766                     (IIOMetadataFormatImpl.standardMetadataFormatName)) {
767                 return getStandardTree();
768             }
769         }
770         throw  new IllegalArgumentException("Unsupported format name: "
771                                                 + formatName);
772     }
773 
774     IIOMetadataNode getNativeTree() {
775         IIOMetadataNode root;
776         IIOMetadataNode top;
777         Iterator iter = markerSequence.iterator();
778         if (isStream) {
779             root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName);
780             top = root;
781         } else {
782             IIOMetadataNode sequence = new IIOMetadataNode("markerSequence");
783             if (!inThumb) {
784                 root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName);
785                 IIOMetadataNode header = new IIOMetadataNode("JPEGvariety");
786                 root.appendChild(header);
787                 JFIFMarkerSegment jfif = (JFIFMarkerSegment)
788                     findMarkerSegment(JFIFMarkerSegment.class, true);
789                 if (jfif != null) {
790                     iter.next();  // JFIF must be first, so this skips it
791                     header.appendChild(jfif.getNativeNode());
792                 }
793                 root.appendChild(sequence);
794             } else {
795                 root = sequence;
796             }
797             top = sequence;
798         }
799         while(iter.hasNext()) {
800             MarkerSegment seg = (MarkerSegment) iter.next();
801             top.appendChild(seg.getNativeNode());
802         }
803         return root;
804     }
805 
806     // Standard tree node methods
807 
808     protected IIOMetadataNode getStandardChromaNode() {
809         hasAlpha = false;  // Unless we find otherwise
810 
811         // Colorspace type - follow the rules in the spec
812         // First get the SOF marker segment, if there is one
813         SOFMarkerSegment sof = (SOFMarkerSegment)
814             findMarkerSegment(SOFMarkerSegment.class, true);
815         if (sof == null) {
816             // No image, so no chroma
817             return null;
818         }
819 
820         IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
821         IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
822         chroma.appendChild(csType);
823 
824         // get the number of channels
825         int numChannels = sof.componentSpecs.length;
826 
827         IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
828         chroma.appendChild(numChanNode);
829         numChanNode.setAttribute("value", Integer.toString(numChannels));
830 
831         // is there a JFIF marker segment?
832         if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) {
833             if (numChannels == 1) {
834                 csType.setAttribute("name", "GRAY");
835             } else {
836                 csType.setAttribute("name", "YCbCr");
837             }
838             return chroma;
839         }
840 
841         // How about an Adobe marker segment?
842         AdobeMarkerSegment adobe =
843             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
844         if (adobe != null){
845             switch (adobe.transform) {
846             case JPEG.ADOBE_YCCK:
847                 csType.setAttribute("name", "YCCK");
848                 break;
849             case JPEG.ADOBE_YCC:
850                 csType.setAttribute("name", "YCbCr");
851                 break;
852             case JPEG.ADOBE_UNKNOWN:
853                 if (numChannels == 3) {
854                     csType.setAttribute("name", "RGB");
855                 } else if (numChannels == 4) {
856                     csType.setAttribute("name", "CMYK");
857                 }
858                 break;
859             }
860             return chroma;
861         }
862 
863         // Neither marker.  Check components
864         if (numChannels < 3) {
865             csType.setAttribute("name", "GRAY");
866             if (numChannels == 2) {
867                 hasAlpha = true;
868             }
869             return chroma;
870         }
871 
872         boolean idsAreJFIF = true;
873 
874         for (int i = 0; i < sof.componentSpecs.length; i++) {
875             int id = sof.componentSpecs[i].componentId;
876             if ((id < 1) || (id >= sof.componentSpecs.length)) {
877                 idsAreJFIF = false;
878             }
879         }
880 
881         if (idsAreJFIF) {
882             csType.setAttribute("name", "YCbCr");
883             if (numChannels == 4) {
884                 hasAlpha = true;
885             }
886             return chroma;
887         }
888 
889         // Check against the letters
890         if ((sof.componentSpecs[0].componentId == 'R')
891             && (sof.componentSpecs[1].componentId == 'G')
892             && (sof.componentSpecs[2].componentId == 'B')){
893 
894             csType.setAttribute("name", "RGB");
895             if ((numChannels == 4)
896                 && (sof.componentSpecs[3].componentId == 'A')) {
897                 hasAlpha = true;
898             }
899             return chroma;
900         }
901 
902         if ((sof.componentSpecs[0].componentId == 'Y')
903             && (sof.componentSpecs[1].componentId == 'C')
904             && (sof.componentSpecs[2].componentId == 'c')){
905 
906             csType.setAttribute("name", "PhotoYCC");
907             if ((numChannels == 4)
908                 && (sof.componentSpecs[3].componentId == 'A')) {
909                 hasAlpha = true;
910             }
911             return chroma;
912         }
913 
914         // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
915         // 4-channel subsampled are YCbCrA, unsubsampled are CMYK
916 
917         boolean subsampled = false;
918 
919         int hfactor = sof.componentSpecs[0].HsamplingFactor;
920         int vfactor = sof.componentSpecs[0].VsamplingFactor;
921 
922         for (int i = 1; i<sof.componentSpecs.length; i++) {
923             if ((sof.componentSpecs[i].HsamplingFactor != hfactor)
924                 || (sof.componentSpecs[i].VsamplingFactor != vfactor)){
925                 subsampled = true;
926                 break;
927             }
928         }
929 
930         if (subsampled) {
931             csType.setAttribute("name", "YCbCr");
932             if (numChannels == 4) {
933                 hasAlpha = true;
934             }
935             return chroma;
936         }
937 
938         // Not subsampled.  numChannels < 3 is taken care of above
939         if (numChannels == 3) {
940             csType.setAttribute("name", "RGB");
941         } else {
942             csType.setAttribute("name", "CMYK");
943         }
944 
945         return chroma;
946     }
947 
948     protected IIOMetadataNode getStandardCompressionNode() {
949 
950         IIOMetadataNode compression = new IIOMetadataNode("Compression");
951 
952         // CompressionTypeName
953         IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
954         name.setAttribute("value", "JPEG");
955         compression.appendChild(name);
956 
957         // Lossless - false
958         IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
959         lossless.setAttribute("value", "FALSE");
960         compression.appendChild(lossless);
961 
962         // NumProgressiveScans - count sos segments
963         int sosCount = 0;
964         Iterator iter = markerSequence.iterator();
965         while (iter.hasNext()) {
966             MarkerSegment ms = (MarkerSegment) iter.next();
967             if (ms.tag == JPEG.SOS) {
968                 sosCount++;
969             }
970         }
971         if (sosCount != 0) {
972             IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans");
973             prog.setAttribute("value", Integer.toString(sosCount));
974             compression.appendChild(prog);
975         }
976 
977         return compression;
978     }
979 
980     protected IIOMetadataNode getStandardDimensionNode() {
981         // If we have a JFIF marker segment, we know a little
982         // otherwise all we know is the orientation, which is always normal
983         IIOMetadataNode dim = new IIOMetadataNode("Dimension");
984         IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
985         orient.setAttribute("value", "normal");
986         dim.appendChild(orient);
987 
988         JFIFMarkerSegment jfif =
989             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
990         if (jfif != null) {
991 
992             // Aspect Ratio is width of pixel / height of pixel
993             float aspectRatio;
994             if (jfif.resUnits == 0) {
995                 // In this case they just encode aspect ratio directly
996                 aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;
997             } else {
998                 // They are true densities (e.g. dpi) and must be inverted
999                 aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;
1000             }
1001             IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
1002             aspect.setAttribute("value", Float.toString(aspectRatio));
1003             dim.insertBefore(aspect, orient);
1004 
1005             // Pixel size
1006             if (jfif.resUnits != 0) {
1007                 // 1 == dpi, 2 == dpc
1008                 float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F;
1009 
1010                 IIOMetadataNode horiz =
1011                     new IIOMetadataNode("HorizontalPixelSize");
1012                 horiz.setAttribute("value",
1013                                    Float.toString(scale/jfif.Xdensity));
1014                 dim.appendChild(horiz);
1015 
1016                 IIOMetadataNode vert =
1017                     new IIOMetadataNode("VerticalPixelSize");
1018                 vert.setAttribute("value",
1019                                   Float.toString(scale/jfif.Ydensity));
1020                 dim.appendChild(vert);
1021             }
1022         }
1023         return dim;
1024     }
1025 
1026     protected IIOMetadataNode getStandardTextNode() {
1027         IIOMetadataNode text = null;
1028         // Add a text entry for each COM Marker Segment
1029         if (findMarkerSegment(JPEG.COM) != null) {
1030             text = new IIOMetadataNode("Text");
1031             Iterator iter = markerSequence.iterator();
1032             while (iter.hasNext()) {
1033                 MarkerSegment seg = (MarkerSegment) iter.next();
1034                 if (seg.tag == JPEG.COM) {
1035                     COMMarkerSegment com = (COMMarkerSegment) seg;
1036                     IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
1037                     entry.setAttribute("keyword", "comment");
1038                     entry.setAttribute("value", com.getComment());
1039                 text.appendChild(entry);
1040                 }
1041             }
1042         }
1043         return text;
1044     }
1045 
1046     protected IIOMetadataNode getStandardTransparencyNode() {
1047         IIOMetadataNode trans = null;
1048         if (hasAlpha == true) {
1049             trans = new IIOMetadataNode("Transparency");
1050             IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
1051             alpha.setAttribute("value", "nonpremultiplied"); // Always assume
1052             trans.appendChild(alpha);
1053         }
1054         return trans;
1055     }
1056 
1057     // Editing
1058 
1059     public boolean isReadOnly() {
1060         return false;
1061     }
1062 
1063     public void mergeTree(String formatName, Node root)
1064         throws IIOInvalidTreeException {
1065         if (formatName == null) {
1066             throw new IllegalArgumentException("null formatName!");
1067         }
1068         if (root == null) {
1069             throw new IllegalArgumentException("null root!");
1070         }
1071         List copy = null;
1072         if (resetSequence == null) {
1073             resetSequence = cloneSequence();  // Deep copy
1074             copy = resetSequence;  // Avoid cloning twice
1075         } else {
1076             copy = cloneSequence();
1077         }
1078         if (isStream &&
1079             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
1080                 mergeNativeTree(root);
1081         } else if (!isStream &&
1082                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
1083             mergeNativeTree(root);
1084         } else if (!isStream &&
1085                    (formatName.equals
1086                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
1087             mergeStandardTree(root);
1088         } else {
1089             throw  new IllegalArgumentException("Unsupported format name: "
1090                                                 + formatName);
1091         }
1092         if (!isConsistent()) {
1093             markerSequence = copy;
1094             throw new IIOInvalidTreeException
1095                 ("Merged tree is invalid; original restored", root);
1096         }
1097     }
1098 
1099     private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
1100         String name = root.getNodeName();
1101         if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
1102                                 : JPEG.nativeImageMetadataFormatName)) {
1103             throw new IIOInvalidTreeException("Invalid root node name: " + name,
1104                                               root);
1105         }
1106         if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
1107             throw new IIOInvalidTreeException(
1108                 "JPEGvariety and markerSequence nodes must be present", root);
1109         }
1110         mergeJFIFsubtree(root.getFirstChild());
1111         mergeSequenceSubtree(root.getLastChild());
1112     }
1113 
1114     /**
1115      * Merge a JFIF subtree into the marker sequence, if the subtree
1116      * is non-empty.
1117      * If a JFIF marker exists, update it from the subtree.
1118      * If none exists, create one from the subtree and insert it at the
1119      * beginning of the marker sequence.
1120      */
1121     private void mergeJFIFsubtree(Node JPEGvariety)
1122         throws IIOInvalidTreeException {
1123         if (JPEGvariety.getChildNodes().getLength() != 0) {
1124             Node jfifNode = JPEGvariety.getFirstChild();
1125             // is there already a jfif marker segment?
1126             JFIFMarkerSegment jfifSeg =
1127                 (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1128             if (jfifSeg != null) {
1129                 jfifSeg.updateFromNativeNode(jfifNode, false);
1130             } else {
1131                 // Add it as the first element in the list.
1132                 markerSequence.add(0, new JFIFMarkerSegment(jfifNode));
1133             }
1134         }
1135     }
1136 
1137     private void mergeSequenceSubtree(Node sequenceTree)
1138         throws IIOInvalidTreeException {
1139         NodeList children = sequenceTree.getChildNodes();
1140         for (int i = 0; i < children.getLength(); i++) {
1141             Node node = children.item(i);
1142             String name = node.getNodeName();
1143             if (name.equals("dqt")) {
1144                 mergeDQTNode(node);
1145             } else if (name.equals("dht")) {
1146                 mergeDHTNode(node);
1147             } else if (name.equals("dri")) {
1148                 mergeDRINode(node);
1149             } else if (name.equals("com")) {
1150                 mergeCOMNode(node);
1151             } else if (name.equals("app14Adobe")) {
1152                 mergeAdobeNode(node);
1153             } else if (name.equals("unknown")) {
1154                 mergeUnknownNode(node);
1155             } else if (name.equals("sof")) {
1156                 mergeSOFNode(node);
1157             } else if (name.equals("sos")) {
1158                 mergeSOSNode(node);
1159             } else {
1160                 throw new IIOInvalidTreeException("Invalid node: " + name, node);
1161             }
1162         }
1163     }
1164 
1165     /**
1166      * Merge the given DQT node into the marker sequence.  If there already
1167      * exist DQT marker segments in the sequence, then each table in the
1168      * node replaces the first table, in any DQT segment, with the same
1169      * table id.  If none of the existing DQT segments contain a table with
1170      * the same id, then the table is added to the last existing DQT segment.
1171      * If there are no DQT segments, then a new one is created and added
1172      * as follows:
1173      * If there are DHT segments, the new DQT segment is inserted before the
1174      * first one.
1175      * If there are no DHT segments, the new DQT segment is inserted before
1176      * an SOF segment, if there is one.
1177      * If there is no SOF segment, the new DQT segment is inserted before
1178      * the first SOS segment, if there is one.
1179      * If there is no SOS segment, the new DQT segment is added to the end
1180      * of the sequence.
1181      */
1182     private void mergeDQTNode(Node node) throws IIOInvalidTreeException {
1183         // First collect any existing DQT nodes into a local list
1184         ArrayList oldDQTs = new ArrayList();
1185         Iterator iter = markerSequence.iterator();
1186         while (iter.hasNext()) {
1187             MarkerSegment seg = (MarkerSegment) iter.next();
1188             if (seg instanceof DQTMarkerSegment) {
1189                 oldDQTs.add(seg);
1190             }
1191         }
1192         if (!oldDQTs.isEmpty()) {
1193             NodeList children = node.getChildNodes();
1194             for (int i = 0; i < children.getLength(); i++) {
1195                 Node child = children.item(i);
1196                 int childID = MarkerSegment.getAttributeValue(child,
1197                                                               null,
1198                                                               "qtableId",
1199                                                               0, 3,
1200                                                               true);
1201                 DQTMarkerSegment dqt = null;
1202                 int tableIndex = -1;
1203                 for (int j = 0; j < oldDQTs.size(); j++) {
1204                     DQTMarkerSegment testDQT = (DQTMarkerSegment) oldDQTs.get(j);
1205                     for (int k = 0; k < testDQT.tables.size(); k++) {
1206                         DQTMarkerSegment.Qtable testTable =
1207                             (DQTMarkerSegment.Qtable) testDQT.tables.get(k);
1208                         if (childID == testTable.tableID) {
1209                             dqt = testDQT;
1210                             tableIndex = k;
1211                             break;
1212                         }
1213                     }
1214                     if (dqt != null) break;
1215                 }
1216                 if (dqt != null) {
1217                     dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
1218                 } else {
1219                     dqt = (DQTMarkerSegment) oldDQTs.get(oldDQTs.size()-1);
1220                     dqt.tables.add(dqt.getQtableFromNode(child));
1221                 }
1222             }
1223         } else {
1224             DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
1225             int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
1226             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1227             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1228             if (firstDHT != -1) {
1229                 markerSequence.add(firstDHT, newGuy);
1230             } else if (firstSOF != -1) {
1231                 markerSequence.add(firstSOF, newGuy);
1232             } else if (firstSOS != -1) {
1233                 markerSequence.add(firstSOS, newGuy);
1234             } else {
1235                 markerSequence.add(newGuy);
1236             }
1237         }
1238     }
1239 
1240     /**
1241      * Merge the given DHT node into the marker sequence.  If there already
1242      * exist DHT marker segments in the sequence, then each table in the
1243      * node replaces the first table, in any DHT segment, with the same
1244      * table class and table id.  If none of the existing DHT segments contain
1245      * a table with the same class and id, then the table is added to the last
1246      * existing DHT segment.
1247      * If there are no DHT segments, then a new one is created and added
1248      * as follows:
1249      * If there are DQT segments, the new DHT segment is inserted immediately
1250      * following the last DQT segment.
1251      * If there are no DQT segments, the new DHT segment is inserted before
1252      * an SOF segment, if there is one.
1253      * If there is no SOF segment, the new DHT segment is inserted before
1254      * the first SOS segment, if there is one.
1255      * If there is no SOS segment, the new DHT segment is added to the end
1256      * of the sequence.
1257      */
1258     private void mergeDHTNode(Node node) throws IIOInvalidTreeException {
1259         // First collect any existing DQT nodes into a local list
1260         ArrayList oldDHTs = new ArrayList();
1261         Iterator iter = markerSequence.iterator();
1262         while (iter.hasNext()) {
1263             MarkerSegment seg = (MarkerSegment) iter.next();
1264             if (seg instanceof DHTMarkerSegment) {
1265                 oldDHTs.add(seg);
1266             }
1267         }
1268         if (!oldDHTs.isEmpty()) {
1269             NodeList children = node.getChildNodes();
1270             for (int i = 0; i < children.getLength(); i++) {
1271                 Node child = children.item(i);
1272                 NamedNodeMap attrs = child.getAttributes();
1273                 int childID = MarkerSegment.getAttributeValue(child,
1274                                                               attrs,
1275                                                               "htableId",
1276                                                               0, 3,
1277                                                               true);
1278                 int childClass = MarkerSegment.getAttributeValue(child,
1279                                                                  attrs,
1280                                                                  "class",
1281                                                                  0, 1,
1282                                                                  true);
1283                 DHTMarkerSegment dht = null;
1284                 int tableIndex = -1;
1285                 for (int j = 0; j < oldDHTs.size(); j++) {
1286                     DHTMarkerSegment testDHT = (DHTMarkerSegment) oldDHTs.get(j);
1287                     for (int k = 0; k < testDHT.tables.size(); k++) {
1288                         DHTMarkerSegment.Htable testTable =
1289                             (DHTMarkerSegment.Htable) testDHT.tables.get(k);
1290                         if ((childID == testTable.tableID) &&
1291                             (childClass == testTable.tableClass)) {
1292                             dht = testDHT;
1293                             tableIndex = k;
1294                             break;
1295                         }
1296                     }
1297                     if (dht != null) break;
1298                 }
1299                 if (dht != null) {
1300                     dht.tables.set(tableIndex, dht.getHtableFromNode(child));
1301                 } else {
1302                     dht = (DHTMarkerSegment) oldDHTs.get(oldDHTs.size()-1);
1303                     dht.tables.add(dht.getHtableFromNode(child));
1304                 }
1305             }
1306         } else {
1307             DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
1308             int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
1309             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1310             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1311             if (lastDQT != -1) {
1312                 markerSequence.add(lastDQT+1, newGuy);
1313             } else if (firstSOF != -1) {
1314                 markerSequence.add(firstSOF, newGuy);
1315             } else if (firstSOS != -1) {
1316                 markerSequence.add(firstSOS, newGuy);
1317             } else {
1318                 markerSequence.add(newGuy);
1319             }
1320         }
1321     }
1322 
1323     /**
1324      * Merge the given DRI node into the marker sequence.
1325      * If there already exists a DRI marker segment, the restart interval
1326      * value is updated.
1327      * If there is no DRI segment, then a new one is created and added as
1328      * follows:
1329      * If there is an SOF segment, the new DRI segment is inserted before
1330      * it.
1331      * If there is no SOF segment, the new DRI segment is inserted before
1332      * the first SOS segment, if there is one.
1333      * If there is no SOS segment, the new DRI segment is added to the end
1334      * of the sequence.
1335      */
1336     private void mergeDRINode(Node node) throws IIOInvalidTreeException {
1337         DRIMarkerSegment dri =
1338             (DRIMarkerSegment) findMarkerSegment(DRIMarkerSegment.class, true);
1339         if (dri != null) {
1340             dri.updateFromNativeNode(node, false);
1341         } else {
1342             DRIMarkerSegment newGuy = new DRIMarkerSegment(node);
1343             int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
1344             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1345             if (firstSOF != -1) {
1346                 markerSequence.add(firstSOF, newGuy);
1347             } else if (firstSOS != -1) {
1348                 markerSequence.add(firstSOS, newGuy);
1349             } else {
1350                 markerSequence.add(newGuy);
1351             }
1352         }
1353     }
1354 
1355     /**
1356      * Merge the given COM node into the marker sequence.
1357      * A new COM marker segment is created and added to the sequence
1358      * using insertCOMMarkerSegment.
1359      */
1360     private void mergeCOMNode(Node node) throws IIOInvalidTreeException {
1361         COMMarkerSegment newGuy = new COMMarkerSegment(node);
1362         insertCOMMarkerSegment(newGuy);
1363     }
1364 
1365      /**
1366       * Insert a new COM marker segment into an appropriate place in the
1367       * marker sequence, as follows:
1368       * If there already exist COM marker segments, the new one is inserted
1369       * after the last one.
1370       * If there are no COM segments, the new COM segment is inserted after the
1371       * JFIF segment, if there is one.
1372       * If there is no JFIF segment, the new COM segment is inserted after the
1373       * Adobe marker segment, if there is one.
1374       * If there is no Adobe segment, the new COM segment is inserted
1375       * at the beginning of the sequence.
1376       */
1377     private void insertCOMMarkerSegment(COMMarkerSegment newGuy) {
1378         int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false);
1379         boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1380         int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1381         if (lastCOM != -1) {
1382             markerSequence.add(lastCOM+1, newGuy);
1383         } else if (hasJFIF) {
1384             markerSequence.add(1, newGuy);  // JFIF is always 0
1385         } else if (firstAdobe != -1) {
1386             markerSequence.add(firstAdobe+1, newGuy);
1387         } else {
1388             markerSequence.add(0, newGuy);
1389         }
1390     }
1391 
1392     /**
1393      * Merge the given Adobe APP14 node into the marker sequence.
1394      * If there already exists an Adobe marker segment, then its attributes
1395      * are updated from the node.
1396      * If there is no Adobe segment, then a new one is created and added
1397      * using insertAdobeMarkerSegment.
1398      */
1399     private void mergeAdobeNode(Node node) throws IIOInvalidTreeException {
1400         AdobeMarkerSegment adobe =
1401             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1402         if (adobe != null) {
1403             adobe.updateFromNativeNode(node, false);
1404         } else {
1405             AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node);
1406             insertAdobeMarkerSegment(newGuy);
1407         }
1408     }
1409 
1410     /**
1411      * Insert the given AdobeMarkerSegment into the marker sequence, as
1412      * follows (we assume there is no Adobe segment yet):
1413      * If there is a JFIF segment, then the new Adobe segment is inserted
1414      * after it.
1415      * If there is no JFIF segment, the new Adobe segment is inserted after the
1416      * last Unknown segment, if there are any.
1417      * If there are no Unknown segments, the new Adobe segment is inserted
1418      * at the beginning of the sequence.
1419      */
1420     private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) {
1421         boolean hasJFIF =
1422             (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1423         int lastUnknown = findLastUnknownMarkerSegmentPosition();
1424         if (hasJFIF) {
1425             markerSequence.add(1, newGuy);  // JFIF is always 0
1426         } else if (lastUnknown != -1) {
1427             markerSequence.add(lastUnknown+1, newGuy);
1428         } else {
1429             markerSequence.add(0, newGuy);
1430         }
1431     }
1432 
1433     /**
1434      * Merge the given Unknown node into the marker sequence.
1435      * A new Unknown marker segment is created and added to the sequence as
1436      * follows:
1437      * If there already exist Unknown marker segments, the new one is inserted
1438      * after the last one.
1439      * If there are no Unknown marker segments, the new Unknown marker segment
1440      * is inserted after the JFIF segment, if there is one.
1441      * If there is no JFIF segment, the new Unknown segment is inserted before
1442      * the Adobe marker segment, if there is one.
1443      * If there is no Adobe segment, the new Unknown segment is inserted
1444      * at the beginning of the sequence.
1445      */
1446     private void mergeUnknownNode(Node node) throws IIOInvalidTreeException {
1447         MarkerSegment newGuy = new MarkerSegment(node);
1448         int lastUnknown = findLastUnknownMarkerSegmentPosition();
1449         boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
1450         int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
1451         if (lastUnknown != -1) {
1452             markerSequence.add(lastUnknown+1, newGuy);
1453         } else if (hasJFIF) {
1454             markerSequence.add(1, newGuy);  // JFIF is always 0
1455         } if (firstAdobe != -1) {
1456             markerSequence.add(firstAdobe, newGuy);
1457         } else {
1458             markerSequence.add(0, newGuy);
1459         }
1460     }
1461 
1462     /**
1463      * Merge the given SOF node into the marker sequence.
1464      * If there already exists an SOF marker segment in the sequence, then
1465      * its values are updated from the node.
1466      * If there is no SOF segment, then a new one is created and added as
1467      * follows:
1468      * If there are any SOS segments, the new SOF segment is inserted before
1469      * the first one.
1470      * If there is no SOS segment, the new SOF segment is added to the end
1471      * of the sequence.
1472      *
1473      */
1474     private void mergeSOFNode(Node node) throws IIOInvalidTreeException {
1475         SOFMarkerSegment sof =
1476             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1477         if (sof != null) {
1478             sof.updateFromNativeNode(node, false);
1479         } else {
1480             SOFMarkerSegment newGuy = new SOFMarkerSegment(node);
1481             int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
1482             if (firstSOS != -1) {
1483                 markerSequence.add(firstSOS, newGuy);
1484             } else {
1485                 markerSequence.add(newGuy);
1486             }
1487         }
1488     }
1489 
1490     /**
1491      * Merge the given SOS node into the marker sequence.
1492      * If there already exists a single SOS marker segment, then the values
1493      * are updated from the node.
1494      * If there are more than one existing SOS marker segments, then an
1495      * IIOInvalidTreeException is thrown, as SOS segments cannot be merged
1496      * into a set of progressive scans.
1497      * If there are no SOS marker segments, a new one is created and added
1498      * to the end of the sequence.
1499      */
1500     private void mergeSOSNode(Node node) throws IIOInvalidTreeException {
1501         SOSMarkerSegment firstSOS =
1502             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1503         SOSMarkerSegment lastSOS =
1504             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, false);
1505         if (firstSOS != null) {
1506             if (firstSOS != lastSOS) {
1507                 throw new IIOInvalidTreeException
1508                     ("Can't merge SOS node into a tree with > 1 SOS node", node);
1509             }
1510             firstSOS.updateFromNativeNode(node, false);
1511         } else {
1512             markerSequence.add(new SOSMarkerSegment(node));
1513         }
1514     }
1515 
1516     private boolean transparencyDone;
1517 
1518     private void mergeStandardTree(Node root) throws IIOInvalidTreeException {
1519         transparencyDone = false;
1520         NodeList children = root.getChildNodes();
1521         for (int i = 0; i < children.getLength(); i++) {
1522             Node node = children.item(i);
1523             String name = node.getNodeName();
1524             if (name.equals("Chroma")) {
1525                 mergeStandardChromaNode(node, children);
1526             } else if (name.equals("Compression")) {
1527                 mergeStandardCompressionNode(node);
1528             } else if (name.equals("Data")) {
1529                 mergeStandardDataNode(node);
1530             } else if (name.equals("Dimension")) {
1531                 mergeStandardDimensionNode(node);
1532             } else if (name.equals("Document")) {
1533                 mergeStandardDocumentNode(node);
1534             } else if (name.equals("Text")) {
1535                 mergeStandardTextNode(node);
1536             } else if (name.equals("Transparency")) {
1537                 mergeStandardTransparencyNode(node);
1538             } else {
1539                 throw new IIOInvalidTreeException("Invalid node: " + name, node);
1540             }
1541         }
1542     }
1543 
1544     /*
1545      * In general, it could be possible to convert all non-pixel data to some
1546      * textual form and include it in comments, but then this would create the
1547      * expectation that these comment forms be recognized by the reader, thus
1548      * creating a defacto extension to JPEG metadata capabilities.  This is
1549      * probably best avoided, so the following convert only text nodes to
1550      * comments, and lose the keywords as well.
1551      */
1552 
1553     private void mergeStandardChromaNode(Node node, NodeList siblings)
1554         throws IIOInvalidTreeException {
1555         // ColorSpaceType can change the target colorspace for compression
1556         // This must take any transparency node into account as well, as
1557         // that affects the number of channels (if alpha is present).  If
1558         // a transparency node is dealt with here, set a flag to indicate
1559         // this to the transparency processor below.  If we discover that
1560         // the nodes are not in order, throw an exception as the tree is
1561         // invalid.
1562 
1563         if (transparencyDone) {
1564             throw new IIOInvalidTreeException
1565                 ("Transparency node must follow Chroma node", node);
1566         }
1567 
1568         Node csType = node.getFirstChild();
1569         if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) {
1570             // If there is no ColorSpaceType node, we have nothing to do
1571             return;
1572         }
1573 
1574         String csName = csType.getAttributes().getNamedItem("name").getNodeValue();
1575 
1576         int numChannels = 0;
1577         boolean wantJFIF = false;
1578         boolean wantAdobe = false;
1579         int transform = 0;
1580         boolean willSubsample = false;
1581         byte [] ids = {1, 2, 3, 4};  // JFIF compatible
1582         if (csName.equals("GRAY")) {
1583             numChannels = 1;
1584             wantJFIF = true;
1585         } else if (csName.equals("YCbCr")) {
1586             numChannels = 3;
1587             wantJFIF = true;
1588             willSubsample = true;
1589         } else if (csName.equals("PhotoYCC")) {
1590             numChannels = 3;
1591             wantAdobe = true;
1592             transform = JPEG.ADOBE_YCC;
1593             ids[0] = (byte) 'Y';
1594             ids[1] = (byte) 'C';
1595             ids[2] = (byte) 'c';
1596         } else if (csName.equals("RGB")) {
1597             numChannels = 3;
1598             wantAdobe = true;
1599             transform = JPEG.ADOBE_UNKNOWN;
1600             ids[0] = (byte) 'R';
1601             ids[1] = (byte) 'G';
1602             ids[2] = (byte) 'B';
1603         } else if ((csName.equals("XYZ"))
1604                    || (csName.equals("Lab"))
1605                    || (csName.equals("Luv"))
1606                    || (csName.equals("YxY"))
1607                    || (csName.equals("HSV"))
1608                    || (csName.equals("HLS"))
1609                    || (csName.equals("CMY"))
1610                    || (csName.equals("3CLR"))) {
1611             numChannels = 3;
1612         } else if (csName.equals("YCCK")) {
1613             numChannels = 4;
1614             wantAdobe = true;
1615             transform = JPEG.ADOBE_YCCK;
1616             willSubsample = true;
1617         } else if (csName.equals("CMYK")) {
1618             numChannels = 4;
1619             wantAdobe = true;
1620             transform = JPEG.ADOBE_UNKNOWN;
1621         } else if (csName.equals("4CLR")) {
1622             numChannels = 4;
1623         } else { // We can't handle them, so don't modify any metadata
1624             return;
1625         }
1626 
1627         boolean wantAlpha = false;
1628         for (int i = 0; i < siblings.getLength(); i++) {
1629             Node trans = siblings.item(i);
1630             if (trans.getNodeName().equals("Transparency")) {
1631                 wantAlpha = wantAlpha(trans);
1632                 break;  // out of for
1633             }
1634         }
1635 
1636         if (wantAlpha) {
1637             numChannels++;
1638             wantJFIF = false;
1639             if (ids[0] == (byte) 'R') {
1640                 ids[3] = (byte) 'A';
1641                 wantAdobe = false;
1642             }
1643         }
1644 
1645         JFIFMarkerSegment jfif =
1646             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1647         AdobeMarkerSegment adobe =
1648             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
1649         SOFMarkerSegment sof =
1650             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1651         SOSMarkerSegment sos =
1652             (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
1653 
1654         // If the metadata specifies progressive, then the number of channels
1655         // must match, so that we can modify all the existing SOS marker segments.
1656         // If they don't match, we don't know what to do with SOS so we can't do
1657         // the merge.  We then just return silently.
1658         // An exception would not be appropriate.  A warning might, but we have
1659         // nowhere to send it to.
1660         if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
1661             if ((sof.componentSpecs.length != numChannels) && (sos != null)) {
1662                 return;
1663             }
1664         }
1665 
1666         // JFIF header might be removed
1667         if (!wantJFIF && (jfif != null)) {
1668             markerSequence.remove(jfif);
1669         }
1670 
1671         // Now add a JFIF if we do want one, but only if it isn't stream metadata
1672         if (wantJFIF && !isStream) {
1673             markerSequence.add(0, new JFIFMarkerSegment());
1674         }
1675 
1676         // Adobe header might be removed or the transform modified, if it isn't
1677         // stream metadata
1678         if (wantAdobe) {
1679             if ((adobe == null) && !isStream) {
1680                 adobe = new AdobeMarkerSegment(transform);
1681                 insertAdobeMarkerSegment(adobe);
1682             } else {
1683                 adobe.transform = transform;
1684             }
1685         } else if (adobe != null) {
1686             markerSequence.remove(adobe);
1687         }
1688 
1689         boolean updateQtables = false;
1690         boolean updateHtables = false;
1691 
1692         boolean progressive = false;
1693 
1694         int [] subsampledSelectors = {0, 1, 1, 0 } ;
1695         int [] nonSubsampledSelectors = { 0, 0, 0, 0};
1696 
1697         int [] newTableSelectors = willSubsample
1698                                    ? subsampledSelectors
1699                                    : nonSubsampledSelectors;
1700 
1701         // Keep the old componentSpecs array
1702         SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;
1703         // SOF might be modified
1704         if (sof != null) {
1705             oldCompSpecs = sof.componentSpecs;
1706             progressive = (sof.tag == JPEG.SOF2);
1707             // Now replace the SOF with a new one; it might be the same, but
1708             // this is easier.
1709             markerSequence.set(markerSequence.indexOf(sof),
1710                                new SOFMarkerSegment(progressive,
1711                                                     false, // we never need extended
1712                                                     willSubsample,
1713                                                     ids,
1714                                                     numChannels));
1715 
1716             // Now suss out if subsampling changed and set the boolean for
1717             // updating the q tables
1718             // if the old componentSpec q table selectors don't match
1719             // the new ones, update the qtables.  The new selectors are already
1720             // in place in the new SOF segment above.
1721             for (int i = 0; i < oldCompSpecs.length; i++) {
1722                 if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
1723                     updateQtables = true;
1724                 }
1725             }
1726 
1727             if (progressive) {
1728                 // if the component ids are different, update all the existing scans
1729                 // ignore Huffman tables
1730                 boolean idsDiffer = false;
1731                 for (int i = 0; i < oldCompSpecs.length; i++) {
1732                     if (ids[i] != oldCompSpecs[i].componentId) {
1733                         idsDiffer = true;
1734                     }
1735                 }
1736                 if (idsDiffer) {
1737                     // update the ids in each SOS marker segment
1738                     for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1739                         MarkerSegment seg = (MarkerSegment) iter.next();
1740                         if (seg instanceof SOSMarkerSegment) {
1741                             SOSMarkerSegment target = (SOSMarkerSegment) seg;
1742                             for (int i = 0; i < target.componentSpecs.length; i++) {
1743                                 int oldSelector =
1744                                     target.componentSpecs[i].componentSelector;
1745                                 // Find the position in the old componentSpecs array
1746                                 // of the old component with the old selector
1747                                 // and replace the component selector with the
1748                                 // new id at the same position, as these match
1749                                 // the new component specs array in the SOF created
1750                                 // above.
1751                                 for (int j = 0; j < oldCompSpecs.length; j++) {
1752                                     if (oldCompSpecs[j].componentId == oldSelector) {
1753                                         target.componentSpecs[i].componentSelector =
1754                                             ids[j];
1755                                     }
1756                                 }
1757                             }
1758                         }
1759                     }
1760                 }
1761             } else {
1762                 if (sos != null) {
1763                     // htables - if the old htable selectors don't match the new ones,
1764                     // update the tables.
1765                     for (int i = 0; i < sos.componentSpecs.length; i++) {
1766                         if ((sos.componentSpecs[i].dcHuffTable
1767                              != newTableSelectors[i])
1768                             || (sos.componentSpecs[i].acHuffTable
1769                                 != newTableSelectors[i])) {
1770                             updateHtables = true;
1771                         }
1772                     }
1773 
1774                     // Might be the same as the old one, but this is easier.
1775                     markerSequence.set(markerSequence.indexOf(sos),
1776                                new SOSMarkerSegment(willSubsample,
1777                                                     ids,
1778                                                     numChannels));
1779                 }
1780             }
1781         } else {
1782             // should be stream metadata if there isn't an SOF, but check it anyway
1783             if (isStream) {
1784                 // update tables - routines below check if it's really necessary
1785                 updateQtables = true;
1786                 updateHtables = true;
1787             }
1788         }
1789 
1790         if (updateQtables) {
1791             List tableSegments = new ArrayList();
1792             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1793                 MarkerSegment seg = (MarkerSegment) iter.next();
1794                 if (seg instanceof DQTMarkerSegment) {
1795                     tableSegments.add(seg);
1796                 }
1797             }
1798             // If there are no tables, don't add them, as the metadata encodes an
1799             // abbreviated stream.
1800             // If we are not subsampling, we just need one, so don't do anything
1801             if (!tableSegments.isEmpty() && willSubsample) {
1802                 // Is it really necessary?  There should be at least 2 tables.
1803                 // If there is only one, assume it's a scaled "standard"
1804                 // luminance table, extract the scaling factor, and generate a
1805                 // scaled "standard" chrominance table.
1806 
1807                 // Find the table with selector 1.
1808                 boolean found = false;
1809                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1810                     DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1811                     for (Iterator tabiter = testdqt.tables.iterator();
1812                          tabiter.hasNext();) {
1813                         DQTMarkerSegment.Qtable tab =
1814                             (DQTMarkerSegment.Qtable) tabiter.next();
1815                         if (tab.tableID == 1) {
1816                             found = true;
1817                         }
1818                     }
1819                 }
1820                 if (!found) {
1821                     //    find the table with selector 0.  There should be one.
1822                     DQTMarkerSegment.Qtable table0 = null;
1823                     for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1824                         DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
1825                         for (Iterator tabiter = testdqt.tables.iterator();
1826                              tabiter.hasNext();) {
1827                             DQTMarkerSegment.Qtable tab =
1828                                 (DQTMarkerSegment.Qtable) tabiter.next();
1829                             if (tab.tableID == 0) {
1830                                 table0 = tab;
1831                             }
1832                         }
1833                     }
1834 
1835                     // Assuming that the table with id 0 is a luminance table,
1836                     // compute a new chrominance table of the same quality and
1837                     // add it to the last DQT segment
1838                     DQTMarkerSegment dqt =
1839                         (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1840                     dqt.tables.add(dqt.getChromaForLuma(table0));
1841                 }
1842             }
1843         }
1844 
1845         if (updateHtables) {
1846             List tableSegments = new ArrayList();
1847             for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
1848                 MarkerSegment seg = (MarkerSegment) iter.next();
1849                 if (seg instanceof DHTMarkerSegment) {
1850                     tableSegments.add(seg);
1851                 }
1852             }
1853             // If there are no tables, don't add them, as the metadata encodes an
1854             // abbreviated stream.
1855             // If we are not subsampling, we just need one, so don't do anything
1856             if (!tableSegments.isEmpty() && willSubsample) {
1857                 // Is it really necessary?  There should be at least 2 dc and 2 ac
1858                 // tables.  If there is only one, add a
1859                 // "standard " chrominance table.
1860 
1861                 // find a table with selector 1. AC/DC is irrelevant
1862                 boolean found = false;
1863                 for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
1864                     DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();
1865                     for (Iterator tabiter = testdht.tables.iterator();
1866                          tabiter.hasNext();) {
1867                         DHTMarkerSegment.Htable tab =
1868                             (DHTMarkerSegment.Htable) tabiter.next();
1869                         if (tab.tableID == 1) {
1870                             found = true;
1871                         }
1872                     }
1873                 }
1874                 if (!found) {
1875                     // Create new standard dc and ac chrominance tables and add them
1876                     // to the last DHT segment
1877                     DHTMarkerSegment lastDHT =
1878                         (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
1879                     lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
1880                     lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
1881                 }
1882             }
1883         }
1884     }
1885 
1886     private boolean wantAlpha(Node transparency) {
1887         boolean returnValue = false;
1888         Node alpha = transparency.getFirstChild();  // Alpha must be first if present
1889         if (alpha.getNodeName().equals("Alpha")) {
1890             if (alpha.hasAttributes()) {
1891                 String value =
1892                     alpha.getAttributes().getNamedItem("value").getNodeValue();
1893                 if (!value.equals("none")) {
1894                     returnValue = true;
1895                 }
1896             }
1897         }
1898         transparencyDone = true;
1899         return returnValue;
1900     }
1901 
1902     private void mergeStandardCompressionNode(Node node)
1903         throws IIOInvalidTreeException {
1904         // NumProgressiveScans is ignored.  Progression must be enabled on the
1905         // ImageWriteParam.
1906         // No-op
1907     }
1908 
1909     private void mergeStandardDataNode(Node node)
1910         throws IIOInvalidTreeException {
1911         // No-op
1912     }
1913 
1914     private void mergeStandardDimensionNode(Node node)
1915         throws IIOInvalidTreeException {
1916         // Pixel Aspect Ratio or pixel size can be incorporated if there is,
1917         // or can be, a JFIF segment
1918         JFIFMarkerSegment jfif =
1919             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
1920         if (jfif == null) {
1921             // Can there be one?
1922             // Criteria:
1923             // SOF must be present with 1 or 3 channels, (stream metadata fails this)
1924             //     Component ids must be JFIF compatible.
1925             boolean canHaveJFIF = false;
1926             SOFMarkerSegment sof =
1927                 (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
1928             if (sof != null) {
1929                 int numChannels = sof.componentSpecs.length;
1930                 if ((numChannels == 1) || (numChannels == 3)) {
1931                     canHaveJFIF = true; // remaining tests are negative
1932                     for (int i = 0; i < sof.componentSpecs.length; i++) {
1933                         if (sof.componentSpecs[i].componentId != i+1)
1934                             canHaveJFIF = false;
1935                     }
1936                     // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel,
1937                     //     ADOBE_YCC for 3-channel.
1938                     AdobeMarkerSegment adobe =
1939                         (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
1940                                                                true);
1941                     if (adobe != null) {
1942                         if (adobe.transform != ((numChannels == 1)
1943                                                 ? JPEG.ADOBE_UNKNOWN
1944                                                 : JPEG.ADOBE_YCC)) {
1945                             canHaveJFIF = false;
1946                         }
1947                     }
1948                 }
1949             }
1950             // If so, create one and insert it into the sequence.  Note that
1951             // default is just pixel ratio at 1:1
1952             if (canHaveJFIF) {
1953                 jfif = new JFIFMarkerSegment();
1954                 markerSequence.add(0, jfif);
1955             }
1956         }
1957         if (jfif != null) {
1958             NodeList children = node.getChildNodes();
1959             for (int i = 0; i < children.getLength(); i++) {
1960                 Node child = children.item(i);
1961                 NamedNodeMap attrs = child.getAttributes();
1962                 String name = child.getNodeName();
1963                 if (name.equals("PixelAspectRatio")) {
1964                     String valueString = attrs.getNamedItem("value").getNodeValue();
1965                     float value = Float.parseFloat(valueString);
1966                     Point p = findIntegerRatio(value);
1967                     jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
1968                     jfif.Xdensity = p.x;
1969                     jfif.Xdensity = p.y;
1970                 } else if (name.equals("HorizontalPixelSize")) {
1971                     String valueString = attrs.getNamedItem("value").getNodeValue();
1972                     float value = Float.parseFloat(valueString);
1973                     // Convert from mm/dot to dots/cm
1974                     int dpcm = (int) Math.round(1.0/(value*10.0));
1975                     jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1976                     jfif.Xdensity = dpcm;
1977                 } else if (name.equals("VerticalPixelSize")) {
1978                     String valueString = attrs.getNamedItem("value").getNodeValue();
1979                     float value = Float.parseFloat(valueString);
1980                     // Convert from mm/dot to dots/cm
1981                     int dpcm = (int) Math.round(1.0/(value*10.0));
1982                     jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
1983                     jfif.Ydensity = dpcm;
1984                 }
1985 
1986             }
1987         }
1988     }
1989 
1990     /*
1991      * Return a pair of integers whose ratio (x/y) approximates the given
1992      * float value.
1993      */
1994     private static Point findIntegerRatio(float value) {
1995         float epsilon = 0.005F;
1996 
1997         // Normalize
1998         value = Math.abs(value);
1999 
2000         // Deal with min case
2001         if (value <= epsilon) {
2002             return new Point(1, 255);
2003         }
2004 
2005         // Deal with max case
2006         if (value >= 255) {
2007             return new Point(255, 1);
2008         }
2009 
2010         // Remember if we invert
2011         boolean inverted = false;
2012         if (value < 1.0) {
2013             value = 1.0F/value;
2014             inverted = true;
2015         }
2016 
2017         // First approximation
2018         int y = 1;
2019         int x = (int) Math.round(value);
2020 
2021         float ratio = (float) x;
2022         float delta = Math.abs(value - ratio);
2023         while (delta > epsilon) { // not close enough
2024             // Increment y and compute a new x
2025             y++;
2026             x = (int) Math.round(y*value);
2027             ratio = (float)x/(float)y;
2028             delta = Math.abs(value - ratio);
2029         }
2030         return inverted ? new Point(y, x) : new Point(x, y);
2031     }
2032 
2033     private void mergeStandardDocumentNode(Node node)
2034         throws IIOInvalidTreeException {
2035         // No-op
2036     }
2037 
2038     private void mergeStandardTextNode(Node node)
2039         throws IIOInvalidTreeException {
2040         // Convert to comments.  For the moment ignore the encoding issue.
2041         // Ignore keywords, language, and encoding (for the moment).
2042         // If compression tag is present, use only entries with "none".
2043         NodeList children = node.getChildNodes();
2044         for (int i = 0; i < children.getLength(); i++) {
2045             Node child = children.item(i);
2046             NamedNodeMap attrs = child.getAttributes();
2047             Node comp = attrs.getNamedItem("compression");
2048             boolean copyIt = true;
2049             if (comp != null) {
2050                 String compString = comp.getNodeValue();
2051                 if (!compString.equals("none")) {
2052                     copyIt = false;
2053                 }
2054             }
2055             if (copyIt) {
2056                 String value = attrs.getNamedItem("value").getNodeValue();
2057                 COMMarkerSegment com = new COMMarkerSegment(value);
2058                 insertCOMMarkerSegment(com);
2059             }
2060         }
2061     }
2062 
2063     private void mergeStandardTransparencyNode(Node node)
2064         throws IIOInvalidTreeException {
2065         // This might indicate that an alpha channel is being added or removed.
2066         // The nodes must appear in order, and a Chroma node will process any
2067         // transparency, so process it here only if there was no Chroma node
2068         // Do nothing for stream metadata
2069         if (!transparencyDone && !isStream) {
2070             boolean wantAlpha = wantAlpha(node);
2071             // do we have alpha already?  If the number of channels is 2 or 4,
2072             // we do, as we don't support CMYK, nor can we add alpha to it
2073             // The number of channels can be determined from the SOF
2074             JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment
2075                 (JFIFMarkerSegment.class, true);
2076             AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment
2077                 (AdobeMarkerSegment.class, true);
2078             SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment
2079                 (SOFMarkerSegment.class, true);
2080             SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment
2081                 (SOSMarkerSegment.class, true);
2082 
2083             // We can do nothing for progressive, as we don't know how to
2084             // modify the scans.
2085             if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
2086                 return;
2087             }
2088 
2089             // Do we already have alpha?  We can tell by the number of channels
2090             // We must have an sof, or we can't do anything further
2091             if (sof != null) {
2092                 int numChannels = sof.componentSpecs.length;
2093                 boolean hadAlpha = (numChannels == 2) || (numChannels == 4);
2094                 // proceed only if the old state and the new state differ
2095                 if (hadAlpha != wantAlpha) {
2096                     if (wantAlpha) {  // Adding alpha
2097                         numChannels++;
2098                         if (jfif != null) {
2099                             markerSequence.remove(jfif);
2100                         }
2101 
2102                         // If an adobe marker is present, transform must be UNKNOWN
2103                         if (adobe != null) {
2104                             adobe.transform = JPEG.ADOBE_UNKNOWN;
2105                         }
2106 
2107                         // Add a component spec with appropriate parameters to SOF
2108                         SOFMarkerSegment.ComponentSpec [] newSpecs =
2109                             new SOFMarkerSegment.ComponentSpec[numChannels];
2110                         for (int i = 0; i < sof.componentSpecs.length; i++) {
2111                             newSpecs[i] = sof.componentSpecs[i];
2112                         }
2113                         byte oldFirstID = (byte) sof.componentSpecs[0].componentId;
2114                         byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4);
2115                         newSpecs[numChannels-1] =
2116                             sof.getComponentSpec(newID,
2117                                 sof.componentSpecs[0].HsamplingFactor,
2118                                 sof.componentSpecs[0].QtableSelector);
2119 
2120                         sof.componentSpecs = newSpecs;
2121 
2122                         // Add a component spec with appropriate parameters to SOS
2123                         SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2124                             new SOSMarkerSegment.ScanComponentSpec [numChannels];
2125                         for (int i = 0; i < sos.componentSpecs.length; i++) {
2126                             newScanSpecs[i] = sos.componentSpecs[i];
2127                         }
2128                         newScanSpecs[numChannels-1] =
2129                             sos.getScanComponentSpec (newID, 0);
2130                         sos.componentSpecs = newScanSpecs;
2131                     } else {  // Removing alpha
2132                         numChannels--;
2133                         // Remove a component spec from SOF
2134                         SOFMarkerSegment.ComponentSpec [] newSpecs =
2135                             new SOFMarkerSegment.ComponentSpec[numChannels];
2136                         for (int i = 0; i < numChannels; i++) {
2137                             newSpecs[i] = sof.componentSpecs[i];
2138                         }
2139                         sof.componentSpecs = newSpecs;
2140 
2141                         // Remove a component spec from SOS
2142                         SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
2143                             new SOSMarkerSegment.ScanComponentSpec [numChannels];
2144                         for (int i = 0; i < numChannels; i++) {
2145                             newScanSpecs[i] = sos.componentSpecs[i];
2146                         }
2147                         sos.componentSpecs = newScanSpecs;
2148                     }
2149                 }
2150             }
2151         }
2152     }
2153 
2154 
2155     public void setFromTree(String formatName, Node root)
2156         throws IIOInvalidTreeException {
2157         if (formatName == null) {
2158             throw new IllegalArgumentException("null formatName!");
2159         }
2160         if (root == null) {
2161             throw new IllegalArgumentException("null root!");
2162         }
2163         if (isStream &&
2164             (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
2165             setFromNativeTree(root);
2166         } else if (!isStream &&
2167                    (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
2168             setFromNativeTree(root);
2169         } else if (!isStream &&
2170                    (formatName.equals
2171                     (IIOMetadataFormatImpl.standardMetadataFormatName))) {
2172             // In this case a reset followed by a merge is correct
2173             super.setFromTree(formatName, root);
2174         } else {
2175             throw  new IllegalArgumentException("Unsupported format name: "
2176                                                 + formatName);
2177         }
2178     }
2179 
2180     private void setFromNativeTree(Node root) throws IIOInvalidTreeException {
2181         if (resetSequence == null) {
2182             resetSequence = markerSequence;
2183         }
2184         markerSequence = new ArrayList();
2185 
2186         // Build a whole new marker sequence from the tree
2187 
2188         String name = root.getNodeName();
2189         if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
2190                                 : JPEG.nativeImageMetadataFormatName)) {
2191             throw new IIOInvalidTreeException("Invalid root node name: " + name,
2192                                               root);
2193         }
2194         if (!isStream) {
2195             if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
2196                 throw new IIOInvalidTreeException(
2197                     "JPEGvariety and markerSequence nodes must be present", root);
2198             }
2199 
2200             Node JPEGvariety = root.getFirstChild();
2201 
2202             if (JPEGvariety.getChildNodes().getLength() != 0) {
2203                 markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
2204             }
2205         }
2206 
2207         Node markerSequenceNode = isStream ? root : root.getLastChild();
2208         setFromMarkerSequenceNode(markerSequenceNode);
2209 
2210     }
2211 
2212     void setFromMarkerSequenceNode(Node markerSequenceNode)
2213         throws IIOInvalidTreeException{
2214 
2215         NodeList children = markerSequenceNode.getChildNodes();
2216         // for all the children, add a marker segment
2217         for (int i = 0; i < children.getLength(); i++) {
2218             Node node = children.item(i);
2219             String childName = node.getNodeName();
2220             if (childName.equals("dqt")) {
2221                 markerSequence.add(new DQTMarkerSegment(node));
2222             } else if (childName.equals("dht")) {
2223                 markerSequence.add(new DHTMarkerSegment(node));
2224             } else if (childName.equals("dri")) {
2225                 markerSequence.add(new DRIMarkerSegment(node));
2226             } else if (childName.equals("com")) {
2227                 markerSequence.add(new COMMarkerSegment(node));
2228             } else if (childName.equals("app14Adobe")) {
2229                 markerSequence.add(new AdobeMarkerSegment(node));
2230             } else if (childName.equals("unknown")) {
2231                 markerSequence.add(new MarkerSegment(node));
2232             } else if (childName.equals("sof")) {
2233                 markerSequence.add(new SOFMarkerSegment(node));
2234             } else if (childName.equals("sos")) {
2235                 markerSequence.add(new SOSMarkerSegment(node));
2236             } else {
2237                 throw new IIOInvalidTreeException("Invalid "
2238                     + (isStream ? "stream " : "image ") + "child: "
2239                     + childName, node);
2240             }
2241         }
2242     }
2243 
2244     /**
2245      * Check that this metadata object is in a consistent state and
2246      * return <code>true</code> if it is or <code>false</code>
2247      * otherwise.  All the constructors and modifiers should call
2248      * this method at the end to guarantee that the data is always
2249      * consistent, as the writer relies on this.
2250      */
2251     private boolean isConsistent() {
2252         SOFMarkerSegment sof =
2253             (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
2254                                                  true);
2255         JFIFMarkerSegment jfif =
2256             (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
2257                                                   true);
2258         AdobeMarkerSegment adobe =
2259             (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
2260                                                    true);
2261         boolean retval = true;
2262         if (!isStream) {
2263             if (sof != null) {
2264                 // SOF numBands = total scan bands
2265                 int numSOFBands = sof.componentSpecs.length;
2266                 int numScanBands = countScanBands();
2267                 if (numScanBands != 0) {  // No SOS is OK
2268                     if (numScanBands != numSOFBands) {
2269                         retval = false;
2270                     }
2271                 }
2272                 // If JFIF is present, component ids are 1-3, bands are 1 or 3
2273                 if (jfif != null) {
2274                     if ((numSOFBands != 1) && (numSOFBands != 3)) {
2275                         retval = false;
2276                     }
2277                     for (int i = 0; i < numSOFBands; i++) {
2278                         if (sof.componentSpecs[i].componentId != i+1) {
2279                             retval = false;
2280                         }
2281                     }
2282 
2283                     // If both JFIF and Adobe are present,
2284                     // Adobe transform == unknown for gray,
2285                     // YCC for 3-chan.
2286                     if ((adobe != null)
2287                         && (((numSOFBands == 1)
2288                              && (adobe.transform != JPEG.ADOBE_UNKNOWN))
2289                             || ((numSOFBands == 3)
2290                                 && (adobe.transform != JPEG.ADOBE_YCC)))) {
2291                         retval = false;
2292                     }
2293                 }
2294             } else {
2295                 // stream can't have jfif, adobe, sof, or sos
2296                 SOSMarkerSegment sos =
2297                     (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
2298                                                          true);
2299                 if ((jfif != null) || (adobe != null)
2300                     || (sof != null) || (sos != null)) {
2301                     retval = false;
2302                 }
2303             }
2304         }
2305         return retval;
2306     }
2307 
2308     /**
2309      * Returns the total number of bands referenced in all SOS marker
2310      * segments, including 0 if there are no SOS marker segments.
2311      */
2312     private int countScanBands() {
2313         List ids = new ArrayList();
2314         Iterator iter = markerSequence.iterator();
2315         while(iter.hasNext()) {
2316             MarkerSegment seg = (MarkerSegment)iter.next();
2317             if (seg instanceof SOSMarkerSegment) {
2318                 SOSMarkerSegment sos = (SOSMarkerSegment) seg;
2319                 SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
2320                 for (int i = 0; i < specs.length; i++) {
2321                     Integer id = new Integer(specs[i].componentSelector);
2322                     if (!ids.contains(id)) {
2323                         ids.add(id);
2324                     }
2325                 }
2326             }
2327         }
2328 
2329         return ids.size();
2330     }
2331 
2332     ///// Writer support
2333 
2334     void writeToStream(ImageOutputStream ios,
2335                        boolean ignoreJFIF,
2336                        boolean forceJFIF,
2337                        List thumbnails,
2338                        ICC_Profile iccProfile,
2339                        boolean ignoreAdobe,
2340                        int newAdobeTransform,
2341                        JPEGImageWriter writer)
2342         throws IOException {
2343         if (forceJFIF) {
2344             // Write a default JFIF segment, including thumbnails
2345             // This won't be duplicated below because forceJFIF will be
2346             // set only if there is no JFIF present already.
2347             JFIFMarkerSegment.writeDefaultJFIF(ios,
2348                                                thumbnails,
2349                                                iccProfile,
2350                                                writer);
2351             if ((ignoreAdobe == false)
2352                 && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
2353                 if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
2354                     && (newAdobeTransform != JPEG.ADOBE_YCC)) {
2355                     // Not compatible, so ignore Adobe.
2356                     ignoreAdobe = true;
2357                     writer.warningOccurred
2358                         (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2359                 }
2360             }
2361         }
2362         // Iterate over each MarkerSegment
2363         Iterator iter = markerSequence.iterator();
2364         while(iter.hasNext()) {
2365             MarkerSegment seg = (MarkerSegment)iter.next();
2366             if (seg instanceof JFIFMarkerSegment) {
2367                 if (ignoreJFIF == false) {
2368                     JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
2369                     jfif.writeWithThumbs(ios, thumbnails, writer);
2370                     if (iccProfile != null) {
2371                         JFIFMarkerSegment.writeICC(iccProfile, ios);
2372                     }
2373                 } // Otherwise ignore it, as requested
2374             } else if (seg instanceof AdobeMarkerSegment) {
2375                 if (ignoreAdobe == false) {
2376                     if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
2377                         AdobeMarkerSegment newAdobe =
2378                             (AdobeMarkerSegment) seg.clone();
2379                         newAdobe.transform = newAdobeTransform;
2380                         newAdobe.write(ios);
2381                     } else if (forceJFIF) {
2382                         // If adobe isn't JFIF compatible, ignore it
2383                         AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
2384                         if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
2385                             || (adobe.transform == JPEG.ADOBE_YCC)) {
2386                             adobe.write(ios);
2387                         } else {
2388                             writer.warningOccurred
2389                          (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
2390                         }
2391                     } else {
2392                         seg.write(ios);
2393                     }
2394                 } // Otherwise ignore it, as requested
2395             } else {
2396                 seg.write(ios);
2397             }
2398         }
2399     }
2400 
2401     //// End of writer support
2402 
2403     public void reset() {
2404         if (resetSequence != null) {  // Otherwise no need to reset
2405             markerSequence = resetSequence;
2406             resetSequence = null;
2407         }
2408     }
2409 
2410     public void print() {
2411         for (int i = 0; i < markerSequence.size(); i++) {
2412             MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
2413             seg.print();
2414         }
2415     }
2416 
2417 }