org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.java

Source

/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 * 
 *    (C) 2008, Open Source Geospatial Foundation (OSGeo)
 *
 *    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.geotools.gce.imagemosaic.catalogbuilder;

import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.SampleModel;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.SwingUtilities;

import org.apache.commons.io.DirectoryWalker;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FalseFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.HiddenFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.coverage.grid.io.UnknownFormat;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.gce.image.WorldImageFormat;
import org.geotools.gce.imagemosaic.MosaicConfigurationBean;
import org.geotools.gce.imagemosaic.Utils;
import org.geotools.gce.imagemosaic.Utils.Prop;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalog;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalogFactory;
import org.geotools.gce.imagemosaic.properties.PropertiesCollector;
import org.geotools.gce.imagemosaic.properties.PropertiesCollectorFinder;
import org.geotools.gce.imagemosaic.properties.PropertiesCollectorSPI;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.io.ImageIOExt;
import org.geotools.referencing.CRS;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.util.Utilities;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;

import com.sun.media.imageioimpl.common.BogusColorSpace;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;

/**
 * This class is in responsible for creating the index for a mosaic of images
 * that we want to tie together as a single coverage.
 * 
 * @author Simone Giannecchini, GeoSolutions
 * 
 * @source $URL$
 */
@SuppressWarnings("rawtypes")
public class CatalogBuilder implements Runnable {

    final private static double RESOLUTION_TOLERANCE_FACTOR = 1E-2;

    /** Default Logger * */
    final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(CatalogBuilder.class);

    static abstract public class ProcessingEventListener implements EventListener {

        abstract public void getNotification(final ProcessingEvent event);

        abstract public void exceptionOccurred(final ExceptionEvent event);

    }

    /**
     * @author Simone Giannecchini, GeoSolutions.
     * 
     */
    static public class ProcessingEvent extends EventObject {

        private static final long serialVersionUID = 6930580659705360225L;

        private String message = null;

        private double percentage = 0;

        /**
         * @param source
         */
        public ProcessingEvent(final Object source, final String message, final double percentage) {
            super(source);
            this.message = message;
            this.percentage = percentage;
        }

        public double getPercentage() {
            return percentage;
        }

        public String getMessage() {
            return message;
        }

    }

    /**
     * Event launched when an exception occurs. Percentage and message may be missing, in this case
     * they will be -1 and the exception message (localized if available, standard otherwise)
     * 
     * @author aaime, TOPP.
     * 
     */
    static public final class ExceptionEvent extends ProcessingEvent {

        private static final long serialVersionUID = 2272452028229922551L;

        private Exception exception;

        public ExceptionEvent(Object source, String message, double percentage, Exception exception) {
            super(source, message, percentage);
            this.exception = exception;
        }

        public ExceptionEvent(Object source, Exception exception) {
            super(source, Utils.getMessageFromException(exception), -1);
            this.exception = exception;
        }

        public Exception getException() {
            return exception;
        }

    }

    /**
     * Private Class which simply fires the events using a copy of the listeners
     * list in order to avoid problems with listeners that remove themselves or
     * are removed by someone else
     */
    final static class ProgressEventDispatchThreadEventLauncher implements Runnable {

        /**
         * The event we want to fire away.
         */
        private ProcessingEvent event;

        /**
         * The list of listeners.
         */
        private Object[] listeners;

        /**
         * Default constructor.
         * 
         */
        ProgressEventDispatchThreadEventLauncher() {
        }

        /**
         * Used to send an event to an array of listeners.
         * 
         * @param evt
         *            is the {@link ProcessingEvent} to send.
         * @param listeners
         *            is the array of {@link ProcessingEventListener}s to
         *            notify.
         */
        synchronized void setEvent(final ProcessingEvent evt, final Object[] listeners) {
            if (listeners == null || evt == null)
                throw new NullPointerException("Input argumentBuilder cannot be null");
            this.listeners = listeners;
            this.event = evt;

        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Runnable#run()
         */
        public void run() {
            final int numListeners = listeners.length;
            if (event instanceof ExceptionEvent)
                for (int i = 0; i < numListeners; i++)
                    ((ProcessingEventListener) listeners[i]).exceptionOccurred((ExceptionEvent) this.event);
            else
                for (int i = 0; i < numListeners; i++)
                    ((ProcessingEventListener) listeners[i]).getNotification(this.event);
        }

    }

    /**
     * This class is responsible for walking through he files inside a directory
     * (and its children directories) which respect a specified wildcard.
     * 
     * <p>
     * Its role is basically to simplify the construction of the mosaic by
     * implementing a visitor pattern for the files that we have to use for the
     * index.
     * 
     * <p>
     * It is based on the Commons IO {@link DirectoryWalker} class.
     * 
     * @author Simone Giannecchini, GeoSolutions SAS
     * 
     */
    final class CatalogBuilderDirectoryWalker extends DirectoryWalker {

        private DefaultTransaction transaction;
        private volatile boolean canceled;

        @Override
        protected void handleCancelled(File startDirectory, Collection results, CancelException cancel)
                throws IOException {
            super.handleCancelled(startDirectory, results, cancel);
            //clean up objects and rollback transaction
            if (LOGGER.isLoggable(Level.INFO))
                LOGGER.info("Stop requested when walking directory " + startDirectory);
            super.handleEnd(results);
        }

        @Override
        protected boolean handleIsCancelled(final File file, final int depth, Collection results)
                throws IOException {

            //
            // Anyone has asked us to stop?
            //
            if (!checkStop()) {
                canceled = true;
                return true;
            }
            return false;
        }

        @Override
        protected void handleFile(final File fileBeingProcessed, final int depth, final Collection results)
                throws IOException {

            // increment counter
            fileIndex++;

            //
            // Check that this file is actually good to go
            //
            if (!checkFile(fileBeingProcessed))
                return;

            // replacing chars on input path
            String validFileName;
            try {
                validFileName = fileBeingProcessed.getCanonicalPath();
                validFileName = FilenameUtils.normalize(validFileName);
            } catch (IOException e1) {
                fireException(e1);
                return;
            }
            validFileName = FilenameUtils.getName(validFileName);
            fireEvent(Level.INFO, "Now indexing file " + validFileName, ((fileIndex * 100.0) / numFiles));
            ImageInputStream inStream = null;
            ImageReader imageioReader = null;
            AbstractGridCoverage2DReader coverageReader = null;
            try {
                //
                // STEP 1
                // Getting an ImageIO reader for this coverage.
                //
                // try to use cache
                if (cachedStreamSPI != null) {
                    inStream = cachedStreamSPI.createInputStreamInstance(fileBeingProcessed);
                }
                if (inStream == null) {
                    // failed, look for a new SPI
                    cachedStreamSPI = ImageIOExt.getImageInputStreamSPI(fileBeingProcessed);
                    if (cachedStreamSPI != null) {
                        inStream = cachedStreamSPI.createInputStreamInstance(fileBeingProcessed);
                    }

                }
                if (inStream == null) {
                    // failed again
                    fireEvent(Level.INFO,
                            fileBeingProcessed + " has been skipped since we could not get a stream for it",
                            ((fileIndex * 100.0) / numFiles));
                    return;
                }
                inStream.mark();

                cachedReaderSPITest: {
                    // there is no cached reader spi, let's look for one
                    if (cachedReaderSPI == null) {
                        final Iterator<ImageReader> it = ImageIO.getImageReaders(inStream);
                        if (it.hasNext()) {
                            imageioReader = it.next();
                            if (imageioReader != null) {
                                //cache the SPI
                                cachedReaderSPI = imageioReader.getOriginatingProvider();
                                imageioReader.setInput(inStream);
                            }
                        } else {
                            imageioReader = null;
                        }
                    } else {
                        // we have a cached SPI, let's try to use it
                        if (!cachedReaderSPI.canDecodeInput(inStream)) {
                            // the SPI is no good for this input
                            cachedReaderSPI = null;
                            //take me to the SPI search
                            break cachedReaderSPITest;
                        }
                        // the spi is good
                        imageioReader = cachedReaderSPI.createReaderInstance();
                        imageioReader.setInput(inStream);
                    }
                }
                // did we found a reader
                if (imageioReader == null) {
                    // send a message
                    fireEvent(Level.INFO,
                            new StringBuilder("Skipped file ").append(fileBeingProcessed)
                                    .append(":No ImageIO reader   s availaible.").toString(),
                            ((fileIndex * 99.0) / numFiles));
                    return;
                }

                //Append
                // STEP 2
                // Getting a coverage reader for this coverage.
                //
                final AbstractGridFormat format;
                if (cachedFormat == null) {
                    format = (AbstractGridFormat) GridFormatFinder.findFormat(fileBeingProcessed);
                } else {
                    if (cachedFormat.accepts(fileBeingProcessed)) {
                        format = cachedFormat;
                    } else {
                        format = new UnknownFormat();
                    }
                }
                if ((format instanceof UnknownFormat) || format == null) {
                    fireEvent(Level.INFO,
                            new StringBuilder("Skipped file ").append(fileBeingProcessed)
                                    .append(": File format is not supported.").toString(),
                            ((fileIndex * 99.0) / numFiles));
                    return;
                }
                cachedFormat = format;
                coverageReader = (AbstractGridCoverage2DReader) format.getReader(fileBeingProcessed,
                        runConfiguration.getHints());
                GeneralEnvelope envelope = (GeneralEnvelope) coverageReader.getOriginalEnvelope();
                CoordinateReferenceSystem actualCRS = coverageReader.getCrs();

                //
                // STEP 3
                // Get the type specifier for this image and the check that the
                // image has the correct sample model and color model.
                // If this is the first cycle of the loop we initialize everything.
                //
                final ImageTypeSpecifier its = ((ImageTypeSpecifier) imageioReader.getImageTypes(0).next());
                if (numberOfProcessedFiles == 0) {

                    //
                    // at the first step we initialize everything that we will
                    // reuse afterwards starting with color models, sample
                    // models, crs, etc....
                    //

                    defaultCM = its.getColorModel();
                    defaultSM = its.getSampleModel();
                    if (defaultCM instanceof IndexColorModel) {
                        IndexColorModel icm = (IndexColorModel) defaultCM;
                        int numBands = defaultCM.getNumColorComponents();
                        defaultPalette = new byte[3][icm.getMapSize()];
                        icm.getReds(defaultPalette[0]);
                        icm.getGreens(defaultPalette[0]);
                        icm.getBlues(defaultPalette[0]);
                        if (numBands == 4)
                            icm.getAlphas(defaultPalette[0]);

                    }
                    defaultCRS = actualCRS;

                    //
                    // getting information about resolution
                    //

                    // get the dimension of the hr image and build the model
                    // as well as computing the resolution
                    // 
                    // resetting reader and recreating stream, turnaround for a
                    // strange imageio bug that sometimes pops up
                    imageioReader.reset();
                    try {
                        inStream.reset();
                    } catch (IOException e) {
                        //close me and reopen me
                        try {
                            inStream.close();
                        } catch (Throwable e1) {
                            if (LOGGER.isLoggable(Level.FINE))
                                LOGGER.log(Level.FINE, e1.getLocalizedMessage(), e1);
                        }
                        inStream = ImageIO.createImageInputStream(fileBeingProcessed);
                    }

                    //let's check if we got something now
                    if (inStream == null) {
                        //skip file
                        fireEvent(Level.INFO,
                                fileBeingProcessed + " has been skipped since we could not get a stream for it",
                                ((fileIndex * 100.0) / numFiles));
                        return;
                    }
                    imageioReader.setInput(inStream);
                    int numberOfLevels = imageioReader.getNumImages(true);
                    double[][] resolutionLevels = new double[2][numberOfLevels];
                    setupResolutions(resolutionLevels, numberOfLevels, coverageReader, imageioReader, null);
                    mosaicConfiguration.setLevelsNum(numberOfLevels);
                    mosaicConfiguration.setLevels(resolutionLevels);

                    //
                    // creating the schema
                    //

                    String schema = runConfiguration.getSchema();
                    if (schema != null) {
                        schema = schema.trim();
                        // get the schema
                        try {
                            indexSchema = DataUtilities.createType(mosaicConfiguration.getName(), schema);
                            //override the crs in case the provided one was wrong or absent
                            indexSchema = DataUtilities.createSubType(indexSchema,
                                    DataUtilities.attributeNames(indexSchema), actualCRS);
                        } catch (Throwable e) {
                            if (LOGGER.isLoggable(Level.FINE))
                                LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
                            indexSchema = null;
                        }
                    }
                    if (indexSchema == null) {
                        final SimpleFeatureTypeBuilder featureBuilder = new SimpleFeatureTypeBuilder();
                        featureBuilder.setName(runConfiguration.getIndexName());
                        featureBuilder.setNamespaceURI("http://www.geo-solutions.it/");
                        featureBuilder.add(runConfiguration.getLocationAttribute().trim(), String.class);
                        featureBuilder.add("the_geom", Polygon.class, actualCRS);
                        featureBuilder.setDefaultGeometry("the_geom");
                        if (runConfiguration.getTimeAttribute() != null)
                            featureBuilder.add(runConfiguration.getTimeAttribute().trim(), Date.class);
                        indexSchema = featureBuilder.buildFeatureType();
                    }
                    // create the schema for the new shape file
                    catalog.createType(indexSchema);

                } else {
                    if (!mosaicConfiguration.isHeterogeneous()) {
                        // //
                        //
                        // There is no need to check resolutions if the mosaic
                        // has been already marked as heterogeneous
                        //
                        // //
                        int numberOfLevels = imageioReader.getNumImages(true);
                        if (numberOfLevels != mosaicConfiguration.getLevelsNum()) {
                            mosaicConfiguration.setHeterogeneous(true);
                            if (numberOfLevels > mosaicConfiguration.getLevelsNum()) {
                                final double[][] resolutionLevels = new double[2][numberOfLevels];
                                setupResolutions(resolutionLevels, numberOfLevels, coverageReader, imageioReader,
                                        null);
                                mosaicConfiguration.setLevelsNum(numberOfLevels);
                                mosaicConfiguration.setLevels(resolutionLevels);
                            }
                        } else {
                            final double[][] mosaicLevels = mosaicConfiguration.getLevels();
                            final double[][] resolutionLevels = new double[2][numberOfLevels];
                            final boolean homogeneousLevels = setupResolutions(resolutionLevels, numberOfLevels,
                                    coverageReader, imageioReader, mosaicLevels);
                            if (!homogeneousLevels) {
                                mosaicConfiguration.setHeterogeneous(true);
                            }
                        }
                    }

                    // ////////////////////////////////////////////////////////
                    // 
                    // comparing ColorModel
                    // comparing SampeModel
                    // comparing CRSs
                    // ////////////////////////////////////////////////////////
                    ColorModel actualCM = its.getColorModel();
                    if ((fileIndex > 0 ? !(CRS.equalsIgnoreMetadata(defaultCRS, actualCRS)) : false)) {
                        fireEvent(Level.INFO,
                                new StringBuilder("Skipping image ").append(fileBeingProcessed)
                                        .append(" because CRSs do not match.").toString(),
                                (((fileIndex + 1) * 99.0) / numFiles));
                        return;
                    }

                    if (checkColorModels(defaultCM, defaultPalette, actualCM)) {
                        fireEvent(Level.INFO,
                                new StringBuilder("Skipping image ").append(fileBeingProcessed)
                                        .append(" because color models do not match.").toString(),
                                (((fileIndex + 1) * 99.0) / numFiles));
                        return;
                    }
                }

                // ////////////////////////////////////////////////////////
                //
                // STEP 4
                //
                // create and store features
                //
                // ////////////////////////////////////////////////////////
                final SimpleFeature feature = DataUtilities.template(indexSchema);
                feature.setAttribute(indexSchema.getGeometryDescriptor().getLocalName(),
                        geomFactory.toGeometry(new ReferencedEnvelope((Envelope) envelope)));
                feature.setAttribute(runConfiguration.getLocationAttribute(), prepareLocation(fileBeingProcessed));

                // collect and dump properties
                if (propertiesCollectors != null && propertiesCollectors.size() > 0)
                    for (PropertiesCollector pc : propertiesCollectors) {
                        pc.collect(fileBeingProcessed).collect(coverageReader).collect(imageioReader)
                                .setProperties(feature);
                        pc.reset();
                    }

                catalog.addGranule(feature, transaction);

                // fire event
                fireEvent(Level.FINE, "Done with file " + fileBeingProcessed,
                        (((fileIndex + 1) * 99.0) / numFiles));

                // advance files
                numberOfProcessedFiles++;
            } catch (IOException e) {
                fireException(e);
                return;
            } catch (ArrayIndexOutOfBoundsException e) {
                fireException(e);
                return;
            } finally {
                // ////////////////////////////////////////////////////////
                //
                // STEP 5
                //
                // release resources
                //
                // ////////////////////////////////////////////////////////
                try {
                    if (inStream != null)
                        inStream.close();
                } catch (Throwable e) {
                    // ignore exception
                    if (LOGGER.isLoggable(Level.FINEST))
                        LOGGER.log(Level.FINEST, e.getLocalizedMessage(), e);
                }
                try {
                    if (imageioReader != null)
                        imageioReader.dispose();
                } catch (Throwable e) {
                    // ignore exception
                    if (LOGGER.isLoggable(Level.FINEST))
                        LOGGER.log(Level.FINEST, e.getLocalizedMessage(), e);
                }

                try {
                    if (coverageReader != null)
                        // release resources
                        coverageReader.dispose();
                } catch (Throwable e) {
                    // ignore exception
                    if (LOGGER.isLoggable(Level.FINEST))
                        LOGGER.log(Level.FINEST, e.getLocalizedMessage(), e);
                }
            }

            super.handleFile(fileBeingProcessed, depth, results);
        }

        private String prepareLocation(final File fileBeingProcessed) throws IOException {
            //absolute
            if (runConfiguration.isAbsolute())
                return fileBeingProcessed.getAbsolutePath();

            // relative
            String path = fileBeingProcessed.getCanonicalPath();
            path = path.substring(runConfiguration.getRootMosaicDirectory().length());
            return path;

        }

        private boolean checkStop() {

            if (getStop()) {

                fireEvent(Level.INFO, "Stopping requested at file  " + fileIndex + " of " + numFiles + " files",
                        ((fileIndex * 100.0) / numFiles));
                return false;
            }
            return true;
        }

        private boolean checkFile(final File fileBeingProcessed) {
            if (!fileBeingProcessed.exists() || !fileBeingProcessed.canRead() || !fileBeingProcessed.isFile()) {
                // send a message
                fireEvent(Level.INFO, "Skipped file " + fileBeingProcessed + " snce it seems invalid",
                        ((fileIndex * 99.0) / numFiles));
                return false;
            }
            return true;
        }

        public CatalogBuilderDirectoryWalker(final List<String> indexingDirectories, final FileFilter filter)
                throws IOException {
            super(filter, Integer.MAX_VALUE);//runConfiguration.isRecursive()?Integer.MAX_VALUE:0);

            this.transaction = new DefaultTransaction("MosaicCreationTransaction" + System.nanoTime());
            indexingPreamble();

            try {
                // start walking directories
                for (String indexingDirectory : indexingDirectories) {
                    walk(new File(indexingDirectory), null);

                    // did we cancel?
                    if (canceled)
                        break;
                }
                // did we cancel?
                if (canceled)
                    transaction.rollback();
                else
                    transaction.commit();
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Failure occurred while collecting the granules", e);
                transaction.rollback();
            } finally {
                try {
                    transaction.close();
                } catch (Exception e) {
                    final String message = "Unable to close indexing" + e.getLocalizedMessage();
                    if (LOGGER.isLoggable(Level.WARNING)) {
                        LOGGER.log(Level.WARNING, message, e);
                    }
                    // notify listeners
                    fireException(e);
                }

                try {
                    indexingPostamble(!canceled);
                } catch (Exception e) {
                    final String message = "Unable to close indexing" + e.getLocalizedMessage();
                    if (LOGGER.isLoggable(Level.WARNING)) {
                        LOGGER.log(Level.WARNING, message, e);
                    }
                    // notify listeners
                    fireException(e);
                }
            }

        }

        public int getNumberOfProcessedFiles() {
            return numberOfProcessedFiles;
        }

        /**
         * This method checks the {@link ColorModel} of the current image with the
         * one of the first image in order to check if they are compatible or not in
         * order to perform a mosaic operation.
         * 
         * <p>
         * It is worth to point out that we also check if, in case we have two index
         * color model image, we also try to suggest whether or not we should do a
         * color expansion.
         * 
         * @param defaultCM
         * @param defaultPalette
         * @param actualCM
         * @return a boolean asking to skip this feature.
         */
        private boolean checkColorModels(ColorModel defaultCM, byte[][] defaultPalette, ColorModel actualCM) {

            //
            //
            // ComponentColorModel
            //
            //

            if (defaultCM instanceof ComponentColorModel && actualCM instanceof ComponentColorModel) {
                final ComponentColorModel defCCM = (ComponentColorModel) defaultCM,
                        actualCCM = (ComponentColorModel) actualCM;
                final ColorSpace defCS = defCCM.getColorSpace();
                final ColorSpace actualCS = actualCCM.getColorSpace();
                final boolean isBogusDef = defCS instanceof BogusColorSpace;
                final boolean isBogusActual = actualCS instanceof BogusColorSpace;
                final boolean colorSpaceIsOk;
                if (isBogusDef && isBogusActual) {
                    final BogusColorSpace def = (BogusColorSpace) defCS;
                    final BogusColorSpace act = (BogusColorSpace) actualCS;
                    colorSpaceIsOk = def.getNumComponents() == act.getNumComponents()
                            && def.isCS_sRGB() == act.isCS_sRGB() && def.getType() == act.getType();
                } else
                    colorSpaceIsOk = defCS.equals(actualCS);

                return !(defCCM.getNumColorComponents() == actualCCM.getNumColorComponents()
                        && defCCM.hasAlpha() == actualCCM.hasAlpha() && colorSpaceIsOk
                        && defCCM.getTransparency() == actualCCM.getTransparency()
                        && defCCM.getTransferType() == actualCCM.getTransferType());
            }

            //
            //
            // IndexColorModel
            //
            //

            if (defaultCM instanceof IndexColorModel && actualCM instanceof IndexColorModel) {
                final IndexColorModel defICM = (IndexColorModel) defaultCM, actualICM = (IndexColorModel) actualCM;
                if (defICM.getNumColorComponents() != actualICM.getNumColorComponents()
                        || defICM.hasAlpha() != actualICM.hasAlpha()
                        || !defICM.getColorSpace().equals(actualICM.getColorSpace())
                        || defICM.getTransferType() != actualICM.getTransferType())
                    return true;

                //
                // Suggesting expansion in the simplest case
                //
                if (defICM.getMapSize() != actualICM.getMapSize()
                        || defICM.getTransparency() != actualICM.getTransparency()
                        || defICM.getTransferType() != actualICM.getTransferType()
                        || defICM.getTransparentPixel() != actualICM.getTransparentPixel()) {
                    mustConvertToRGB = true;
                    return false;
                }

                //
                // Now checking palettes to see if we need to do a color convert
                //
                // get the palette for this color model
                int numBands = actualICM.getNumColorComponents();
                byte[][] actualPalette = new byte[3][actualICM.getMapSize()];
                actualICM.getReds(actualPalette[0]);
                actualICM.getGreens(actualPalette[0]);
                actualICM.getBlues(actualPalette[0]);
                if (numBands == 4)
                    actualICM.getAlphas(defaultPalette[0]);
                // compare them
                for (int i = 0; i < defICM.getMapSize(); i++)
                    for (int j = 0; j < numBands; j++)
                        if (actualPalette[j][i] != defaultPalette[j][i]) {
                            mustConvertToRGB = true;
                            break;
                        }
                return false;

            }

            //
            // if we get here this means that the two color models where completely
            // different, hence skip this feature.
            //
            return true;
        }

        //      @Override
        //      protected void handleEnd(Collection results) throws IOException {
        //         try{
        //            transaction.commit();
        //         }
        //         finally{
        //            transaction.close();
        //         }      
        //         indexingPostamble();
        //         super.handleEnd(results);
        //      }

        //      @Override
        //      protected void handleStart(File startDirectory, Collection results)
        //            throws IOException {
        //         indexingPreamble();
        //         super.handleStart(startDirectory, results);
        //         
        //         
        //      }

    }

    /** Number of files to process. */
    private int numFiles;

    /**
     * List containing all the objects that want to be notified during
     * processing.
     */
    private List<ProcessingEventListener> notificationListeners = new CopyOnWriteArrayList<ProcessingEventListener>();

    /**
     * Set this to false for command line UIs where the delayed event sending
     * may prevent some messages to be seen before the tool exits, to true for
     * real GUI where you don't want the processing to be blocked too long, or
     * when you have slow listeners in general.
     */
    private boolean sendDelayedMessages = false;

    /**
     * Proper way to stop a thread is not by calling Thread.stop() but by using
     * a shared variable that can be checked in order to notify a terminating
     * condition.
     */
    private volatile boolean stop = false;

    private MosaicConfigurationBean mosaicConfiguration;

    private GeometryFactory geomFactory;

    private GranuleCatalog catalog;

    private int numberOfProcessedFiles;

    /**
     * This field will tell the plugin if it must do a conversion of color from
     * the original index color model to an RGB color model. This happens f the
     * original images uses different color maps between each other making for
     * us impossible to reuse it for the mosaic.
     */
    private boolean mustConvertToRGB = Utils.DEFAULT_COLOR_EXPANSION_BEHAVIOR;

    private int fileIndex = 0;

    private ColorModel defaultCM = null;

    private CoordinateReferenceSystem defaultCRS = null;

    private byte[][] defaultPalette = null;

    private CatalogBuilderConfiguration runConfiguration;

    private ImageReaderSpi cachedReaderSPI;

    private ImageInputStreamSpi cachedStreamSPI;

    private List<PropertiesCollector> propertiesCollectors;

    private SampleModel defaultSM;

    private ReferencedEnvelope imposedBBox;

    private SimpleFeatureType indexSchema;

    private AbstractGridFormat cachedFormat;

    /* (non-Javadoc)
     * @see org.geotools.gce.imagemosaic.JMXIndexBuilderMBean#run()
     */
    public void run() {

        try {

            //
            // creating the file filters for scanning for files to check and index
            //
            final IOFileFilter finalFilter = createGranuleFilterRules();

            //TODO we might want to remove this in the future for performance
            numFiles = 0;
            for (String indexingDirectory : runConfiguration.getIndexingDirectories()) {
                final File directoryToScan = new File(indexingDirectory);
                final Collection files = FileUtils.listFiles(directoryToScan, finalFilter,
                        runConfiguration.isRecursive() ? TrueFileFilter.INSTANCE : FalseFileFilter.INSTANCE);
                numFiles += files.size();
            }
            //
            // walk over the files that have filtered out
            //
            if (numFiles > 0) {
                final List<String> indexingDirectories = runConfiguration.getIndexingDirectories();
                @SuppressWarnings("unused")
                final CatalogBuilderDirectoryWalker walker = new CatalogBuilderDirectoryWalker(indexingDirectories,
                        finalFilter);

            }

        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        }

    }

    /**
     * @return
     */
    private IOFileFilter createGranuleFilterRules() {
        final IOFileFilter specialWildCardFileFilter = new WildcardFileFilter(runConfiguration.getWildcard(),
                IOCase.INSENSITIVE);
        IOFileFilter dirFilter = FileFilterUtils.andFileFilter(FileFilterUtils.directoryFileFilter(),
                HiddenFileFilter.VISIBLE);
        IOFileFilter fileFilter = Utils.excludeFilters(
                FileFilterUtils.makeSVNAware(FileFilterUtils.makeFileOnly(
                        FileFilterUtils.andFileFilter(specialWildCardFileFilter, HiddenFileFilter.VISIBLE))),
                FileFilterUtils.suffixFileFilter("shp"), FileFilterUtils.suffixFileFilter("dbf"),
                FileFilterUtils.suffixFileFilter("shx"), FileFilterUtils.suffixFileFilter("qix"),
                FileFilterUtils.suffixFileFilter("lyr"), FileFilterUtils.suffixFileFilter("prj"),
                FileFilterUtils.nameFileFilter("error.txt"), FileFilterUtils.nameFileFilter("error.txt.lck"),
                FileFilterUtils.suffixFileFilter("properties"), FileFilterUtils.suffixFileFilter("svn-base"));

        // exclude common extensions
        Set<String> extensions = WorldImageFormat.getWorldExtension("png");
        for (String ext : extensions) {
            fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                    FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter(ext.substring(1))));
        }
        extensions = WorldImageFormat.getWorldExtension("gif");
        for (String ext : extensions) {
            fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                    FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter(ext.substring(1))));
        }
        extensions = WorldImageFormat.getWorldExtension("jpg");
        for (String ext : extensions) {
            fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                    FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter(ext.substring(1))));
        }
        extensions = WorldImageFormat.getWorldExtension("tiff");
        for (String ext : extensions) {
            fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                    FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter(ext.substring(1))));
        }
        extensions = WorldImageFormat.getWorldExtension("bmp");
        for (String ext : extensions) {
            fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                    FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter(ext.substring(1))));
        }

        //sdw
        fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter("sdw")));
        //aux
        fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter("aux")));
        //wld
        fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter("wld")));
        //svn
        fileFilter = FileFilterUtils.andFileFilter(fileFilter,
                FileFilterUtils.notFileFilter(FileFilterUtils.suffixFileFilter("svn")));

        final IOFileFilter finalFilter = FileFilterUtils.orFileFilter(dirFilter, fileFilter);
        return finalFilter;
    }

    /**
     * Default constructor
     * @throws  
     * @throws IllegalArgumentException 
     */
    public CatalogBuilder(final CatalogBuilderConfiguration configuration) {
        Utilities.ensureNonNull("runConfiguration", configuration);
        Utilities.ensureNonNull("root location", configuration.getRootMosaicDirectory());

        // look for and indexed.properties file
        final File parent = new File(configuration.getRootMosaicDirectory());
        final File indexerProperties = new File(parent, Utils.INDEXER_PROPERTIES);
        if (Utils.checkFileReadable(indexerProperties)) {
            // load it and parse it
            final Properties props = Utils.loadPropertiesFromURL(DataUtilities.fileToURL(indexerProperties));

            // name
            if (props.containsKey(Prop.NAME))
                configuration.setIndexName(props.getProperty(Prop.NAME));

            // absolute
            if (props.containsKey(Prop.ABSOLUTE_PATH))
                configuration.setAbsolute(Boolean.valueOf(props.getProperty(Prop.ABSOLUTE_PATH)));

            // recursive
            if (props.containsKey(Prop.RECURSIVE))
                configuration.setRecursive(Boolean.valueOf(props.getProperty(Prop.RECURSIVE)));

            // wildcard
            if (props.containsKey(Prop.WILDCARD))
                configuration.setWildcard(props.getProperty(Prop.WILDCARD));

            // schema
            if (props.containsKey(Prop.SCHEMA))
                configuration.setSchema(props.getProperty(Prop.SCHEMA));

            // time attr
            if (props.containsKey(Prop.TIME_ATTRIBUTE))
                configuration.setTimeAttribute(props.getProperty(Prop.TIME_ATTRIBUTE));

            // elevation attr
            if (props.containsKey(Prop.ELEVATION_ATTRIBUTE))
                configuration.setElevationAttribute(props.getProperty(Prop.ELEVATION_ATTRIBUTE));

            // Additional domain attr
            if (props.containsKey(Prop.ADDITIONAL_DOMAIN_ATTRIBUTES))
                configuration.setAdditionalDomainAttribute(props.getProperty(Prop.ADDITIONAL_DOMAIN_ATTRIBUTES));

            // imposed BBOX
            if (props.containsKey(Prop.ENVELOPE2D))
                configuration.setEnvelope2D(props.getProperty(Prop.ENVELOPE2D));

            // imposed Pyramid Layout
            if (props.containsKey(Prop.RESOLUTION_LEVELS))
                configuration.setResolutionLevels(props.getProperty(Prop.RESOLUTION_LEVELS));

            // collectors
            if (props.containsKey(Prop.PROPERTY_COLLECTORS))
                configuration.setPropertyCollectors(props.getProperty(Prop.PROPERTY_COLLECTORS));

            if (props.containsKey(Prop.CACHING))
                configuration.setCaching(Boolean.valueOf(props.getProperty(Prop.CACHING)));
        }

        //check config
        configuration.check();

        this.runConfiguration = new CatalogBuilderConfiguration(configuration);

    }

    /**
     * Adding a listener to the {@link ProcessingEventListener}s' list.
     * 
     * @param listener
     *            to add to the list of listeners.
     */
    public final void addProcessingEventListener(final ProcessingEventListener listener) {
        notificationListeners.add(listener);
    }

    /**
     * Perform proper clean up.
     * 
     * <p>
     * Make sure to call this method when you are not running the
     * {@link CatalogBuilder} or bad things can happen. If it is running, please
     * stop it first.
     */
    public void reset() {
        removeAllProcessingEventListeners();
        // clear stop
        stop = false;

        closeIndexObjects();

        //clear other stuff
        defaultCM = null;
        defaultCRS = null;
        defaultPalette = null;
        fileIndex = 0;
        numberOfProcessedFiles = 0;

        // clear directories
        runConfiguration = null;

    }

    /**
     * Firing an event to listeners in order to inform them about what we are
     * doing and about the percentage of work already carried out.
     * @param level 
     * 
     * @param message
     *            The message to show.
     * @param percentage
     *            The percentage for the process.
     */
    private void fireEvent(Level level, final String inMessage, final double percentage) {
        if (LOGGER.isLoggable(level)) {
            LOGGER.log(level, inMessage);
        }
        synchronized (notificationListeners) {
            final String newLine = System.getProperty("line.separator");
            final StringBuilder message = new StringBuilder("Thread Name ");
            message.append(Thread.currentThread().getName()).append(newLine);
            message.append(this.getClass().toString()).append(newLine).append(inMessage);
            final ProcessingEvent evt = new ProcessingEvent(this, message.toString(), percentage);
            ProgressEventDispatchThreadEventLauncher eventLauncher = new ProgressEventDispatchThreadEventLauncher();
            eventLauncher.setEvent(evt, this.notificationListeners.toArray());
            sendEvent(eventLauncher);
        }
    }

    /**
     * Firing an exception event to listeners in order to inform them that
     * processing broke and we can no longer proceed. This is a convenience
     * method, it will call {@link #fireException(String, double, Exception)}
     * with the exception message and -1 as percentage.
     * 
     * @param ex
     *            the actual exception occurred
     */
    private void fireException(Exception ex) {
        synchronized (notificationListeners) {
            fireException(Utils.getMessageFromException(ex), -1, ex);
        }
    }

    /**
     * Firing an exception event to listeners in order to inform them that
     * processing broke and we can no longer proceed
     * 
     * @param string
     *            The message to show.
     * @param percentage
     *            The percentage for the process.
     * @param ex
     *            the actual exception occurred
     */
    private void fireException(final String string, final double percentage, Exception ex) {
        synchronized (notificationListeners) {
            final String newLine = System.getProperty("line.separator");
            final StringBuilder message = new StringBuilder("Thread Name ");
            message.append(Thread.currentThread().getName()).append(newLine);
            message.append(this.getClass().toString()).append(newLine).append(string);
            final ExceptionEvent evt = new ExceptionEvent(this, string, percentage, ex);
            ProgressEventDispatchThreadEventLauncher eventLauncher = new ProgressEventDispatchThreadEventLauncher();
            eventLauncher.setEvent(evt, this.notificationListeners.toArray());
            sendEvent(eventLauncher);
        }
    }

    /* (non-Javadoc)
     * @see org.geotools.gce.imagemosaic.JMXIndexBuilderMBean#getStop()
     */
    public boolean getStop() {
        return stop;
    }

    public boolean isSendDelayedMessages() {
        return sendDelayedMessages;
    }

    public void setSendDelayedMessages(boolean sendDelayedMessages) {
        this.sendDelayedMessages = sendDelayedMessages;
    }

    /**
     * Removing all the listeners.
     * 
     */
    public void removeAllProcessingEventListeners() {
        synchronized (notificationListeners) {
            notificationListeners.clear();
        }

    }

    /**
     * Removing a {@link ProcessingEventListener} from the listeners' list.
     * 
     * @param listener
     *            {@link ProcessingEventListener} to remove from the list of
     *            listeners.
     */
    public void removeProcessingEventListener(final ProcessingEventListener listener) {
        notificationListeners.remove(listener);

    }

    private void sendEvent(ProgressEventDispatchThreadEventLauncher eventLauncher) {
        if (sendDelayedMessages)
            SwingUtilities.invokeLater(eventLauncher);
        else
            eventLauncher.run();
    }

    /* (non-Javadoc)
     * @see org.geotools.gce.imagemosaic.JMXIndexBuilderMBean#stop()
     */
    public void stop() {
        stop = true;
    }

    private void indexingPreamble() throws IOException {

        //
        // declaring a precision model to adhere the java double type
        // precision
        //
        final PrecisionModel precMod = new PrecisionModel(PrecisionModel.FLOATING);
        geomFactory = new GeometryFactory(precMod);

        //
        // create the index
        //
        // do we have a datastore.properties file?
        final File parent = new File(runConfiguration.getRootMosaicDirectory());
        final File datastoreProperties = new File(parent, "datastore.properties");
        if (Utils.checkFileReadable(datastoreProperties)) {
            // read the properties file
            Properties properties = Utils.loadPropertiesFromURL(DataUtilities.fileToURL(datastoreProperties));
            if (properties == null)
                throw new IOException("Unable to load properties from:" + datastoreProperties.getAbsolutePath());

            // SPI
            final String SPIClass = properties.getProperty("SPI");
            try {
                // create a datastore as instructed
                final DataStoreFactorySpi spi = (DataStoreFactorySpi) Class.forName(SPIClass).newInstance();
                final Map<String, Serializable> params = Utils.createDataStoreParamsFromPropertiesFile(properties,
                        spi);

                // set ParentLocation parameter since for embedded database like H2 we must change the database
                // to incorporate the path where to write the db 
                params.put("ParentLocation", DataUtilities.fileToURL(parent).toExternalForm());
                catalog = GranuleCatalogFactory.createGranuleCatalog(params, false, true, spi);
            } catch (ClassNotFoundException e) {
                final IOException ioe = new IOException();
                throw (IOException) ioe.initCause(e);
            } catch (InstantiationException e) {
                final IOException ioe = new IOException();
                throw (IOException) ioe.initCause(e);
            } catch (IllegalAccessException e) {
                final IOException ioe = new IOException();
                throw (IOException) ioe.initCause(e);
            }
        } else {

            // we do not have a datastore properties file therefore we continue with a shapefile datastore
            final URL file = new File(parent, runConfiguration.getIndexName() + ".shp").toURI().toURL();
            final Map<String, Serializable> params = new HashMap<String, Serializable>();
            params.put(ShapefileDataStoreFactory.URLP.key, file);
            if (file.getProtocol().equalsIgnoreCase("file"))
                params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key, Boolean.TRUE);
            params.put(ShapefileDataStoreFactory.MEMORY_MAPPED.key, Boolean.TRUE);
            params.put(ShapefileDataStoreFactory.DBFTIMEZONE.key, TimeZone.getTimeZone("UTC"));
            catalog = GranuleCatalogFactory.createGranuleCatalog(params, false, true, Utils.SHAPE_SPI);
        }

        //
        // creating a mosaic runConfiguration bean to store the properties file elements         
        //
        mosaicConfiguration = new MosaicConfigurationBean();
        mosaicConfiguration.setName(runConfiguration.getIndexName());

        //
        // IMPOSED ENVELOPE
        //
        String bbox = runConfiguration.getEnvelope2D();
        try {
            this.imposedBBox = Utils.parseEnvelope(bbox);
        } catch (Exception e) {
            this.imposedBBox = null;
            if (LOGGER.isLoggable(Level.WARNING))
                LOGGER.log(Level.WARNING, "Unable to parse imposed bbox", e);
        }
        mosaicConfiguration.setCaching(runConfiguration.isCaching());
        //
        // load property collectors
        //
        loadPropertyCollectors();

    }

    private void loadPropertyCollectors() {
        // load property collectors
        String pcConfig = runConfiguration.getPropertyCollectors();
        if (pcConfig != null && pcConfig.length() > 0) {
            pcConfig = pcConfig.trim();
            // load the SPI set
            final Set<PropertiesCollectorSPI> pcSPIs = PropertiesCollectorFinder.getPropertiesCollectorSPI();

            // parse the string
            final List<PropertiesCollector> pcs = new ArrayList<PropertiesCollector>();
            final String[] pcsDefs = pcConfig.trim().split(",");
            for (String pcDef : pcsDefs) {
                // parse this def as NAME[CONFIG_FILE](PROPERTY;PROPERTY;....;PROPERTY)
                final int squareLPos = pcDef.indexOf("[");
                final int squareRPos = pcDef.indexOf("]");
                final int squareRPosLast = pcDef.lastIndexOf("]");
                final int roundLPos = pcDef.indexOf("(");
                final int roundRPos = pcDef.indexOf(")");
                final int roundRPosLast = pcDef.lastIndexOf(")");
                if (squareRPos != squareRPosLast) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (squareLPos == -1 || squareRPos == -1) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (squareLPos == 0) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }

                if (roundRPos != roundRPosLast) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (roundLPos == -1 || roundRPos == -1) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (roundLPos == 0) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (roundLPos != squareRPos + 1) {//]( or exit
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }
                if (roundRPos != (pcDef.length() - 1)) {// end with )
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Skipping unparseable PropertyCollector definition: " + pcDef);
                    }
                    continue;
                }

                // name
                final String name = pcDef.substring(0, squareLPos);
                PropertiesCollectorSPI selectedSPI = null;
                for (PropertiesCollectorSPI spi : pcSPIs) {
                    if (spi.isAvailable() && spi.getName().equalsIgnoreCase(name)) {
                        selectedSPI = spi;
                        break;
                    }
                }
                if (selectedSPI == null) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Unable to find a PropertyCollector for this definition: " + pcDef);
                    }
                    continue;
                }

                // config
                final String config = squareLPos < squareRPos ? pcDef.substring(squareLPos + 1, squareRPos) : "";
                final File configFile = new File(runConfiguration.getRootMosaicDirectory(), config + ".properties");
                if (!Utils.checkFileReadable(configFile)) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Unable to access the file for this PropertyCollector: "
                                + configFile.getAbsolutePath());
                    }
                    continue;
                }
                // it is readable

                // property names
                final String propertyNames[] = pcDef.substring(roundLPos + 1, roundRPos).split(",");

                // create the PropertiesCollector
                final PropertiesCollector pc = selectedSPI.create(configFile, Arrays.asList(propertyNames));
                if (pc != null) {
                    pcs.add(pc);
                } else {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info(
                                "Unable to create PropertyCollector " + pcDef + " from config file:" + configFile);
                    }
                }

            }
            this.propertiesCollectors = pcs;
        }
    }

    private void indexingPostamble(final boolean success) throws IOException {
        //close shapefile elements
        closeIndexObjects();

        if (success) {
            // create sample image if the needed elements are available
            createSampleImage();

            // complete initialization of mosaic configuration
            if (numberOfProcessedFiles > 0) {
                mosaicConfiguration.setName(runConfiguration.getIndexName());
                mosaicConfiguration.setExpandToRGB(mustConvertToRGB);
                mosaicConfiguration.setAbsolutePath(runConfiguration.isAbsolute());
                mosaicConfiguration.setLocationAttribute(runConfiguration.getLocationAttribute());
                mosaicConfiguration.setCaching(runConfiguration.isCaching());
                final String timeAttribute = runConfiguration.getTimeAttribute();
                if (timeAttribute != null) {
                    mosaicConfiguration.setTimeAttribute(runConfiguration.getTimeAttribute());
                }
                final String elevationAttribute = runConfiguration.getElevationAttribute();
                if (elevationAttribute != null) {
                    mosaicConfiguration.setElevationAttribute(runConfiguration.getElevationAttribute());
                }

                final String additionalDomainAttribute = runConfiguration.getAdditionalDomainAttribute();
                if (additionalDomainAttribute != null) {
                    mosaicConfiguration
                            .setAdditionalDomainAttributes(runConfiguration.getAdditionalDomainAttribute());
                }
                createPropertiesFiles();

                // processing information
                fireEvent(Level.FINE, "Done!!!", 100);
            } else {
                //   processing information
                fireEvent(Level.FINE, "Nothing to process!!!", 100);
            }
        } else {
            // processing information
            fireEvent(Level.FINE, "Canceled!!!", 100);

            //TODO remove all files created so far
        }
    }

    /**
     * Store a sample image frmo which we can derive the default SM and CM
     */
    private void createSampleImage() {
        // create a sample image to store SM and CM
        if (defaultCM != null && defaultSM != null) {

            // sample image file
            final File sampleImageFile = new File(runConfiguration.getRootMosaicDirectory() + "/sample_image");
            try {
                Utils.storeSampleImage(sampleImageFile, defaultSM, defaultCM);
            } catch (IOException e) {
                fireEvent(Level.SEVERE, e.getLocalizedMessage(), 0);
            }
        }
    }

    private void closeIndexObjects() {
        try {
            if (catalog != null) {
                catalog.dispose();
            }
        } catch (Throwable e) {
            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        }

        catalog = null;
    }

    /**
     * Creates the final properties file.
     */
    private void createPropertiesFiles() {

        //
        // FINAL STEP
        //
        // CREATING GENERAL INFO FILE
        //

        fireEvent(Level.INFO, "Creating final properties file ", 99.9);

        // envelope
        final Properties properties = new Properties();
        properties.setProperty(Utils.Prop.ABSOLUTE_PATH, Boolean.toString(mosaicConfiguration.isAbsolutePath()));
        properties.setProperty(Utils.Prop.LOCATION_ATTRIBUTE, mosaicConfiguration.getLocationAttribute());
        final String timeAttribute = mosaicConfiguration.getTimeAttribute();
        if (timeAttribute != null) {
            properties.setProperty(Utils.Prop.TIME_ATTRIBUTE, mosaicConfiguration.getTimeAttribute());
        }
        final String elevationAttribute = mosaicConfiguration.getElevationAttribute();
        if (elevationAttribute != null) {
            properties.setProperty(Utils.Prop.ELEVATION_ATTRIBUTE, mosaicConfiguration.getElevationAttribute());
        }

        final String additionalDomainAttribute = mosaicConfiguration.getAdditionalDomainAttributes();
        if (additionalDomainAttribute != null) {
            properties.setProperty(Utils.Prop.ADDITIONAL_DOMAIN_ATTRIBUTES,
                    mosaicConfiguration.getAdditionalDomainAttributes());
        }

        final int numberOfLevels = mosaicConfiguration.getLevelsNum();
        final double[][] resolutionLevels = mosaicConfiguration.getLevels();
        properties.setProperty(Utils.Prop.LEVELS_NUM, Integer.toString(numberOfLevels));
        final StringBuilder levels = new StringBuilder();
        for (int k = 0; k < numberOfLevels; k++) {
            levels.append(Double.toString(resolutionLevels[0][k])).append(",")
                    .append(Double.toString(resolutionLevels[1][k]));
            if (k < numberOfLevels - 1)
                levels.append(" ");
        }
        properties.setProperty(Utils.Prop.LEVELS, levels.toString());
        properties.setProperty(Utils.Prop.NAME, mosaicConfiguration.getName());
        properties.setProperty(Utils.Prop.TYPENAME, mosaicConfiguration.getName());
        properties.setProperty(Utils.Prop.EXP_RGB, Boolean.toString(mustConvertToRGB));
        properties.setProperty(Utils.Prop.HETEROGENEOUS, Boolean.toString(mosaicConfiguration.isHeterogeneous()));

        if (cachedReaderSPI != null) {
            // suggested spi
            properties.setProperty(Utils.Prop.SUGGESTED_SPI, cachedReaderSPI.getClass().getName());
        }

        // write down imposed bbox
        if (imposedBBox != null) {
            properties.setProperty(Utils.Prop.ENVELOPE2D, imposedBBox.getMinX() + "," + imposedBBox.getMinY() + " "
                    + imposedBBox.getMaxX() + "," + imposedBBox.getMaxY());
        }
        properties.setProperty(Utils.Prop.CACHING, Boolean.toString(mosaicConfiguration.isCaching()));
        OutputStream outStream = null;
        try {
            outStream = new BufferedOutputStream(new FileOutputStream(runConfiguration.getRootMosaicDirectory()
                    + "/" + runConfiguration.getIndexName() + ".properties"));
            properties.store(outStream, "-Automagically created from GeoTools-");
        } catch (FileNotFoundException e) {
            fireEvent(Level.SEVERE, e.getLocalizedMessage(), 0);
        } catch (IOException e) {
            fireEvent(Level.SEVERE, e.getLocalizedMessage(), 0);
        } finally {
            if (outStream != null) {
                IOUtils.closeQuietly(outStream);
            }

        }
    }

    public void dispose() {
        reset();
    }

    /**
     * 
     * @param resolutionLevels
     * @param numberOfLevels
     * @param coverageReader
     * @param imageioReader
     * @param compareLevels optional resolutionLevels to be compared.
     * @return 
     * @throws IndexOutOfBoundsException
     * @throws IOException
     */
    private static boolean setupResolutions(final double[][] resolutionLevels, final int numberOfLevels,
            final AbstractGridCoverage2DReader coverageReader, final ImageReader imageioReader,
            final double[][] compareLevels) throws IndexOutOfBoundsException, IOException {
        double[] res = CoverageUtilities
                .getResolution((AffineTransform) coverageReader.getOriginalGridToWorld(PixelInCell.CELL_CORNER));
        resolutionLevels[0][0] = res[0];
        resolutionLevels[1][0] = res[1];
        final boolean checkLevels = compareLevels != null;

        // resolutions levels are computed using the raster space scale factors
        if (numberOfLevels >= 1) {
            for (int k = 0; k < numberOfLevels; k++) {
                resolutionLevels[0][k] = resolutionLevels[0][0] * coverageReader.getOriginalGridRange().getSpan(0)
                        / (1.0 * imageioReader.getWidth(k));
                resolutionLevels[1][k] = resolutionLevels[1][0] * coverageReader.getOriginalGridRange().getSpan(1)
                        / (1.0 * imageioReader.getHeight(k));
                if (checkLevels) {
                    if (Math.abs(resolutionLevels[0][k] - compareLevels[0][k]) > RESOLUTION_TOLERANCE_FACTOR
                            * compareLevels[0][k]
                            || Math.abs(resolutionLevels[1][k] - compareLevels[1][k]) > RESOLUTION_TOLERANCE_FACTOR
                                    * compareLevels[1][k]) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public MosaicConfigurationBean getMosaicConfiguration() {
        return new MosaicConfigurationBean(mosaicConfiguration);
    }

}