/*
* Geopaparazzi - Digital field mapping on Android based devices
* Copyright (C) 2016 HydroloGIS (www.hydrologis.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.geopaparazzi.spatialite.database.spatial;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import org.json.JSONException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import eu.geopaparazzi.library.GPApplication;
import eu.geopaparazzi.library.core.maps.SpatialiteMap;
import eu.geopaparazzi.library.database.GPLog;
import eu.geopaparazzi.library.features.Feature;
import eu.geopaparazzi.library.profiles.ProfilesHandler;
import eu.geopaparazzi.spatialite.database.spatial.core.daos.SPL_Vectors;
import eu.geopaparazzi.spatialite.database.spatial.core.databasehandlers.SpatialiteDatabaseHandler;
import eu.geopaparazzi.spatialite.database.spatial.core.enums.GeometryType;
import eu.geopaparazzi.library.util.types.ESpatialDataSources;
import eu.geopaparazzi.spatialite.database.spatial.core.enums.VectorLayerQueryModes;
import eu.geopaparazzi.spatialite.database.spatial.core.tables.AbstractSpatialTable;
import eu.geopaparazzi.spatialite.database.spatial.core.tables.SpatialVectorTable;
import eu.geopaparazzi.spatialite.database.spatial.util.SpatialiteLibraryConstants;
import jsqlite.Exception;
/**
* The base maps sources manager.
*
* @author Andrea Antonello (www.hydrologis.com)
*/
public enum SpatialiteSourcesManager {
INSTANCE;
private SharedPreferences mPreferences;
private List<SpatialiteMap> mSpatialiteMaps;
private HashMap<SpatialiteMap, SpatialVectorTable> mSpatialiteMaps2TablesMap = new HashMap<>();
private HashMap<SpatialiteMap, SpatialiteDatabaseHandler> mSpatialiteMaps2DbHandlersMap = new HashMap<>();
private boolean mReReadBasemaps = true;
SpatialiteSourcesManager() {
try {
GPApplication gpApplication = GPApplication.getInstance();
mPreferences = PreferenceManager.getDefaultSharedPreferences(gpApplication);
boolean doSpatialiteRecoveryMode = mPreferences.getBoolean(SpatialiteLibraryConstants.PREFS_KEY_SPATIALITE_RECOVERY_MODE,
false);
// doSpatialiteRecoveryMode=true;
if (doSpatialiteRecoveryMode) {
// Turn on Spatialite Recovery Modus
SPL_Vectors.VECTORLAYER_QUERYMODE = VectorLayerQueryModes.CORRECTIVEWITHINDEX;
// and reset it in the preferences
Editor editor = mPreferences.edit();
editor.putBoolean(SpatialiteLibraryConstants.PREFS_KEY_SPATIALITE_RECOVERY_MODE, false);
editor.apply();
}
} catch (java.lang.Exception e) {
GPLog.error(this, null, e);
}
}
/**
* Getter for the current available spatialite maps.
*
* @return the list of spatialite maps.
*/
public List<SpatialiteMap> getSpatialiteMaps() {
try {
if (mSpatialiteMaps == null || mReReadBasemaps) {
getSpatialiteMapsFromPreferences();
mReReadBasemaps = false;
}
return mSpatialiteMaps;
} catch (java.lang.Exception e) {
GPLog.error(this, null, e);
}
return Collections.emptyList();
}
public void forceSpatialitemapsreRead() {
mSpatialiteMaps = null;
mReReadBasemaps = true;
}
/**
* Reads the maps from preferences and extracts the tables necessary.
*
* @throws java.lang.Exception
*/
private void getSpatialiteMapsFromPreferences() throws java.lang.Exception {
mSpatialiteMaps2TablesMap.clear();
clearHandlers();
List<SpatialiteMap> spatialiteMaps;
if (ProfilesHandler.INSTANCE.getActiveProfile() == null) {
String baseMapsJson = mPreferences.getString(SpatialiteMap.SPATIALITEMAPS_PREF_KEY, "");
spatialiteMaps = SpatialiteMap.fromJsonString(baseMapsJson);
} else {
if (mSpatialiteMaps != null)
mSpatialiteMaps.clear();
List<String> dbPaths = ProfilesHandler.INSTANCE.getActiveProfile().spatialiteList;
for (String path : dbPaths) {
File file = new File(path);
if (file.exists()) collectTablesFromFile(file);
}
spatialiteMaps = new ArrayList<>();
if (mSpatialiteMaps != null)
spatialiteMaps.addAll(mSpatialiteMaps);
}
connectSpatialiteMaps(spatialiteMaps);
}
private void clearHandlers() {
// close all databases
for (Map.Entry<SpatialiteMap, SpatialiteDatabaseHandler> entry : mSpatialiteMaps2DbHandlersMap.entrySet()) {
try {
entry.getValue().close();
} catch (Exception e) {
GPLog.error(this, null, e);
}
}
mSpatialiteMaps2DbHandlersMap.clear();
}
/**
* Save the given list of SpatialiteMaps to the json preferences.
*
* @param spatialiteMaps
* @throws JSONException
*/
public void saveSpatialiteMapsToPreferences(List<SpatialiteMap> spatialiteMaps) throws JSONException {
if (ProfilesHandler.INSTANCE.getActiveProfile() != null) {
// in the case of profiles, the datasets config is readonly
return;
}
String spatialiteMapJson = SpatialiteMap.toJsonString(spatialiteMaps);
Editor editor = mPreferences.edit();
editor.putString(SpatialiteMap.SPATIALITEMAPS_PREF_KEY, spatialiteMapJson);
editor.apply();
}
/**
* Save the current maps in memory to preferences.
*/
public void saveCurrentSpatialiteMapsToPreferences() {
try {
saveSpatialiteMapsToPreferences(getSpatialiteMaps());
} catch (JSONException e) {
GPLog.error(this, null, e);
}
}
/**
* Adds all SpatialiteMaps contained in the database in the given path.
* <p/>
* <p>SpatialiteMaps and database tables/handlers are added to the lists/maps of this manager.</p>
*
* @param file
* @return true, if at least one supported table was found.
*/
public boolean addSpatialiteMapFromFile(File file) {
boolean foundSpatialiteMap = false;
try {
foundSpatialiteMap = collectTablesFromFile(file);
saveSpatialiteMapsToPreferences(mSpatialiteMaps);
} catch (java.lang.Exception e) {
GPLog.error(this, null, e);
}
return foundSpatialiteMap;
}
/**
* Remove a SpatialiteMap and save to preferences.
* <p/>
* <p>The SpatialiteMap related tables and handlers are also removed and the connection to
* the database is closed.</p>
*
* @param spatialiteMap
* @throws java.lang.Exception
*/
public void removeSpatialiteMap(SpatialiteMap spatialiteMap) throws java.lang.Exception {
mSpatialiteMaps.remove(spatialiteMap);
mSpatialiteMaps2TablesMap.remove(spatialiteMap);
SpatialiteDatabaseHandler handler = mSpatialiteMaps2DbHandlersMap.remove(spatialiteMap);
if (handler != null) {
handler.close();
}
saveSpatialiteMapsToPreferences(mSpatialiteMaps);
}
/**
* Remove a list of SpatialiteMap and save to preferences.
* <p/>
* <p>The SpatialiteMap related tables and handlers are also removed and the connections to
* the databases are closed.</p>
*
* @param spatialiteMaps
* @throws java.lang.Exception
*/
public void removeSpatialiteMaps(List<SpatialiteMap> spatialiteMaps) throws java.lang.Exception {
mSpatialiteMaps.removeAll(spatialiteMaps);
for (SpatialiteMap spatialiteMap : spatialiteMaps) {
mSpatialiteMaps2TablesMap.remove(spatialiteMap);
SpatialiteDatabaseHandler handler = mSpatialiteMaps2DbHandlersMap.remove(spatialiteMap);
if (handler != null) {
handler.close();
}
}
saveSpatialiteMapsToPreferences(mSpatialiteMaps);
}
/**
* Collects all the tables from the given file and adds them to the current list/maps of tables.
* <p>
* <p>This creates default SpatialiteMaps objects for each table.</p>
*
* @param file
* @return true is at leats one supported table was found.
* @throws Exception
*/
private boolean collectTablesFromFile(File file) throws java.lang.Exception {
if (mSpatialiteMaps == null) mSpatialiteMaps = new ArrayList<>();
/*
* SPATIALITE TABLES
*/
boolean foundTables = false;
SpatialiteDatabaseHandler sdbHandler = getDatabaseHandlerForFile(file);
if (sdbHandler != null) {
List<SpatialVectorTable> tables = sdbHandler.getSpatialVectorTables(false);
for (SpatialVectorTable table : tables) {
SpatialiteMap tmpSpatialiteMap = table2BaseMap(table);
if (!mSpatialiteMaps2TablesMap.containsKey(tmpSpatialiteMap)) {
mSpatialiteMaps.add(tmpSpatialiteMap);
mSpatialiteMaps2TablesMap.put(tmpSpatialiteMap, table);
mSpatialiteMaps2DbHandlersMap.put(tmpSpatialiteMap, sdbHandler);
foundTables = true;
}
}
}
if (!foundTables && sdbHandler != null) {
// close this unused db connection
sdbHandler.close();
}
return foundTables;
}
/**
* Collects tables and handlers for a list of SpatialiteMaps objects, that were
* not yet connected to their databases.
*
* @param spatialiteMaps the list of maps to create database links for.
* @return true if tables were found.
* @throws java.lang.Exception
*/
private boolean connectSpatialiteMaps(List<SpatialiteMap> spatialiteMaps) throws java.lang.Exception {
HashMap<String, HashMap<String, SpatialiteMap>> db2Title2Maps = new HashMap<>();
for (SpatialiteMap spatialiteMap : spatialiteMaps) {
HashMap<String, SpatialiteMap> tmpMaps = db2Title2Maps.get(spatialiteMap.databasePath);
if (tmpMaps == null) {
tmpMaps = new HashMap<>();
db2Title2Maps.put(spatialiteMap.databasePath, tmpMaps);
}
tmpMaps.put(spatialiteMap.tableName, spatialiteMap);
}
if (mSpatialiteMaps == null) mSpatialiteMaps = new ArrayList<>();
/*
* SPATIALITE TABLES
*/
boolean foundTables = false;
for (Map.Entry<String, HashMap<String, SpatialiteMap>> entry : db2Title2Maps.entrySet()) {
SpatialiteDatabaseHandler sdbHandler = getDatabaseHandlerForFile(new File(entry.getKey()));
if (sdbHandler != null) {
HashMap<String, SpatialiteMap> maps = entry.getValue();
List<SpatialVectorTable> tables = sdbHandler.getSpatialVectorTables(false);
for (SpatialVectorTable table : tables) {
String tableTitle = table.getTitle();
SpatialiteMap spatialiteMap = maps.get(tableTitle);
if (spatialiteMap != null && !mSpatialiteMaps2TablesMap.containsKey(spatialiteMap)) {
mSpatialiteMaps.add(spatialiteMap);
mSpatialiteMaps2TablesMap.put(spatialiteMap, table);
mSpatialiteMaps2DbHandlersMap.put(spatialiteMap, sdbHandler);
foundTables = true;
}
}
}
}
return foundTables;
}
/**
* Create a vector handler for the given file by connecting to the database.
*
* @param file the file.
* @return the handler or null if the file is not supported.
*/
public SpatialiteDatabaseHandler getDatabaseHandlerForFile(File file) throws IOException {
if (file.exists() && file.isFile()) {
String name = file.getName();
for (ESpatialDataSources spatialiteType : ESpatialDataSources.values()) {
if (!spatialiteType.isSpatialiteBased()) {
continue;
}
String extension = spatialiteType.getExtension();
if (name.endsWith(extension)) {
SpatialiteDatabaseHandler sdb = new SpatialiteDatabaseHandler(file.getAbsolutePath());
if (sdb.isValid()) {
return sdb;
}
}
}
}
return null;
}
/**
* Get an existing db handler by its database path.
*
* @param databasePath the database path.
* @return the handler ot null.
*/
public SpatialiteDatabaseHandler getExistingDatabaseHandlerByPath(String databasePath) {
for (SpatialiteMap spatialiteMap : mSpatialiteMaps) {
if (spatialiteMap.databasePath.equals(databasePath)) {
return mSpatialiteMaps2DbHandlersMap.get(spatialiteMap);
}
}
return null;
}
/**
* Get an existing db handler by a contained SpatialVectorTable.
*
* @param spatialVectorTable the table contained in the requested database.
* @return the handler ot null.
*/
public SpatialiteDatabaseHandler getExistingDatabaseHandlerByTable(SpatialVectorTable spatialVectorTable) {
String databasePath = spatialVectorTable.getDatabasePath();
return getExistingDatabaseHandlerByPath(databasePath);
}
public HashMap<SpatialiteMap, SpatialVectorTable> getSpatialiteMaps2TablesMap() {
getSpatialiteMaps();
return mSpatialiteMaps2TablesMap;
}
public HashMap<SpatialiteMap, SpatialiteDatabaseHandler> getSpatialiteMaps2DbHandlersMap() {
getSpatialiteMaps();
return mSpatialiteMaps2DbHandlersMap;
}
@NonNull
private SpatialiteMap table2BaseMap(AbstractSpatialTable table) {
SpatialiteMap newSpatialiteMap = new SpatialiteMap();
newSpatialiteMap.databasePath = table.getDatabasePath();
newSpatialiteMap.tableName = table.getTableName();
if (table instanceof SpatialVectorTable) {
try {
SpatialVectorTable vectorTable = (SpatialVectorTable) table;
newSpatialiteMap.tableType = vectorTable.getTableTypeDescription();
GeometryType TYPE = GeometryType.forValue(vectorTable.getGeomType());
newSpatialiteMap.geometryType = TYPE.getDescription();
} catch (java.lang.Exception e) {
GPLog.error(this, null, e);
}
}
return newSpatialiteMap;
}
public SpatialVectorTable getTableFromFeature(Feature feature) {
String tableName = feature.getTableName();
String databasePath = feature.getDatabasePath();
for (SpatialiteMap spatialiteMap : mSpatialiteMaps) {
if (spatialiteMap.databasePath.equals(databasePath) && spatialiteMap.tableName.equals(tableName)) {
return mSpatialiteMaps2TablesMap.get(spatialiteMap);
}
}
return null;
}
}