/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012-2016, 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.coverage.filestore; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.imageio.ImageReader; import javax.imageio.ImageWriter; import javax.imageio.spi.ImageReaderSpi; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.storage.coverage.AbstractCoverageStore; import org.geotoolkit.storage.coverage.CoverageStoreFactory; import org.geotoolkit.storage.coverage.CoverageType; import org.geotoolkit.util.NamesExt; import org.geotoolkit.image.io.NamedImageStore; import org.geotoolkit.image.io.UnsupportedImageFormatException; import org.geotoolkit.image.io.XImageIO; import org.geotoolkit.parameter.Parameters; import org.geotoolkit.utility.parameter.ParametersExt; import org.geotoolkit.storage.DataFileStore; import org.geotoolkit.storage.DataNode; import org.geotoolkit.storage.DataStores; import org.geotoolkit.storage.DefaultDataNode; import org.opengis.util.GenericName; import org.opengis.parameter.ParameterValueGroup; /** * Coverage Store which rely on standard java readers and writers. * * @author Johann Sorel (Geomatys) * @module */ public class FileCoverageStore extends AbstractCoverageStore implements DataFileStore { private static final String REGEX_SEPARATOR; static { if (File.separatorChar == '\\') { REGEX_SEPARATOR = "\\\\"; } else { REGEX_SEPARATOR = File.separator; } } private final Path root; private final String format; private final URI rootPath; private final String separator; //initialized at first access, this is not done in the constructor to //ensure whoever created the store to be able to attach warning listeners on it. private DataNode rootNode; //default spi final ImageReaderSpi spi; public FileCoverageStore(URL url, String format) throws URISyntaxException, IOException { this(toParameters(url.toURI(), format)); } public FileCoverageStore(Path path, String format) throws URISyntaxException, IOException { this(toParameters(path.toUri(), format)); } public FileCoverageStore(URI uri, String format) throws URISyntaxException, IOException { this(toParameters(uri, format)); } public FileCoverageStore(ParameterValueGroup params) throws URISyntaxException, IOException { super(params); rootPath = (URI) params.parameter(FileCoverageStoreFactory.PATH.getName().getCode()).getValue(); root = Paths.get(rootPath); format = (String) params.parameter(FileCoverageStoreFactory.TYPE.getName().getCode()).getValue(); if("AUTO".equalsIgnoreCase(format)){ spi = null; }else{ spi = XImageIO.getReaderSpiByFormatName(format); } separator = Parameters.value(FileCoverageStoreFactory.PATH_SEPARATOR, params); } private static ParameterValueGroup toParameters(URI uri, String format){ final ParameterValueGroup params = FileCoverageStoreFactory.PARAMETERS_DESCRIPTOR.createValue(); ParametersExt.getOrCreateValue(params,"path").setValue(uri); if(format!=null){ ParametersExt.getOrCreateValue(params,"type").setValue(format); } return params; } @Override public CoverageStoreFactory getFactory() { return (CoverageStoreFactory) DataStores.getFactoryById(FileCoverageStoreFactory.NAME); } @Override public synchronized DataNode getRootNode() throws DataStoreException{ if(rootNode==null){ rootNode = new DefaultDataNode(); try { visit(root); } catch (DataStoreException ex) { rootNode = null; throw ex; }catch (IOException ex) { rootNode = null; throw new DataStoreException(ex.getMessage(),ex); } } return rootNode; } /** * Visit all files and directories contained in the directory specified. * * @param file */ private void visit(final Path file) throws IOException, DataStoreException { if (Files.isRegularFile(root)) { //we opened a single file, we consider it as a real error try { test(file); } catch (Exception ex) { throw new DataStoreException(ex.getMessage(), ex); } } else { //explore as a folder, we only throw warnings for unsupported files. //this behavior ensure the store will be opened even if a few files are corrupted. Files.walkFileTree(file, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { try { test(file); } catch (UnsupportedImageFormatException ex) { // Tried to parse a incompatible file, not really an error. final LogRecord rec = new LogRecord(Level.FINE, "Unsupported image format encoding or compression for file "+IOUtilities.filename(file)+" : "+ex.getMessage()); rec.setThrown(ex); listeners.warning(rec); } catch (Exception ex) { //Exception type is not specified cause we can get IOException as IllegalArgumentException. final LogRecord rec = new LogRecord(Level.WARNING, "Exception occured decoding file "+IOUtilities.filename(file)+" : "+ex.getMessage()); rec.setThrown(ex); listeners.warning(rec); } return FileVisitResult.CONTINUE; } }); } } private String createLayerName(final Path candidate) { if (separator != null) { //TODO use relativize() final Path absRoot = root.toAbsolutePath(); final Path abscandidate = candidate.toAbsolutePath(); String fullName = abscandidate.toString().replace(absRoot.toString(), ""); if (fullName.startsWith(File.separator)) { fullName = fullName.substring(1); } fullName = fullName.replaceAll(REGEX_SEPARATOR, separator); final int idx = fullName.lastIndexOf('.'); return fullName.substring(0, idx); } else { return IOUtilities.filenameWithoutExtension(candidate); } } /** * * @param candidate Candidate to be a image file. */ private void test(final Path candidate) throws Exception { if (!Files.isRegularFile(candidate)) { return; } ImageReader reader = null; try { //don't comment this block, This raise an error if no reader for the file can be found //this way we are sure that the file is an image. reader = createReader(candidate, spi); final String nmsp = getDefaultNamespace(); final String filename = createLayerName(candidate); final int nbImage = reader.getNumImages(true); if (reader instanceof NamedImageStore) { //try to find a proper name for each image final NamedImageStore nis = (NamedImageStore) reader; final List<String> imageNames = nis.getImageNames(); for (int i = 0, n = imageNames.size(); i < n; i++) { final String in = imageNames.get(i); final GenericName name = NamesExt.create(nmsp, filename + "." + in); final FileCoverageReference fcr = new FileCoverageReference(this, name, candidate, i); rootNode.getChildren().add(fcr); } } else { for (int i = 0; i < nbImage; i++) { final GenericName name; if (nbImage == 1) { //don't number it if there is only one name = NamesExt.create(nmsp, filename); } else { name = NamesExt.create(nmsp, filename + "." + i); } final FileCoverageReference fcr = new FileCoverageReference(this, name, candidate, i); rootNode.getChildren().add(fcr); } } } finally { XImageIO.disposeSilently(reader); } } @Override public void close() { } /** * Create a reader for the given file. * Detect automatically the spi if type is set to 'AUTO'. * * @param candidate file to read * @param spi used to create ImageReader. If null, detect automatically from candidate file. * @return ImageReader, never null * @throws IOException if fail to create a reader. * @throws UnsupportedImageFormatException if spi is defined but can't decode candidate file */ ImageReader createReader(final Path candidate, ImageReaderSpi spi) throws IOException{ final ImageReader reader; if(spi == null){ if (!IOUtilities.extension(candidate).isEmpty()) { reader = XImageIO.getReaderBySuffix(candidate, Boolean.FALSE, Boolean.FALSE); } else { reader = XImageIO.getReader(candidate,Boolean.FALSE,Boolean.FALSE); } }else{ if (spi.canDecodeInput(candidate)) { reader = spi.createReaderInstance(); Object in = XImageIO.toSupportedInput(spi, candidate); reader.setInput(in); } else { throw new UnsupportedImageFormatException("Unsupported file input for spi "+spi.getPluginClassName()); } } return reader; } /** * Create a writer for the given file. * Detect automatically the spi if type is set to 'AUTO'. * * @param candidate * @return ImageWriter, never null * @throws IOException if fail to create a writer. */ ImageWriter createWriter(final Path candidate) throws IOException{ final ImageReaderSpi readerSpi = createReader(candidate,spi).getOriginatingProvider(); final String[] writerSpiNames = readerSpi.getImageWriterSpiNames(); if(writerSpiNames == null || writerSpiNames.length == 0){ throw new IOException("No writer for this format."); } return XImageIO.getWriterByFormatName(readerSpi.getFormatNames()[0], candidate, null); } @Override public CoverageType getType() { return CoverageType.GRID; } @Override public Path[] getDataFiles() throws DataStoreException { return new Path[] {root}; } }