/*
* GeoImporter.java
*
* Created on May 28, 2006, 12:44 PM
*
*/
package ika.geoimport;
import ika.geo.*;
import ika.table.*;
import ika.gui.ProgressIndicator;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
/**
* Abstract class for importing GeoSets.
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public abstract class GeoImporter extends Thread {
final static private String GEOIMPORTER_PROPERTIES =
"ika.geoimport.geoimport";
final static private String GEOIMPORTERS_PROPERTY = "geoimporters";
final static private String GEOPATH_PROPERTY = "geopath";
final static private String GEOSET_PROPERTY = "geoset";
final static public boolean NEW_THREAD = true;
final static public boolean SAME_THREAD = false;
/**
* A ProgressIndicator that is displayed during a long import.
* The ProgressIndicator must be set using setProgressIndicator().
* The default is not to have any ProgressIndicator.
*/
protected ProgressIndicator progressIndicator = null;
/**
* call the complete() method of the progressIndicator when read() finishes.
*/
private boolean callProgressComplete = false;
/**
* A copy of prototypeGeoPath is returned by createGeoPath().
*/
protected static GeoPath prototypeGeoPath;
static {
try {
Properties properties =
ika.utils.PropertiesLoader.loadProperties(GEOIMPORTER_PROPERTIES);
String pathClassName = properties.getProperty(GEOPATH_PROPERTY);
Class pathClass = Class.forName(pathClassName);
Constructor con = pathClass.getConstructor(new Class[]{});
prototypeGeoPath = (GeoPath)con.newInstance(new Object[]{});
} catch (Exception e) {
prototypeGeoPath = new GeoPath();
}
}
/**
* Derived importer classes should not create default GeoPaths, but
* ask createGeoPath for a new GeoPath. This allows for customizing the type
* of new GeoPaths.
*/
protected GeoPath createGeoPath() {
return (GeoPath)prototypeGeoPath.clone();
}
/**
* A copy of prototypeGeoSet is returned by createGeoSet().
*/
protected static GeoSet prototypeGeoSet;
static {
try {
Properties properties =
ika.utils.PropertiesLoader.loadProperties(GEOIMPORTER_PROPERTIES);
String pathClassName = properties.getProperty(GEOSET_PROPERTY);
Class pathClass = Class.forName(pathClassName);
Constructor con = pathClass.getConstructor(new Class[]{});
prototypeGeoSet = (GeoSet)con.newInstance(new Object[]{});
} catch (Exception e) {
prototypeGeoSet = new GeoSet();
}
}
/**
* Derived importer classes should not create default GeoSets, but
* ask createGeoSet for a new GeoSet. This allows for customizing the type
* of new GeoSets.
*/
protected GeoSet createGeoSet() {
return (GeoSet)prototypeGeoSet.clone();
}
/**
* GeoImporters that are asked to import data sources.
* The names of the GeoImporters are loaded from a properties file located
* at GEOIMPORTER_PROPERTIES.
* The property GEOIMPORTERS_PROPERTY contains all class names.
* GeoImporter.findGeoSetImporter(s) searches through this list until it
* finds an importer for a particular data source. The order of the importers
* therefore matters.
*/
private static final ArrayList importers = new ArrayList();
static {
String classesString = null;
try {
// load the class names of the GeoImporters from a properties file.
Properties properties = ika.utils.PropertiesLoader.loadProperties(
GEOIMPORTER_PROPERTIES);
classesString = properties.getProperty(GEOIMPORTERS_PROPERTY);
} catch (Exception exc) {
classesString = "ika.geoimport.ImageImporter," +
"ika.geoimport.ShapeImporter," +
"ika.geoimport.UngenerateImporter," +
"ika.geoimport.ASCIIPointImporter";
}
String[] classes = classesString.split(",");
for (int i = 0; i < classes.length; i++) {
GeoImporter.importers.add(classes[i]);
}
}
/**
* Searches a GeoImporter for a passed URL.
*
* @return The GeoImporter that claims to be able to import the data specified
* by the passed URL or null if no importer can be found.
*/
public static GeoImporter findGeoImporter(java.net.URL url) {
try {
Iterator iterator = GeoImporter.importers.iterator();
while (iterator.hasNext()) {
// construct an instance of an importer
String importerClassName = (String)iterator.next();
Class importerClass = Class.forName(importerClassName);
Class partypes[] = new Class[0];
Constructor cstr = importerClass.getConstructor(partypes);
GeoImporter importer = (GeoImporter)cstr.newInstance((Object[])null);
if (importer.findDataURL(url) != null)
return importer;
}
return null;
} catch (Throwable e) {
return null;
}
}
/**
* Searches a GeoImporter for a passed file path.
* @return The GeoImporter that should be able to import the data or null
* if no importer could be found.
*/
public static GeoImporter findGeoImporter(String filePath) {
java.net.URL url = ika.utils.URLUtils.filePathToURL(filePath);
if (url == null)
return null;
return GeoImporter.findGeoImporter(url);
}
/**
* Searches GeoSetImporters for a set of passed files. Makes sure that
* only one importer is returned per importable data set. A data set can
* consist of multiple files.
*
* @param files A collection of File objects.
* @return An Hashtable containing a series of file paths as String(keys)
* and the associated GeoImporter (value).
*/
public static Hashtable findGeoImporters(Collection files) {
Hashtable url_importer_pairs = new Hashtable();
if (files == null)
return url_importer_pairs;
try {
// loop over all files
Iterator fileIterator = files.iterator();
while (fileIterator.hasNext()) {
File file = (File) fileIterator.next();
java.net.URL url = file.toURL();
// ask each importer whether it can import the file
Iterator importerIterator = GeoImporter.importers.iterator();
while (importerIterator.hasNext()) {
// build an instance of the importer
GeoImporter importer = null;
String importerClassName = "";
try {
// construct an instance of an importer
importerClassName = (String)importerIterator.next();
Class importerClass = Class.forName(importerClassName);
Constructor cstr = importerClass.getConstructor(new Class[0]);
importer = (GeoImporter)cstr.newInstance(new Object[0]);
} catch (Throwable e) {
System.err.println ("Could not instantiate " + importerClassName);
e.printStackTrace();
importer = null;
}
// ask the importer whether it can import the file
try {
if (importer != null) {
// ask the importer for the file containing the data
java.net.URL dataURL = importer.findDataURL(url);
if (dataURL != null) {
url_importer_pairs.put(dataURL, importer);
break; // no need to ask other importers, go to next file.
}
}
} catch (Throwable e) {}
}
}
} catch (Throwable e) {}
return url_importer_pairs;
}
/**
* Creates a new instance of GeoImporter
*/
protected GeoImporter() {
}
/**
* Read data from the passed URL and return as GeoObject.
* The concrete implementation of importData of derived classes must use
* this.createGeoPath() to create new GeoPaths, and
* this.createGeoSet() to create new GeoSets.
* importData() is protected. Use read() to import data, wich takes care
* of displaying a progress indicator and calling the DataReceiver.
*/
protected abstract GeoObject importData(java.net.URL url) throws IOException;
/**
* Returns a human readable string with the name of this GeoImporter.
*/
public abstract String getImporterName();
/**
* Read some data. The data is stored by the DataReceiver. If an error
* occurs, DataReceiver.error() is called.
* @param url The data source to read.
* @param mapDataReceiver DataReceiver that is responsible for storing imported data.
* @param newOrSameThread If newOrSameThread equals GeoImporter.NEW_THREAD an
* additional thread is started to read the data. If newOrSameThread equals
* GeoImporter.SAME_THREAD no additional thread is started.
*/
public void read(java.net.URL url, DataReceiver mapDataReceiver,
boolean newOrSameThread) {
// initialize the progress indicator.
// the run() method is responsible for closing the progress indicator.
if (this.progressIndicator != null) {
this.progressIndicator.start();
}
this.threadParams.url = url;
this.threadParams.dataReceiver = mapDataReceiver;
if (newOrSameThread == GeoImporter.NEW_THREAD) {
this.start();
} else {
this.run();
}
}
/**
* Read data from the passed file and return as GeoObject.
*/
public GeoObject read (String filePath ) throws IOException {
java.net.URL url = ika.utils.URLUtils.filePathToURL(filePath);
SynchroneDataReceiver dataReceiver = new SynchroneDataReceiver();
this.read(url, dataReceiver, GeoImporter.SAME_THREAD);
return dataReceiver.getImportedData();
}
/**
* Read data from the passed file and return as GeoObject.
*/
public GeoObject read (String filePath, GeoSet destinationGeoSet) throws IOException {
java.net.URL url = ika.utils.URLUtils.filePathToURL(filePath);
SynchroneDataReceiver dataReceiver = new SynchroneDataReceiver(destinationGeoSet);
this.read(url, dataReceiver, GeoImporter.SAME_THREAD);
return dataReceiver.getImportedData();
}
/**
* A private utility class to pass parameters to the thread that reads the
* data.
*/
private class ThreadParams {
public java.net.URL url;
public DataReceiver dataReceiver;
};
ThreadParams threadParams = new ThreadParams();
/**
* Read data.
*/
@Override
public void run() {
try {
if (threadParams.dataReceiver == null || threadParams.url == null) {
return;
}
GeoObject importedGeoObj = importData(threadParams.url);
// pass the imported GeoObject to the DataReceiver, which is
// responsible for storing it.
if (this instanceof ika.table.TableLinkImporter) {
TableLink tableLink = ((TableLinkImporter) this).getTableLink();
if (tableLink == null) {
threadParams.dataReceiver.add(importedGeoObj);
} else {
threadParams.dataReceiver.add(tableLink);
}
} else {
threadParams.dataReceiver.add(importedGeoObj);
}
} catch (Exception e) {
// hide the progress dialog
if (progressIndicator != null && callProgressComplete) {
progressIndicator.complete();
}
// pass the exception to the DataReceiver, which is responsible for
// informing the user.
threadParams.dataReceiver.error(e, threadParams.url);
} finally {
if (this.progressIndicator != null && this.callProgressComplete) {
this.progressIndicator.complete();
}
}
}
/**
* An importer may have to access multiple files to importData data, or it
* may not be clear to the user which file in a file set contains the data
* (e.g. a Shape file data set also contains a .shx file). findDataURL will
* search for the file that contains the actual data. It always returns the
* same type of file if the data is spread on multiple files. E.g. for a
* Shape file data set: input data.shx, output data.shp
* @param url The URL pointing to a potential data source.
* @return The URL pointing to the data or null if no valid data is found.
*/
protected abstract java.net.URL findDataURL(java.net.URL url);
public ProgressIndicator getProgressIndicator() {
return progressIndicator;
}
public void setProgressIndicator(ProgressIndicator progressIndicator) {
this.progressIndicator = progressIndicator;
}
public void setProgressIndicator(ProgressIndicator progressIndicator, boolean callProgressComplete) {
this.progressIndicator = progressIndicator;
this.callProgressComplete = callProgressComplete;
}
@Override
public String toString() {
return this.getImporterName();
}
}