/* * ImageI/O-Ext - OpenSource Java Image translation Library * http://www.geo-solutions.it/ * http://java.net/projects/imageio-ext/ * (C) 2008, GeoSolutions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package it.geosolutions.imageio.plugins.jp2k; import it.geosolutions.imageio.plugins.jp2k.box.BitsPerComponentBox; import it.geosolutions.imageio.plugins.jp2k.box.CodestreamHeaderBox; import it.geosolutions.imageio.plugins.jp2k.box.ColorSpecificationBox; import it.geosolutions.imageio.plugins.jp2k.box.ComponentMappingBox; import it.geosolutions.imageio.plugins.jp2k.box.CompositingLayerHeaderBox; import it.geosolutions.imageio.plugins.jp2k.box.ContiguousCodestreamBox; import it.geosolutions.imageio.plugins.jp2k.box.FileTypeBox; import it.geosolutions.imageio.plugins.jp2k.box.IPRBox; import it.geosolutions.imageio.plugins.jp2k.box.ImageHeaderBox; import it.geosolutions.imageio.plugins.jp2k.box.JP2HeaderBox; import it.geosolutions.imageio.plugins.jp2k.box.JP2KFileBox; import it.geosolutions.imageio.plugins.jp2k.box.PaletteBox; import it.geosolutions.imageio.plugins.jp2k.box.ReaderRequirementsBox; import it.geosolutions.imageio.plugins.jp2k.box.ResolutionBox; import it.geosolutions.imageio.plugins.jp2k.box.SignatureBox; import it.geosolutions.imageio.plugins.jp2k.box.FileTypeBox.JPEG2000FileType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; public class JP2KTreeController implements JP2KTreeChecker, TreeModelListener{ private final static Logger LOGGER = Logger.getLogger("it.geosolutions.imageio.plugins.jp2k"); private static Throwable checkCauses(final Collection<Throwable> causes){ if (causes == null || causes.size() == 0) throw new IllegalArgumentException("Causes cannot be null or empty for this MultiCauseIllegalStateException"); return causes.iterator().next(); } /** * Simple subclass of {@link IllegalStateException} which can be used to report multiple causes. * * @author Simone Giannecchini, GeoSolutions * */ public final class MultiCauseIllegalStateException extends IllegalStateException{ /** * */ private static final long serialVersionUID = 8965184317402766015L; private ArrayList<Throwable> causes; public MultiCauseIllegalStateException(final String message,final Collection<Throwable> causes){ super(message,checkCauses(causes)); this.causes= new ArrayList<Throwable>(causes); } public MultiCauseIllegalStateException(final Collection<Throwable> causes){ super(checkCauses(causes)); this.causes= new ArrayList<Throwable>(causes); } public List<Throwable> getCauses() { return Collections.unmodifiableList(causes); } } abstract class PolicyCheck{ List<? extends Throwable> checkNodesInserted( final JP2KBox node, final int[] childrenIndices, final Object[] children){ return Collections.emptyList(); } List<? extends Throwable> checkTreeConsistency(){ return Collections.emptyList(); } } class UnspecifiedPolicy extends PolicyCheck{ @Override List<? extends Throwable> checkNodesInserted( final JP2KBox node, final int[] childrenIndices, final Object[] children) { switch(node.getType()){ case JP2KFileBox.BOX_TYPE: if (childrenIndices.length >= 1 && children != null) { for (int i = 0; i < childrenIndices.length; i++) { final int index = childrenIndices[i]; final Object child = children[i]; if(child==null) continue; switch(index){ case 0: final JP2KBox signature = (JP2KBox) child; if (signature.getType() != SignatureBox.BOX_TYPE) return Arrays.asList(new IllegalStateException("First box of a JPEG2000 file must be the SignatureBox")); final byte[] content = signature.getContent(); final byte[] expectedContent = SignatureBox.LOCAL_DATA; if(expectedContent.length!=content.length) return Arrays.asList(new IllegalStateException("SignatureBox content incorrect")); for(int j=0;j<expectedContent.length;j++) if(expectedContent[j]!=content[j]) return Arrays.asList(new IllegalStateException("SignatureBox content incorrect")); return super.checkNodesInserted(node, childrenIndices, children); case 1: JP2KBox box = (JP2KBox) child; if (box.getType() != FileTypeBox.BOX_TYPE) return Arrays.asList(new IllegalStateException("Second box of a JPEG2000 file must be the FileTypeBox")); //let's convert it if(box instanceof LazyJP2KBox) box = ((LazyJP2KBox) box).getOriginalBox(); JP2KTreeController.this.fileType=((FileTypeBox)box).getBrand(); JP2KTreeController.this.compatibilitySet=((FileTypeBox)box).getCompatibilitySet(); if(!compatibilitySet.contains(JPEG2000FileType.JP2)) return Arrays.asList(new IllegalStateException("We are only able to serve JP2 compatible files")); return super.checkNodesInserted(node, childrenIndices, children); } } } } return Arrays.asList(new IllegalStateException("This node should not be inserted while the file type is still unspecified")); } } class JP2Policy extends PolicyCheck{ @Override List<? extends Throwable> checkNodesInserted( final JP2KBox node, final int[] childrenIndices, final Object[] children) { List<IllegalStateException> errors = new ArrayList<IllegalStateException>(); final Object root = model.getRoot(); final JP2KFileBox fileBox; if (root!=null && root instanceof JP2KFileBox){ fileBox = (JP2KFileBox) root; } else throw new IllegalStateException("Root node unavailable"); switch(node.getType()){ // //////////////////////////////////////////////////// // // Checking the FileBox // // //////////////////////////////////////////////////// case JP2KFileBox.BOX_TYPE: if(childrenIndices.length>=1&&children!=null){ for(int i=0;i<childrenIndices.length;i++){ final int index = childrenIndices[i]; final Object child = children[i]; if(child==null) continue; final JP2KBox candidate = (JP2KBox) child; final int childType = candidate.getType(); switch (childType){ // // // // Checking the JP2HeaderBox // // // case JP2HeaderBox.BOX_TYPE: final int contiguousCodestreamBoxIndex = getChildIndex(node, ContiguousCodestreamBox.BOX_TYPE); if (contiguousCodestreamBoxIndex!=-1) if (contiguousCodestreamBoxIndex<=index) errors.add(new IllegalStateException("ContiguousCodestream Box must appear after the JP2Header Box")); break; // // // // Checking the ContiguousCodestreamBox // // // case ContiguousCodestreamBox.BOX_TYPE: final int jp2HeaderBoxIndex = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HeaderBoxIndex!=-1) if (jp2HeaderBoxIndex>=index) errors.add(new IllegalStateException("JP2Header Box must appear before the ContiguousCodestream")); break; // // // // Checking the IPRBox // // // case IPRBox.BOX_TYPE: final int jp2HeaderIndex = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HeaderIndex!=-1){ final JP2KBox jp2HeaderBox = (JP2KBox) node.getChildAt(jp2HeaderIndex); final int imageHeaderBoxIndex = getChildIndex(jp2HeaderBox, ImageHeaderBox.BOX_TYPE); if (imageHeaderBoxIndex!=-1){ final JP2KBox ihBox = (JP2KBox) jp2HeaderBox.getChildAt(imageHeaderBoxIndex); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(ihBox); if (imageHeaderBox.getIntellectualProperty()!=1){ errors.add(new IllegalStateException("IPRBox needs ImageHeaderBox's IP flag set to 1")); } } } break; } } } // //////////////////////////////////////////////////// // // Checking the JP2Header Box // // //////////////////////////////////////////////////// case JP2HeaderBox.BOX_TYPE: if(childrenIndices.length>=1 && children!=null){ for(int i=0;i<childrenIndices.length;i++){ final int index = childrenIndices[i]; final Object child = children[i]; if (child == null) continue; final JP2KBox childBox = (JP2KBox) child; final int boxType = childBox.getType(); if (index == 0) { // // // // Checking the ImageHeader Box // ---------------------------- // ImageHeaderBox has some bytes which allow to know additional information // about additional Boxes. As an instance, in case the IP parameter is set // to 1, then an IPR box should be present. // Then, I need to check ImageHeaderBox content is consistent with the // already inserted nodes. // // // if (boxType != ImageHeaderBox.BOX_TYPE) throw new IllegalStateException( "First box of a JP2 Header Box must be the ImageHeaderBox"); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(childBox); // // // // Checking IntellectualProperty. // If IP is 0, then the JP2 File shall not contain a // IntellectualPropertyBox // // // final byte ip = imageHeaderBox.getIntellectualProperty(); if (ip == 0) { final int childBoxIndex = getChildIndex(fileBox, IPRBox.BOX_TYPE); if (childBoxIndex!=-1) errors.add(new IllegalStateException("IntellectualProperty Box shall " + "not be defined when ImageHeaderBox has IP=0")); } // // // // Checking BitDepth is consistent with the BitsPerComponent Box. // // // final byte bitDepth = imageHeaderBox.getBitDepth(); if (bitDepth != 0xFF){ // Specification states that in case all components have the same // bit-depth, then the BitDepth contains that value and a // BitsPerComponent box shall not exist. Otherwise, // the BitDepth parameter should be 0xFF final int childBoxIndex = getChildIndex(fileBox, BitsPerComponentBox.BOX_TYPE); if (childBoxIndex!=-1) errors.add(new IllegalStateException("BitsPerComponent Box shall not be defined when ImageHeaderBox has BitDepth != 0xFF")); } }else{ // // // // Checking the BitsPerComponent Box // // // if (boxType == BitsPerComponentBox.BOX_TYPE){ final int imageHeaderBoxIndex = getChildIndex(node, ImageHeaderBox.BOX_TYPE); if (imageHeaderBoxIndex!=-1){ final JP2KBox ihBox = (JP2KBox) node.getChildAt(imageHeaderBoxIndex); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(ihBox); if (imageHeaderBox.getBitDepth()!=0xFF){ errors.add(new IllegalStateException("BitsPerComponent Box shall not be defined when ImageHeaderBox has BitDepth != 0xFF")); } } } // // // // Checking the ColorSpecification Box // // // else if (boxType == ColorSpecificationBox.BOX_TYPE){ final ColorSpecificationBox csBox = (ColorSpecificationBox)LazyJP2KBox.getAsOriginalBox(childBox); final byte method = csBox.getMethod(); if (method==1){ if (csBox.getICCProfile()!=null){ //TODO: maybe avoid adding an error -> Simply logging a warning errors.add(new IllegalStateException("ColorSpecification Box with method = 1 should have a NULL ICP")); } final int ecs = csBox.getEnumeratedColorSpace(); if (ecs!=ColorSpecificationBox.ECS_GRAY && ecs!=ColorSpecificationBox.ECS_sRGB && ecs!=ColorSpecificationBox.ECS_YCC) errors.add(new IllegalStateException("Unsupported Enumerated Color Space in ColorSpecification Box")); } else if (method==2){ if (csBox.getEnumeratedColorSpace()!=-1) errors.add(new IllegalStateException("ColorSpecification Box with method = 2 shouldn't have an Enumerated Color Space defined")); } else errors.add(new IllegalStateException("ColorSpecification Box only supports value 1 and 2 for the M parameter")); } // // // // Checking the ComponentMapping Box // // // else if (boxType == ComponentMappingBox.BOX_TYPE){ // final ComponentMappingBox cmBox = (ComponentMappingBox) child; //TODO: Add checks } // // // // Checking the Resolution Box // // // else if (boxType == ResolutionBox.BOX_TYPE){ // final ResolutionBox resBox = (ResolutionBox) child; //TODO: Add checks } } } } break; case ResolutionBox.BOX_TYPE: } if (errors.size()==0) return super.checkNodesInserted(node, childrenIndices, children); else return errors; } List<? extends Throwable> checkTreeConsistency() { List <IllegalStateException> errors = new ArrayList<IllegalStateException>(); final Object root = model.getRoot(); if (root!=null && root instanceof JP2KFileBox){ boolean signatureBoxOk = false; boolean fileTypeBoxOk = false; boolean jp2HeaderBoxIsPresent = false; boolean contiguousCodestreamBoxIsPresent = false; boolean iprBoxIsPresent = false; boolean iprBoxIsRequired = false; // ///////////////////////////////////////////////////////////////// // // Checking File Boxes // // ///////////////////////////////////////////////////////////////// final JP2KFileBox fileBox = (JP2KFileBox) root; final int rootChildrenCount = fileBox.getChildCount(); int jp2HeaderBoxIndex = -1; for (int i=0; i<rootChildrenCount; i++){ final JP2KBox child = (JP2KBox) fileBox.getChildAt(i); final int boxType = child.getType(); if (boxType==SignatureBox.BOX_TYPE) signatureBoxOk = true; else if (boxType == FileTypeBox.BOX_TYPE) fileTypeBoxOk = true; else if (boxType == IPRBox.BOX_TYPE) iprBoxIsPresent = true; else if (boxType == JP2HeaderBox.BOX_TYPE){ jp2HeaderBoxIsPresent = true; jp2HeaderBoxIndex = i; } else if (boxType == ContiguousCodestreamBox.BOX_TYPE) contiguousCodestreamBoxIsPresent = true; } // // // // Checking all required boxes have been found on the file. // // // if (!signatureBoxOk) errors.add(new IllegalStateException("Missing SignatureBox")); if (!fileTypeBoxOk) errors.add(new IllegalStateException("Missing FileTypeBox")); if (!jp2HeaderBoxIsPresent) errors.add(new IllegalStateException("Missing Jp2HeaderBox")); else{ // ///////////////////////////////////////////////////////// // // Checking JP2Header Boxes // // ///////////////////////////////////////////////////////// JP2KBox jp2header = (JP2KBox) fileBox.getChildAt(jp2HeaderBoxIndex); final int jp2HeaderChildrenCount = jp2header.getChildCount(); // // // Init booleans for checks // // boolean imageHeaderBoxOk = false; boolean bitsPerComponentBoxIsPresent = false; boolean colorSpecificationBoxOk = false; boolean paletteBoxIsPresent = false; boolean componentMappingBoxIsPresent = false; boolean resolutionBoxIsPresent = false; boolean resolutionBoxIsOk = false; byte b = 0; // // // // Loop over the JP2HeaderBox children // // // for (int k=0; k<jp2HeaderChildrenCount; k++){ final JP2KBox jp2HeaderChild = (JP2KBox) jp2header.getChildAt(k); final int childboxType = jp2HeaderChild.getType(); // // // Check ImageHeaderBox consistency // // if (childboxType==ImageHeaderBox.BOX_TYPE){ imageHeaderBoxOk = true; final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(jp2HeaderChild); b = imageHeaderBox.getBitDepth(); iprBoxIsRequired = imageHeaderBox.getIntellectualProperty()==1; } else if (childboxType == BitsPerComponentBox.BOX_TYPE){ bitsPerComponentBoxIsPresent = true; } else if (childboxType == ColorSpecificationBox.BOX_TYPE){ colorSpecificationBoxOk = true; } else if (childboxType == PaletteBox.BOX_TYPE){ paletteBoxIsPresent = true; } else if (childboxType == ComponentMappingBox.BOX_TYPE){ componentMappingBoxIsPresent = true; } else if (childboxType == ResolutionBox.BOX_TYPE){ resolutionBoxIsPresent = true; resolutionBoxIsOk = (jp2HeaderChild.getChildCount()>0); } } // // // // Check all required boxes have been found on JP2HeaderBox. // // // if (!imageHeaderBoxOk) errors.add(new IllegalStateException("Missing ImageHeaderBox")); if (b!=0xFF && bitsPerComponentBoxIsPresent) errors.add(new IllegalStateException("BitsPerComponentBox shall not be defined when Bit-Depth in ImageHeaderBox is not 0xFF")); if (!colorSpecificationBoxOk) errors.add(new IllegalStateException("Missing ColorSpecificationBox")); if (resolutionBoxIsPresent && !resolutionBoxIsOk) errors.add(new IllegalStateException("ResolutionBox superbox doesn't contain any child")); if (paletteBoxIsPresent ^ componentMappingBoxIsPresent) errors.add(new IllegalStateException("PaletteBox requires a ComponentMappingBox and viceversa")); } // // // Checking Intellectual Property Rights consistency // // if (iprBoxIsRequired ^ iprBoxIsPresent){ errors.add(new IllegalStateException("IPRBox is inconsistent with the ImageHeaderBox IP parameter")); } if (!contiguousCodestreamBoxIsPresent) errors.add(new IllegalStateException("ContiguousCodeStreamBox is missing")); } if (errors.isEmpty()) return Collections.emptyList(); return errors; } } class JPXPolicy extends PolicyCheck{ /** * TODO: * * @todo Rules for checking the JPX consistency are more complex * with respect to the JP2's ones. */ @Override List<? extends Throwable> checkTreeConsistency() { List <IllegalStateException> errors = new ArrayList<IllegalStateException>(); final Object root = model.getRoot(); if (root!=null && root instanceof JP2KFileBox){ boolean signatureBoxOk = false; boolean fileTypeBoxOk = false; boolean jp2HeaderBoxIsPresent = false; boolean readerRequirementsBoxIsPresent = false; boolean iprBoxIsPresent = false; boolean iprBoxIsRequired = false; boolean imageHeaderBoxIsPresent = false; int codestreamHeaderBoxes = 0; int numCodestreams = 0; int compositingLayerHeaderBoxes = 0; // ///////////////////////////////////////////////////////////////// // // Checking File Boxes // // ///////////////////////////////////////////////////////////////// final JP2KFileBox fileBox = (JP2KFileBox) root; final int rootChildrenCount = fileBox.getChildCount(); int jp2HeaderBoxIndex = -1; int firstCompositingLayerHeaderBoxIndex = -1; int firstCodestreamHeaderBoxIndex = -1; int firstCodestreamBoxIndex = -1; for (int i=0; i<rootChildrenCount; i++){ final JP2KBox child = (JP2KBox) fileBox.getChildAt(i); final int boxType = child.getType(); if (boxType==SignatureBox.BOX_TYPE) signatureBoxOk = true; else if (boxType == FileTypeBox.BOX_TYPE) fileTypeBoxOk = true; else if (boxType == IPRBox.BOX_TYPE) iprBoxIsPresent = true; else if (boxType == ReaderRequirementsBox.BOX_TYPE) readerRequirementsBoxIsPresent = true; else if (boxType == JP2HeaderBox.BOX_TYPE){ jp2HeaderBoxIsPresent = true; jp2HeaderBoxIndex = i; } else if (boxType == CompositingLayerHeaderBox.BOX_TYPE){ if (firstCompositingLayerHeaderBoxIndex ==-1) firstCompositingLayerHeaderBoxIndex = i; compositingLayerHeaderBoxes++; } else if (boxType == CodestreamHeaderBox.BOX_TYPE){ if (firstCodestreamHeaderBoxIndex == -1) firstCodestreamHeaderBoxIndex = i; codestreamHeaderBoxes++; } else if (boxType == ContiguousCodestreamBox.BOX_TYPE){ if (firstCodestreamBoxIndex == -1) firstCodestreamBoxIndex = i; numCodestreams++; } } // // // // Checking all required boxes have been found on the file. // // // if (!signatureBoxOk) errors.add(new IllegalStateException("Missing SignatureBox")); if (!readerRequirementsBoxIsPresent) errors.add(new IllegalStateException("Missing ReaderRequirementsBox")); if (!fileTypeBoxOk) errors.add(new IllegalStateException("Missing FileTypeBox")); if (!jp2HeaderBoxIsPresent){ if (firstCodestreamHeaderBoxIndex!=-1){ final JP2KBox codestreamHeaderBox = (JP2KBox) fileBox.getChildAt(firstCodestreamHeaderBoxIndex); final int codestreamChildrenCount = fileBox.getChildCount(); for (int i=0; i<codestreamChildrenCount; i++){ final JP2KBox child = (JP2KBox) codestreamHeaderBox.getChildAt(i); final int boxType = child.getType(); if (boxType==ImageHeaderBox.BOX_TYPE){ imageHeaderBoxIsPresent = true; break; } } } else errors.add(new IllegalStateException("Missing both JP2HeaderBox and CodeStreamHeaderBox")); if (firstCompositingLayerHeaderBoxIndex!=-1){ //TODO:Check for the colourSpecificationBox } } else{ // ///////////////////////////////////////////////////////// // // Checking JP2Header Boxes // // ///////////////////////////////////////////////////////// JP2KBox jp2header = (JP2KBox) fileBox.getChildAt(jp2HeaderBoxIndex); final int jp2HeaderChildrenCount = jp2header.getChildCount(); // // // Init booleans for checks // // boolean bitsPerComponentBoxIsPresent = false; boolean colourSpecificationBoxOk = false; boolean paletteBoxIsPresent = false; boolean componentMappingBoxIsPresent = false; boolean resolutionBoxIsPresent = false; boolean resolutionBoxIsOk = false; byte b = 0; // // // // Loop over the JP2HeaderBox children // // // for (int k=0; k<jp2HeaderChildrenCount; k++){ final JP2KBox jp2HeaderChild = (JP2KBox) jp2header.getChildAt(k); final int childboxType = jp2HeaderChild.getType(); // // // Check ImageHeaderBox consistency // // if (childboxType==ImageHeaderBox.BOX_TYPE){ imageHeaderBoxIsPresent = true; final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(jp2HeaderChild); b = imageHeaderBox.getBitDepth(); iprBoxIsRequired = imageHeaderBox.getIntellectualProperty()==1; } else if (childboxType == BitsPerComponentBox.BOX_TYPE){ bitsPerComponentBoxIsPresent = true; } else if (childboxType == ColorSpecificationBox.BOX_TYPE){ colourSpecificationBoxOk = true; } else if (childboxType == PaletteBox.BOX_TYPE){ paletteBoxIsPresent = true; } else if (childboxType == ComponentMappingBox.BOX_TYPE){ componentMappingBoxIsPresent = true; } else if (childboxType == ResolutionBox.BOX_TYPE){ resolutionBoxIsPresent = true; resolutionBoxIsOk = (jp2HeaderChild.getChildCount()>0); } } //TODO: FIX ME! Check Boxes within JP2HeaderBox are not defined in the first // CodestreamHeaderBox and CompositingLayerHeaderBox // // // // Check all required boxes have been found on JP2HeaderBox. // // // if (b!=0xFF && bitsPerComponentBoxIsPresent) errors.add(new IllegalStateException("BitsPerComponentBox shall not be defined when Bit-Depth in ImageHeaderBox is not 0xFF")); if (!colourSpecificationBoxOk) errors.add(new IllegalStateException("Missing ColorSpecificationBox")); if (resolutionBoxIsPresent && !resolutionBoxIsOk) errors.add(new IllegalStateException("ResolutionBox superbox doesn't contain any child")); if (paletteBoxIsPresent ^ componentMappingBoxIsPresent) errors.add(new IllegalStateException("PaletteBox requires a ComponentMappingBox and viceversa")); } if (jp2HeaderBoxIsPresent){ // // // // Rule of M.9.2.7 // // // if (firstCodestreamHeaderBoxIndex!=-1 && jp2HeaderBoxIndex>=firstCodestreamHeaderBoxIndex) errors.add(new IllegalStateException("CodestreamHeader Box must appear after the JP2Header Box")); if (firstCompositingLayerHeaderBoxIndex!=-1 && jp2HeaderBoxIndex>=firstCompositingLayerHeaderBoxIndex) errors.add(new IllegalStateException("CompositingLayerHeader Box must appear after the JP2Header Box")); if (firstCodestreamBoxIndex!=-1 && jp2HeaderBoxIndex>=firstCodestreamBoxIndex) errors.add(new IllegalStateException("Codestream Box must appear after the JP2Header Box")); } if (!imageHeaderBoxIsPresent) errors.add(new IllegalStateException("Missing ImageHeaderBox")); // // // Checking Intellectual Property Rights consistency // // if (iprBoxIsRequired ^ iprBoxIsPresent){ errors.add(new IllegalStateException("IPRBox is inconsistent with the ImageHeaderBox IP parameter")); } if (codestreamHeaderBoxes!=0 && codestreamHeaderBoxes!=numCodestreams){ //TODO: FIX ME! NEED TO TAKE CARE OF FRAGMENT TABLE BOX TOO. errors.add(new IllegalStateException("The number of codestreams and codestreamHeaderBox shall match")); } } if (errors.isEmpty()) return Collections.emptyList(); return errors; } /** * TODO: * * @todo Actual check is a simple copy of the JP2 policy followed by * a compatibility list check. Rules for checking the JPX * consistency are more complex with respect to the JP2's * ones. */ List<? extends Throwable> checkNodesInserted( final JP2KBox node, final int[] childrenIndices, final Object[] children) { List<IllegalStateException> errors = new ArrayList<IllegalStateException>(); final Object root = model.getRoot(); final JP2KFileBox fileBox; if (root!=null && root instanceof JP2KFileBox){ fileBox = (JP2KFileBox) root; } else throw new IllegalStateException("Root node unavailable"); switch(node.getType()){ // //////////////////////////////////////////////////// // // Checking the FileBox // // //////////////////////////////////////////////////// case JP2KFileBox.BOX_TYPE: if(childrenIndices.length>=1&&children!=null){ for(int i=0;i<childrenIndices.length;i++){ final int index = childrenIndices[i]; final Object child = children[i]; if(child==null) continue; final JP2KBox candidate = (JP2KBox) child; final int childType = candidate.getType(); switch (childType){ // // // // Checking the ReaderRequirementsBox // // // case ReaderRequirementsBox.BOX_TYPE: final int fileTypeBoxIndex = getChildIndex(node, FileTypeBox.BOX_TYPE); if (fileTypeBoxIndex!=-1) if (fileTypeBoxIndex>=index) errors.add(new IllegalStateException("ReaderRequirements Box must appear after the FileType Box")); ReaderRequirementsBox rreqbox = (ReaderRequirementsBox)LazyJP2KBox.getAsOriginalBox(candidate); //TODO: Check the GML-JP2 signal: standard Feature Value = 67. break; case JP2HeaderBox.BOX_TYPE: final int contiguousCodestreamBoxIndex = getChildIndex(node, ContiguousCodestreamBox.BOX_TYPE); if (contiguousCodestreamBoxIndex!=-1) if (contiguousCodestreamBoxIndex<=index) errors.add(new IllegalStateException("ContiguousCodestream Box must appear after the JP2Header Box")); final int codestreamHeaderBoxIndex = getChildIndex(node, CodestreamHeaderBox.BOX_TYPE); if (codestreamHeaderBoxIndex!=-1) if (codestreamHeaderBoxIndex<=index) errors.add(new IllegalStateException("CodestreamHeader Box must appear after the JP2Header Box")); final int compositingLayerHeaderBox = getChildIndex(node, CompositingLayerHeaderBox.BOX_TYPE); if (compositingLayerHeaderBox!=-1) if (compositingLayerHeaderBox<=index) errors.add(new IllegalStateException("CompositingLayerHeader Box must appear after the JP2Header Box")); //TODO: Add more checks break; case CompositingLayerHeaderBox.BOX_TYPE: final int jp2HBoxIndex = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HBoxIndex!=-1) if (jp2HBoxIndex>=index) errors.add(new IllegalStateException("CompositingLayerHeader Box must appear after the JP2Header Box")); break; case CodestreamHeaderBox.BOX_TYPE: final int jp2HBoxIndex1 = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HBoxIndex1!=-1) if (jp2HBoxIndex1>=index) errors.add(new IllegalStateException("CodestreamHeader Box must appear after the JP2Header Box")); break; // // // // Checking the ContiguousCodestreamBox // // // case ContiguousCodestreamBox.BOX_TYPE: final int jp2HBoxIndex2 = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HBoxIndex2!=-1) if (jp2HBoxIndex2>=index) errors.add(new IllegalStateException("JP2Header Box must appear before the ContiguousCodestream")); break; // // // // Checking the IPRBox // // // case IPRBox.BOX_TYPE: final int jp2HeaderIndex = getChildIndex(node, JP2HeaderBox.BOX_TYPE); if (jp2HeaderIndex!=-1){ final JP2KBox jp2HeaderBox = (JP2KBox) node.getChildAt(jp2HeaderIndex); final int imageHeaderBoxIndex = getChildIndex(jp2HeaderBox, ImageHeaderBox.BOX_TYPE); if (imageHeaderBoxIndex!=-1){ final JP2KBox ihBox = (JP2KBox) jp2HeaderBox.getChildAt(imageHeaderBoxIndex); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(ihBox); if (imageHeaderBox.getIntellectualProperty()!=1){ errors.add(new IllegalStateException("IPRBox needs ImageHeaderBox's IP flag set to 1")); } } } break; } } } // //////////////////////////////////////////////////// // // Checking the JP2Header Box // // //////////////////////////////////////////////////// case JP2HeaderBox.BOX_TYPE: if(childrenIndices.length>=1 && children!=null){ for(int i=0;i<childrenIndices.length;i++){ final int index = childrenIndices[i]; final Object child = children[i]; if (child == null) continue; final JP2KBox childBox = (JP2KBox) child; final int boxType = childBox.getType(); if (index == 0) { // // // // Checking the ImageHeader Box // ---------------------------- // ImageHeaderBox has some bytes which allow to know additional information // about additional Boxes. As an instance, in case the IP parameter is set // to 1, then an IPR box should be present. // Then, I need to check ImageHeaderBox content is consistent with the // already inserted nodes. // // // if (boxType != ImageHeaderBox.BOX_TYPE) throw new IllegalStateException( "First box of a JP2 Header Box must be the ImageHeaderBox"); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(childBox); // // // // Checking IntellectualProperty. // If IP is 0, then the JP2 File shall not contain a // IntellectualPropertyBox // // // final byte ip = imageHeaderBox.getIntellectualProperty(); if (ip == 0) { final int childBoxIndex = getChildIndex(fileBox, IPRBox.BOX_TYPE); if (childBoxIndex!=-1) errors.add(new IllegalStateException("IntellectualProperty Box shall " + "not be defined when ImageHeaderBox has IP=0")); } // // // // Checking BitDepth is consistent with the BitsPerComponent Box. // // // final byte bitDepth = imageHeaderBox.getBitDepth(); if (bitDepth != 0xFF){ // Specification states that in case all components have the same // bit-depth, then the BitDepth contains that value and a // BitsPerComponent box shall not exist. Otherwise, // the BitDepth parameter should be 0xFF final int childBoxIndex = getChildIndex(fileBox, BitsPerComponentBox.BOX_TYPE); if (childBoxIndex!=-1) errors.add(new IllegalStateException("BitsPerComponent Box shall not be defined when ImageHeaderBox has BitDepth != 0xFF")); } }else{ // // // // Checking the BitsPerComponent Box // // // if (boxType == BitsPerComponentBox.BOX_TYPE){ final int imageHeaderBoxIndex = getChildIndex(node, ImageHeaderBox.BOX_TYPE); if (imageHeaderBoxIndex!=-1){ final JP2KBox ihBox = (JP2KBox) node.getChildAt(imageHeaderBoxIndex); final ImageHeaderBox imageHeaderBox = (ImageHeaderBox)LazyJP2KBox.getAsOriginalBox(ihBox); if (imageHeaderBox.getBitDepth()!=0xFF){ errors.add(new IllegalStateException("BitsPerComponent Box shall not be defined when ImageHeaderBox has BitDepth != 0xFF")); } } } // // // // Checking the ColorSpecification Box // // // else if (boxType == ColorSpecificationBox.BOX_TYPE){ final ColorSpecificationBox csBox = (ColorSpecificationBox)LazyJP2KBox.getAsOriginalBox(childBox); final byte method = csBox.getMethod(); if (method==1){ if (csBox.getICCProfile()!=null){ //TODO: maybe avoid adding an error -> Simply logging a warning errors.add(new IllegalStateException("ColorSpecification Box with method = 1 should have a NULL ICP")); } final int ecs = csBox.getEnumeratedColorSpace(); if (ecs!=ColorSpecificationBox.ECS_GRAY && ecs!=ColorSpecificationBox.ECS_sRGB && ecs!=ColorSpecificationBox.ECS_YCC) errors.add(new IllegalStateException("Unsupported Enumerated Color Space in ColorSpecification Box")); } else if (method==2){ if (csBox.getEnumeratedColorSpace()!=-1) errors.add(new IllegalStateException("ColorSpecification Box with method = 2 shouldn't have an Enumerated Color Space defined")); } else errors.add(new IllegalStateException("ColorSpecification Box only supports value 1 and 2 for the M parameter")); } // // // // Checking the ComponentMapping Box // // // else if (boxType == ComponentMappingBox.BOX_TYPE){ // final ComponentMappingBox cmBox = (ComponentMappingBox) child; //TODO: Add checks } // // // // Checking the Resolution Box // // // else if (boxType == ResolutionBox.BOX_TYPE){ // final ResolutionBox resBox = (ResolutionBox) child; //TODO: Add checks } } } } break; case ResolutionBox.BOX_TYPE: } if (errors.size()==0) return super.checkNodesInserted(node, childrenIndices, children); else return errors; } } private Map<JPEG2000FileType,PolicyCheck> policies = new HashMap<JPEG2000FileType, PolicyCheck>(); private DefaultTreeModel model; private JPEG2000FileType fileType= JPEG2000FileType.UNSPECIFIED; private Set<JPEG2000FileType> compatibilitySet; /** * */ public JP2KTreeController(final DefaultTreeModel model) { policies.put(JPEG2000FileType.JP2, new JP2Policy()); policies.put(JPEG2000FileType.JPX, new JPXPolicy()); policies.put(JPEG2000FileType.JPXB, new JPXPolicy()); policies.put(JPEG2000FileType.UNSPECIFIED, new UnspecifiedPolicy()); this.model=model; } /** * Return the index of a {@link JP2KBox} child from the specified parent * box, given the requested boxType. Return -1 if the child is not * found. * * @param parentBox * the parent box to be scan. * @param boxType * the type specifying the requested box. * @return the index of the desired child, -1 if not found. */ int getChildIndex(final JP2KBox parentBox, final int boxType) { if (parentBox == null) throw new IllegalArgumentException("Specified Parent box is null"); final int numRootChildren = parentBox.getChildCount(); for (int childN = 0; childN < numRootChildren; childN++) { TreeNode childNode = parentBox.getChildAt(childN); if (childNode != null && childNode instanceof JP2KBox) { JP2KBox childBox = (JP2KBox) parentBox.getChildAt(childN); if (childBox.getType() == boxType){ return childN; } } } return -1; } /** * @see javax.swing.event.TreeModelListener#treeNodesChanged(javax.swing.event.TreeModelEvent) */ public void treeNodesChanged(TreeModelEvent e) { System.out.println(e.toString()); } /** * @see javax.swing.event.TreeModelListener#treeNodesInserted(javax.swing.event.TreeModelEvent) */ public void treeNodesInserted(TreeModelEvent e) { if(e==null) return; // node to which we have added the children final TreePath path = e.getTreePath(); final Object root = path.getLastPathComponent(); if(!(root instanceof JP2KBox)) throw new IllegalStateException("Node is not a jp2k node"); final JP2KBox node = (JP2KBox) root; final int[] childrenIndices = e.getChildIndices(); final Object[] children = e.getChildren(); if (childrenIndices == null || children == null) return; List<? extends Throwable> exceptions = this.policies.get(this.fileType).checkNodesInserted(node, childrenIndices, children); if(LOGGER.isLoggable(Level.SEVERE)) for(Throwable t: exceptions) LOGGER.log(Level.SEVERE,t.getLocalizedMessage(),t); if(!exceptions.isEmpty()) // TODO Create a specific exception that wraps them all! throw new IllegalStateException("Check failed on this nodes insertion"); } /** * @see javax.swing.event.TreeModelListener#treeNodesRemoved(javax.swing.event.TreeModelEvent) */ public void treeNodesRemoved(TreeModelEvent e) { System.out.println(e.toString()); } /** * @see javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.event.TreeModelEvent) */ public void treeStructureChanged(TreeModelEvent e) { System.out.println(e.toString()); } public void checkTreeConsistency() { List<? extends Throwable> exceptions = this.policies.get(this.fileType).checkTreeConsistency(); if(LOGGER.isLoggable(Level.SEVERE)) for(Throwable t: exceptions) LOGGER.log(Level.SEVERE,t.getLocalizedMessage(),t); if(!exceptions.isEmpty()) // TODO Create a specific exception that wraps them all! throw new IllegalStateException("Check failed on tree consistency"); } }