/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2015, Geomatys
*
* 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;
* version 2.1 of the License.
*
* 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 org.geotoolkit.image.io.plugin.yaml;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlCategory;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlSampleDimension;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlImageInfo;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlWriterBuilder;
import java.beans.IntrospectionException;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlBuilder;
import org.geotoolkit.image.io.plugin.yaml.internal.YamlSampleCategory;
import org.opengis.coverage.SampleDimension;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
/**
* Aggregate some needed method to read or write image informations into Yaml format.<br><br>
*
* Writing code example :<br><br>
*
* final List<SampleDimension> myListToWrite;<br>
* final YamlWriterBuilder myYamlWriterBuilder = YamlFiles.getBuilder();<br>
* myYamlWriterBuilder.setSampleDimensions(myListToWrite);<br><br>
*
* //-- to write in file path<br>
* final File myPath = new File("myPath");<br>
* YamlFiles.write(myPath, yamBuild);<br><br>
*
* //-- just to dump into String object<br>
* YamlFiles.dump(yamBuild);<br>
* <br><br>
*
* Reading code example : <br>
*
* final Class generatedCategoryDatatype = Byte.class; //-- for example, also could be Double, Float, Short, Integer<br><br>
*
* //-- to read from file<br>
* final File myPath = new File("myPathToRead");<br>
* final List<SampleDimension> myListToWrite = YamlFiles.read(myPath, generatedCategoryDatatype);<br><br>
*
* //-- to load from String object<br>
* final List<SampleDimension> myListToWrite = YamlFiles.load(dumpResult, generatedCategoryDatatype);<br>
*
*
* @author Remi Marechal (Geomatys).
* @since 4.0
*/
public final class YamlFiles {
/**
* Logger to diffuse no blocking error message.
*/
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.image.io.plugin.yaml");
/**
* Define fields order for {@link YamlCategory} attributs.
*/
private static final String[] YAML_CATEGORIES_FIELD_ORDER = new String[]{"name",
"minSampleValue",
"isMinInclusive",
"maxSampleValue",
"isMaxInclusive",
"value",
"scale",
"offset"};
/**
* Define fields order for {@link YamlSampleDimension} attributs.
*/
private static final String[] YAML_SAMPLEDIMS_FIELD_ORDER = new String[]{"description",
"categories"};
/**
* Define fields order for {@link YamlImageInfo} attributs.
*/
private static final String[] YAML_INFO_FIELD_ORDER = new String[]{"version",
"sampleDimension"};
/**
* Multiple Yaml options for object dumping (writing).
*/
private static final DumperOptions DUMPER_OPTION = new DumperOptions();
/**
* Define properties to how interpret class name.
*/
private static final Representer DUMP_REPRESENTER = new NullRepresenter();
/**
* {@link Constructor} to define List and map attributs properties.
*/
private static final Constructor DUMP_CONSTRUCTOR = new Constructor();
/**
* {@link Constructor} to define attribut load.
*/
private static final Constructor LOAD_CONSTRUCTOR = new Constructor();
static {
//-- one attribut per line
DUMPER_OPTION.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
//-- replace class name by choosen title
DUMP_REPRESENTER.addClassTag(YamlImageInfo.class, Tag.MAP);
DUMP_REPRESENTER.addClassTag(YamlSampleCategory.class, Tag.MAP);
//-- to organize attribut writing order
DUMP_REPRESENTER.setPropertyUtils(new writerPropertyUtils());
//-- define internal list and map attributs
final TypeDescription imageInfoTypeDescription = new TypeDescription(YamlImageInfo.class);
imageInfoTypeDescription.putListPropertyType("sampleDimension", YamlSampleDimension.class);
final TypeDescription sampleDimensionTypeDescription = new TypeDescription(YamlSampleDimension.class);
sampleDimensionTypeDescription.putListPropertyType("categories", YamlCategory.class);
DUMP_CONSTRUCTOR.addTypeDescription(imageInfoTypeDescription);
DUMP_CONSTRUCTOR.addTypeDescription(sampleDimensionTypeDescription);
//--------------------------- Read ---------------------------------
LOAD_CONSTRUCTOR.addTypeDescription(imageInfoTypeDescription);
LOAD_CONSTRUCTOR.addTypeDescription(sampleDimensionTypeDescription);
LOAD_CONSTRUCTOR.addTypeDescription(new TypeDescription(YamlImageInfo.class, Tag.MAP));
LOAD_CONSTRUCTOR.addTypeDescription(new TypeDescription(YamlSampleCategory.class, Tag.MAP));
}
/**
* Read Yaml informations from {@link File}.<br>
* If there was any parsing problem during reading action, an {@link Collections#EMPTY_LIST} will be return.
*
* @param path path to Yaml file informations.
* @param sampleType sample data type to interprete categories values. (double, short, ...)
* @return read {@link SampleDimension} {@link List},
* or {@linkplain Collections#EMPTY_LIST empty list} if none or any reading problem.
* @throws IllegalStateException if version doesn't match.
*/
public static List<SampleDimension> read(final File path, final Class sampleType) throws IOException {
final Yaml yamyam = new Yaml(LOAD_CONSTRUCTOR);
final Object obj;
try (FileReader fileReader = new FileReader(path)) {
obj = yamyam.load(fileReader);
}
if (!(obj instanceof Map))
throw new IllegalStateException("Expected result from Yaml file reading should be instance of Map.");
final YamlBuilder yamBuild = new YamlBuilder((Map<String, Object>) obj, sampleType);
final List<SampleDimension> result = yamBuild.getSampleDimensions();
if (result.isEmpty())
LOGGER.log(Level.FINE, "Yaml image informations could not be read.");
return result;
}
/**
* Read Yaml informations from {@link String}.<br>
* If there was any parsing problem during reading action, an {@link Collections#EMPTY_LIST} will be return.
*
* @param yaml path to Yaml file informations.
* @param sampleType sample data type to interprete categories values. (double, short, ...)
* @return read {@link SampleDimension} {@link List},
* or {@linkplain Collections#EMPTY_LIST empty list} if none or any reading problem.
* @throws IllegalStateException if version doesn't match.
*/
public static List<SampleDimension> load(final String yaml, final Class sampleType) throws IOException {
final Yaml yamyam = new Yaml(LOAD_CONSTRUCTOR);
final Object obj = yamyam.load(yaml);
if (!(obj instanceof Map))
throw new IllegalStateException("Expected result from Yaml file reading should be instance of Map.");
final YamlBuilder yamBuild = new YamlBuilder((Map<String, Object>) obj, sampleType);
final List<SampleDimension> result = yamBuild.getSampleDimensions();
if (result.isEmpty())
LOGGER.log(Level.FINE, "Yaml image informations could not be read.");
return result;
}
/**
* Returns needed builder to write image informations into Yaml format.
*
* @return builder to write image informations into Yaml format.
* @see YamlBuilder
*/
public static YamlWriterBuilder getBuilder() {
return new YamlBuilder();
}
/**
* Returns {@String} object which will be the serialization of image informations
* from {@link YamlWriterBuilder} into Yaml format.
*
* @param imageInfoBuilder regroup all image informations which will be shortly serialized.
* @return serialization of image informations into Yaml format.
*/
public static String dump(final YamlWriterBuilder imageInfoBuilder) {
//-- create YamlImageInfo
final YamlImageInfo yii = new YamlImageInfo(imageInfoBuilder);
final Yaml yamyam = new Yaml(DUMP_CONSTRUCTOR, DUMP_REPRESENTER, DUMPER_OPTION);
return yamyam.dump(yii);
}
/**
* Write image informations from precedently filled {@link YamlWriterBuilder}
* into Yaml format at specified emplacement given by path parameter.
*
* @param path emplacement where Yaml file is create.
* @param imageInfoBuilder regroup all image informations which will be shortly written.
* @throws IOException if problem during Yaml file writing.
*/
public static void write(final File path, final YamlWriterBuilder imageInfoBuilder) throws IOException {
final FileWriter fileW = new FileWriter(path);
fileW.write(dump(imageInfoBuilder));
fileW.flush();
fileW.close();
}
/**
* Override Yaml object to avoid Yaml {@code null} value dumping.
*/
private static class NullRepresenter extends Representer {
@Override
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
if (propertyValue == null) {
return null;
}
return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); //To change body of generated methods, choose Tools | Templates.
}
}
/**
* Override Yaml object to choose writing order of internal class fields.
*/
private static class writerPropertyUtils extends PropertyUtils {
@Override
protected Set<Property> createPropertySet(Class<? extends Object> type, BeanAccess bAccess)
throws IntrospectionException {
final Set<Property> result = new LinkedHashSet<Property>();
final Map<String, Property> props = getPropertiesMap(type, BeanAccess.DEFAULT);
final String[] orderArray;
if (type.isAssignableFrom(org.geotoolkit.image.io.plugin.yaml.internal.YamlCategory.class)
|| type.isAssignableFrom(org.geotoolkit.image.io.plugin.yaml.internal.YamlSampleCategory.class)) {
orderArray = YAML_CATEGORIES_FIELD_ORDER;
} else if (type.isAssignableFrom(org.geotoolkit.image.io.plugin.yaml.internal.YamlSampleDimension.class)) {
orderArray = YAML_SAMPLEDIMS_FIELD_ORDER;
} else if (type.isAssignableFrom(org.geotoolkit.image.io.plugin.yaml.internal.YamlImageInfo.class)) {
orderArray = YAML_INFO_FIELD_ORDER;
} else {
orderArray = null;
}
//-- add properties elements in choosen order.
if (orderArray != null)
for (final String strKey : orderArray) {
for(final String key : props.keySet()) {
if (strKey.equalsIgnoreCase(key)) {
result.add(props.remove(key));
break;
}
}
}
//-- add remaining properties or all properties for other classes.
result.addAll(props.values());
return result;
}
}
}