/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2005-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2007-2012, 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.sql;
import java.util.Map;
import java.util.List;
import java.util.Locale;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.sql.Types;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.text.ParseException;
import javax.measure.Unit;
import org.apache.sis.measure.Units;
import org.apache.sis.measure.UnitFormat;
import org.geotoolkit.coverage.Category;
import org.geotoolkit.coverage.GridSampleDimension;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.internal.sql.table.Table;
import org.geotoolkit.internal.sql.table.Database;
import org.geotoolkit.internal.sql.table.QueryType;
import org.geotoolkit.internal.sql.table.LocalCache;
import org.geotoolkit.internal.sql.table.CatalogException;
import org.geotoolkit.internal.sql.table.IllegalRecordException;
import org.geotoolkit.internal.sql.table.IllegalUpdateException;
/**
* Connection to a table of {@linkplain GridSampleDimension sample dimensions}. This table creates
* instances of {@link GridSampleDimension} for a given format. Sample dimensions are one of the
* components needed for creation of {@link GridCoverage2D}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.15
*
* @since 3.09 (derived from Seagis)
* @module
*/
final class SampleDimensionTable extends Table {
/**
* The {@linkplain Category categories} table, created only when first needed.
*/
private transient CategoryTable categories;
/**
* The unit format for parsing and formatting unit symbols.
* Created only when first needed.
*/
private transient UnitFormat unitFormat;
/**
* Creates a sample dimension table.
*
* @param database Connection to the database.
*/
public SampleDimensionTable(final Database database) {
super(new SampleDimensionQuery(database));
}
/**
* Creates a new instance having the same configuration than the given table.
* This is a copy constructor used for obtaining a new instance to be used
* concurrently with the original instance.
*
* @param table The table to use as a template.
*/
private SampleDimensionTable(final SampleDimensionTable table) {
super(table);
}
/**
* Returns a copy of this table. This is a copy constructor used for obtaining
* a new instance to be used concurrently with the original instance.
*/
@Override
protected SampleDimensionTable clone() {
return new SampleDimensionTable(this);
}
/**
* Returns the {@link CategoryTable} instance, creating it if needed.
*/
private CategoryTable getCategoryTable() throws CatalogException {
CategoryTable table = categories;
if (table == null) {
categories = table = getDatabase().getTable(CategoryTable.class);
}
return table;
}
/**
* Returns the unit format for parsing and formatting unit symbols.
* Uses the France locale because it is the authoritative locale of BIPM.
* For most languages, it doesn't make any change in the set of symbols.
*/
private UnitFormat getUnitFormat() {
if (unitFormat == null) {
unitFormat = new UnitFormat(Locale.FRANCE);
}
return unitFormat;
}
/**
* Returns the sample dimensions for the given format. If no sample dimensions are specified,
* return {@code null} (not an empty list). We are not allowed to return an empty list because
* our Image I/O framework interprets that as "no bands", as opposed to "unknown bands".
*
* @param format The format name.
* @return An entry containing the sample dimensions for the given format, or {@code null} if none.
* @throws SQLException if an error occurred while reading the database.
*/
public CategoryEntry getSampleDimensions(final String format) throws SQLException {
final SampleDimensionQuery query = (SampleDimensionQuery) super.query;
String[] names = new String [8];
Unit<?>[] units = new Unit<?>[8];
int numSampleDimensions = 0;
final LocalCache lc = getLocalCache();
synchronized (lc) {
final LocalCache.Stmt ce = getStatement(lc, QueryType.LIST);
final PreparedStatement statement = ce.statement;
statement.setString(indexOf(query.byFormat), format);
final int bandIndex = indexOf(query.band);
final int nameIndex = indexOf(query.name);
final int unitIndex = indexOf(query.units);
try (ResultSet results = statement.executeQuery()) {
while (results.next()) {
final String name = results.getString(nameIndex);
final int band = results.getInt (bandIndex); // First band is 1.
String unitSymbol = results.getString(unitIndex);
Unit<?> unit = null;
if (unitSymbol != null) {
unitSymbol = unitSymbol.trim();
if (unitSymbol.isEmpty()) {
unit = Units.UNITY;
} else {
try {
unit = (Unit<?>) getUnitFormat().parseObject(unitSymbol);
} catch (ParseException e) {
// The constructor of this exception will close the ResultSet.
final IllegalRecordException ex = new IllegalRecordException(errors().getString(
Errors.Keys.UnparsableString_2, "unit(" + unitSymbol + ')',
unitSymbol.substring(Math.max(0, e.getErrorOffset()))),
this, results, unitIndex, name);
ex.initCause(e);
throw ex;
}
}
}
if (numSampleDimensions >= names.length) {
names = Arrays.copyOf(names, names.length*2);
units = Arrays.copyOf(units, units.length*2);
}
names[numSampleDimensions] = name;
units[numSampleDimensions] = unit;
if (band != ++numSampleDimensions) {
// The constructor of this exception will close the ResultSet.
throw new IllegalRecordException(errors().getString(
Errors.Keys.NonConsecutiveBands_2, numSampleDimensions, band),
this, results, bandIndex, format);
}
}
}
release(lc, ce);
}
/*
* At this point, we have successfully read every SampleDimension rows.
* Now read the categories, provided that there is at least one sample
* dimension.
*/
if (numSampleDimensions == 0) {
return null;
}
final GridSampleDimension[] sampleDimensions = new GridSampleDimension[numSampleDimensions];
final CategoryTable categories = getCategoryTable();
final CategoryEntry entry = categories.getCategories(format);
final Map<Integer,Category[]> cat = entry.categories;
for (int i=0; i<numSampleDimensions; i++) {
try {
sampleDimensions[i] = new GridSampleDimension(names[i], cat.remove(i+1), units[i]);
} catch (IllegalArgumentException exception) {
throw new IllegalRecordException(exception, categories, null, 0, format);
}
}
entry.sampleDimensions = sampleDimensions;
return entry;
}
/**
* Adds the given sample dimensions to the database.
*
* @param format The newly created format for which to write the sample dimensions.
* @param bands The sample dimensions to add.
* @throws SQLException if an error occurred while writing to the database.
*
* @since 3.13
*/
public void addEntries(final String format, final List<GridSampleDimension> bands) throws SQLException {
final SampleDimensionQuery query = (SampleDimensionQuery) super.query;
final LocalCache lc = getLocalCache();
synchronized (lc) {
boolean success = false;
transactionBegin(lc);
try {
final LocalCache.Stmt ce = getStatement(lc, QueryType.INSERT);
final PreparedStatement statement = ce.statement;
statement.setString(indexOf(query.format), format);
final int bandIndex = indexOf(query.band);
final int nameIndex = indexOf(query.name);
final int unitIndex = indexOf(query.units);
final List<List<Category>> categories = new ArrayList<>(bands.size());
boolean isEmpty = true;
int bandNumber = 0;
for (GridSampleDimension band : bands) {
band = band.geophysics(false);
statement.setInt(bandIndex, ++bandNumber);
statement.setString(nameIndex, String.valueOf(band.getDescription()));
final Unit<?> unit = band.getUnits();
if (unit != null) {
statement.setString(unitIndex, getUnitFormat().format(unit));
} else {
statement.setNull(unitIndex, Types.VARCHAR);
}
final int count = statement.executeUpdate();
if (count != 1) {
throw new IllegalUpdateException(getLocale(), count);
}
List<Category> bandCategories = band.getCategories();
if (bandCategories == null) {
bandCategories = Collections.emptyList();
} else if (isEmpty) {
isEmpty = bandCategories.isEmpty();
}
categories.add(bandCategories);
}
release(lc, ce);
if (!isEmpty) {
getCategoryTable().addEntries(format, categories);
}
success = true;
} finally {
transactionEnd(lc, success);
}
}
}
}