001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * Copyright © 2012-2024 Open Geospatial Consortium, Inc. 004 * http://www.geoapi.org 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.opengis.test.coverage.image; 019 020import javax.imageio.ImageReader; 021import javax.imageio.ImageWriter; 022import javax.imageio.spi.ImageReaderSpi; 023import javax.imageio.spi.ImageWriterSpi; 024import javax.imageio.spi.ImageReaderWriterSpi; 025import javax.imageio.metadata.IIOMetadataFormat; 026import javax.imageio.metadata.IIOMetadataFormatImpl; 027 028import org.opengis.test.Validator; 029import org.opengis.test.ValidatorContainer; 030import org.opentest4j.AssertionFailedError; 031 032import static org.junit.jupiter.api.Assertions.*; 033 034 035/** 036 * Validators for implementations of {@link java.awt.image} or {@link javax.imageio} services. 037 * 038 * @author Martin Desruisseaux (Geomatys) 039 * @version 3.1 040 * @since 3.1 041 */ 042public class ImageValidator extends Validator { 043 /** 044 * Creates a new validator instance. 045 * 046 * @param container the set of validators to use for validating other kinds of objects 047 * (see {@linkplain #container field javadoc}). 048 */ 049 public ImageValidator(final ValidatorContainer container) { 050 super(container, "org.opengis.test.coverage.image"); 051 } 052 053 /** 054 * Validates the given provider of image readers. 055 * First, this method verifies that mandatory elements are non-null, arrays are non-empty 056 * (Image I/O specification requires them to be {@code null} rather than empty), and class 057 * names are valid. Next, this method invokes {@link #validate(IIOMetadataFormat)} for each 058 * metadata format (which can be null). 059 * 060 * @param provider the provider to validate, or {@code null} if none. 061 */ 062 public void validate(final ImageReaderSpi provider) { 063 if (provider != null) { 064 validateProvider(provider, ImageReader.class); 065 final Class<?>[] inputTypes = provider.getInputTypes(); 066 mandatory(inputTypes, "ImageReaderSpi: shall have an inputTypes array."); 067 validateArray("inputTypes", inputTypes); 068 final String[] imageWriterSpiNames = provider.getImageWriterSpiNames(); 069 validateArray("imageWriterSpiNames", imageWriterSpiNames); 070 if (imageWriterSpiNames != null) { 071 final ClassLoader loader = provider.getClass().getClassLoader(); 072 for (int i=0; i<imageWriterSpiNames.length; i++) { 073 final String field = "imageWriterSpiNames[" + i + ']'; 074 final String className = imageWriterSpiNames[i]; 075 assertNotNull(className, () -> field + " cannot be null."); 076 validateClass(field, ImageWriterSpi.class, loader, className); 077 } 078 } 079 } 080 } 081 082 /** 083 * Validates the given provider of image writers. 084 * First, this method verifies that mandatory elements are non-null, arrays are non-empty 085 * (Image I/O specification requires them to be {@code null} rather than empty), and class 086 * names are valid. Next, this method invokes {@link #validate(IIOMetadataFormat)} for each 087 * metadata format (which can be null). 088 * 089 * @param provider the provider to validate, or {@code null} if none. 090 */ 091 public void validate(final ImageWriterSpi provider) { 092 if (provider != null) { 093 validateProvider(provider, ImageWriter.class); 094 final Class<?>[] outputTypes = provider.getOutputTypes(); 095 mandatory(outputTypes, "ImageWriterSpi: shall have an outputTypes array."); 096 validateArray("outputTypes", outputTypes); 097 final String[] imageReaderSpiNames = provider.getImageReaderSpiNames(); 098 validateArray("imageReaderSpiNames", imageReaderSpiNames); 099 if (imageReaderSpiNames != null) { 100 final ClassLoader loader = provider.getClass().getClassLoader(); 101 for (int i=0; i<imageReaderSpiNames.length; i++) { 102 final String field = "imageReaderSpiNames[" + i + ']'; 103 final String className = imageReaderSpiNames[i]; 104 assertNotNull(className, () -> field + " cannot be null."); 105 validateClass(field, ImageReaderSpi.class, loader, className); 106 } 107 } 108 } 109 } 110 111 /** 112 * Validates the given image reader or image writer provider. 113 * 114 * @param spi the provider to validate, or {@code null} if none. 115 * @param pluginType expected provider base class. 116 */ 117 private void validateProvider(final ImageReaderWriterSpi spi, final Class<?> pluginType) { 118 mandatory(spi.getVendorName(), "ImageReaderWriterSpi: shall have a vendorName string."); 119 mandatory(spi.getVersion(), "ImageReaderWriterSpi: shall have a version string."); 120 final String[] formatNames = spi.getFormatNames(); 121 mandatory(formatNames, "ImageReaderWriterSpi: shall have a formatNames array."); 122 validateArray("formatNames", formatNames); 123 validateArray("fileSuffixes", spi.getFileSuffixes()); 124 validateArray("MIMETypes", spi.getMIMETypes()); 125 validateClass("pluginClassName", pluginType, spi.getClass().getClassLoader(), spi.getPluginClassName()); 126 validateMetadataFormatName("Stream", spi.getNativeStreamMetadataFormatName(), spi.getExtraStreamMetadataFormatNames()); 127 validateMetadataFormatName("Image", spi.getNativeImageMetadataFormatName(), spi.getExtraImageMetadataFormatNames()); 128 /* 129 * Ensures that a IIOMetadataFormat can be instantiated for each declared format name. 130 * Then, invokes validate(IIOMetadataFormat) for each format. Note that the format are 131 * allowed to be null according Image I/O specification. 132 */ 133 if (spi.isStandardStreamMetadataFormatSupported()) { 134 assertSame(IIOMetadataFormatImpl.getStandardFormatInstance(), 135 spi.getStreamMetadataFormat(IIOMetadataFormatImpl.standardMetadataFormatName), 136 "Expected the standard metadata format instance."); 137 } 138 if (spi.isStandardImageMetadataFormatSupported()) { 139 assertSame(IIOMetadataFormatImpl.getStandardFormatInstance(), 140 spi.getImageMetadataFormat(IIOMetadataFormatImpl.standardMetadataFormatName), 141 "Expected the standard metadata format instance."); 142 } 143 String formatName = spi.getNativeStreamMetadataFormatName(); 144 if (formatName != null) { 145 validate(spi.getStreamMetadataFormat(formatName)); 146 } 147 formatName = spi.getNativeImageMetadataFormatName(); 148 if (formatName != null) { 149 validate(spi.getImageMetadataFormat(formatName)); 150 } 151 String[] names = spi.getExtraStreamMetadataFormatNames(); 152 if (names != null) { 153 for (final String name : names) { 154 validate(spi.getStreamMetadataFormat(name)); 155 } 156 } 157 names = spi.getExtraImageMetadataFormatNames(); 158 if (names != null) { 159 for (final String name : names) { 160 validate(spi.getImageMetadataFormat(name)); 161 } 162 } 163 } 164 165 /** 166 * Validates the image or stream metadata format names. 167 * This method ensures that there is no duplicated values. 168 * 169 * @param type the type of metadata to validate: "Stream" or "Image". 170 * @param nativeMetadataFormatName the name of the native metadata format. 171 * @param extraMetadataFormatNames the name of the extra metadata format. 172 */ 173 private static void validateMetadataFormatName(final String type, String nativeMetadataFormatName, 174 final String[] extraMetadataFormatNames) 175 { 176 if (nativeMetadataFormatName != null) { 177 nativeMetadataFormatName = nativeMetadataFormatName.trim(); 178 assertFalse(IIOMetadataFormatImpl.standardMetadataFormatName.equalsIgnoreCase(nativeMetadataFormatName), 179 () -> "The native" + type + "MetadataFormatName value cannot be equal to \"" 180 + IIOMetadataFormatImpl.standardMetadataFormatName + "\"."); 181 } 182 if (extraMetadataFormatNames != null) { 183 final String field = "extra" + type + "MetadataFormatNames"; 184 validateArray(field, extraMetadataFormatNames); 185 for (int i=0; i<extraMetadataFormatNames.length; i++) { 186 final String formatName = extraMetadataFormatNames[i].trim(); 187 if (IIOMetadataFormatImpl.standardMetadataFormatName.equalsIgnoreCase(formatName)) { 188 fail("The " + field + '[' + i + "] value cannot be equal to \"" + 189 IIOMetadataFormatImpl.standardMetadataFormatName + "\"."); 190 } 191 if (nativeMetadataFormatName != null && nativeMetadataFormatName.equalsIgnoreCase(formatName)) { 192 fail("The " + field + '[' + i + "] value cannot be equal to \"" + nativeMetadataFormatName 193 + "\" because the latter is already declared as the native format name."); 194 } 195 } 196 } 197 } 198 199 /** 200 * Ensures that the given array complies with the conditions of Java Image I/O. 201 * The array can be either null or non-empty; empty arrays are illegal. 202 * Then ensures that there is no duplicated value. 203 * 204 * @param field the field name, used in case of errors. 205 * @param array the array to validate, or {@code null} if none. 206 */ 207 private static void validateArray(final String field, final Object[] array) { 208 if (array != null) { 209 assertNotEquals(array.length, 0, () -> "The " + field + " array shall be either null or non-empty."); 210 for (int i=0; i<array.length; i++) { 211 final Object element = array[i]; 212 if (element == null) { 213 fail(field + '[' + i + "] is null."); 214 } 215 for (int j=i; ++i<array.length;) { 216 if (element.equals(array[i])) { 217 fail(field + '[' + i + "] and [" + j + "] duplicate the \"" + element + "\" value."); 218 } 219 } 220 } 221 } 222 } 223 224 /** 225 * Ensure that given class exists and is an instance of the given type. 226 * 227 * @param field the name of the tested field. 228 * @param expectedType the expected base class. 229 * @param loader the loader to use for loading the class. 230 * @param classname the name of the class to test. 231 */ 232 private void validateClass(final String field, final Class<?> expectedType, 233 final ClassLoader loader, final String classname) 234 { 235 mandatory(classname, "ImageReaderWriterSpi: shall have a " + field + " string."); 236 if (classname != null) try { 237 final Class<?> actual = Class.forName(classname, false, loader); 238 assertTrue(expectedType.isAssignableFrom(actual), 239 () -> actual.getCanonicalName() + " is not an instance of " + expectedType.getSimpleName() + '.'); 240 } catch (ClassNotFoundException e) { 241 throw new AssertionFailedError("Class \"" + classname + "\" declared in " + field + " was not found.", e); 242 } 243 } 244 245 /** 246 * Validates the given metadata format. 247 * 248 * @param format the metadata format to validate, or {@code null} if none. 249 */ 250 public void validate(final IIOMetadataFormat format) { 251 // Not yet implemented. 252 } 253}