content: package tim.prune.load;
  
  import java.io.BufferedReader;
  import java.io.File;
  import java.io.InputStreamReader;
  import java.util.ArrayList;
  
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JDialog;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JProgressBar;
  import javax.swing.SwingUtilities;
  import javax.xml.parsers.SAXParser;
  import javax.xml.parsers.SAXParserFactory;
  
  import tim.prune.App;
  import tim.prune.ExternalTools;
  import tim.prune.GenericFunction;
  import tim.prune.I18nManager;
  import tim.prune.config.Config;
  import tim.prune.data.SourceInfo;
  import tim.prune.load.babel.BabelFilterPanel;
  import tim.prune.load.xml.XmlFileLoader;
  import tim.prune.load.xml.XmlHandler;
  import tim.prune.save.GpxExporter;
  
  /**
   * Superclass to manage the loading of data using GpsBabel
   * Subclasses handle either from GPS or from file
   */
  public abstract class BabelLoadFunction extends GenericFunction
  {
  	private boolean _gpsBabelChecked = false;
  	protected JDialog _dialog = null;
  	// Checkboxes for which kinds of points to load
  	protected JCheckBox _waypointCheckbox = null, _trackCheckbox = null;
  	// Checkbox to save to file or not
  	protected JCheckBox _saveCheckbox = null;
  	private JButton _okButton = null;
  	protected JProgressBar _progressBar = null;
  	protected File _saveFile = null;
  	protected boolean _cancelled = false;
  	protected BabelFilterPanel _filterPanel = null;
  
  
  	/**
  	 * Constructor
  	 * @param inApp Application object to inform of data load
  	 */
  	public BabelLoadFunction(App inApp) {
  		super(inApp);
  	}
  
  	/**
  	 * Open the GUI to select options and start the load
  	 */
  	public void begin()
  	{
  		// Check if gpsbabel looks like it's installed
  		if (_gpsBabelChecked || ExternalTools.isToolInstalled(getConfig(), ExternalTools.TOOL_GPSBABEL)
  			|| JOptionPane.showConfirmDialog(_dialog,
  				I18nManager.getText("dialog.gpsload.nogpsbabel"), getName(),
  				JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
  		{
  			_gpsBabelChecked = true;
  			// Make dialog window
  			if (_dialog == null)
  			{
  				_dialog = new JDialog(_parentFrame, getName(), true);
  				_dialog.setLocationRelativeTo(_parentFrame);
  				_dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
  				_okButton = new JButton(I18nManager.getText("button.ok"));
  				_okButton.addActionListener((e) -> okPressed());
  				_dialog.getContentPane().add(makeDialogComponents(_okButton));
  				_dialog.pack();
  			}
  			// Initialise progress bars, buttons
  			enableOkButton();
  			setupProgressBar(true);
  			initDialog(); // do any subclass-specific init here
  			_dialog.setVisible(true);
  		}
  	}
  
  	/**
  	 * React to ok button pressed
  	 */
  	protected void okPressed()
  	{
  		_cancelled = false;
  		new Thread(this::run).start();
  	}
  
  	/**
  	 * @param inOkButton ok button
  	 * @return a panel containing the main dialog components
  	 */
  	protected abstract JPanel makeDialogComponents(JButton inOkButton);
  
  
  	/** Do any subclass-specific dialog initialisation necessary */
  	protected void initDialog()
  	{
  		// GPSBabel filter, if any
  		_filterPanel.setFilterString(getConfig().getConfigString(Config.KEY_GPSBABEL_FILTER));
  	}
  
  	/**
  	 * @param inStart true if the dialog is restarting
  	 */
  	private void setupProgressBar(boolean inStart)
  	{
  		// set visibility
  		_progressBar.setVisible(!inStart);
  		// set indeterminate flags, initial value
  		_progressBar.setIndeterminate(false);
  		_progressBar.setValue(0);
  	}
  
  
  	/**
  	 * Enable or disable the ok button
  	 */
  	protected void enableOkButton()
  	{
  		_okButton.setEnabled(isInputOk());
  	}
  
  	/**
  	 * @return true if input fields of dialog are valid
  	 */
  	protected abstract boolean isInputOk();
  
  	/**
  	 * Run method for performing tasks in separate thread
  	 */
  	public void run()
  	{
  		_okButton.setEnabled(false);
  		setupProgressBar(false);
  		if (isInputOk())
  		{
  			_progressBar.setIndeterminate(true);
  			_saveFile = null;
  			try {
  				callGpsBabel();
  			}
  			catch (Exception e)
  			{
  				_app.showErrorMessageNoLookup(getNameKey(), e.getMessage());
  				_cancelled = true;
  			}
  		}
  		setupProgressBar(true);
  		enableOkButton();
  
  		// Close dialog
  		if (!_cancelled) {
  			_dialog.dispose();
  		}
  	}
  
  
  	/**
  	 * Execute the call to gpsbabel and pass the results back to the app
  	 */
  	private void callGpsBabel() throws Exception
  	{
  		// Set up command to call gpsbabel
  		String[] commands = getCommandArray();
  		// Save GPS settings in config
  		saveConfigValues();
  
  		String errorMessage = "", errorMessage2 = "";
  		XmlHandler handler = null;
  		Process process = Runtime.getRuntime().exec(commands);
  		String line = null;
  
  		if (_saveFile != null)
  		{
  			// data is being saved to file, so need to wait for it to finish
  			process.waitFor();
  			// try to read error message, if any
  			try {
  				BufferedReader r = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  				while ((line = r.readLine()) != null) {
  					errorMessage += line + "\n";
  				}
  				// Close error stream
  				try {
  					r.close();
  				} catch (Exception e) {}
  			}
  			catch (Exception e) {} // couldn't get error message
  
  			// Trigger it to be loaded by app
  			if (process.exitValue() == 0)
  			{
  				SwingUtilities.invokeLater(() -> {
  					ArrayList<File> fileList = new ArrayList<>();
  					fileList.add(_saveFile);
  					_app.loadDataFiles(fileList);
  				});
  			}
  			else if (errorMessage.length() > 0) {
  				throw new Exception(errorMessage);
  			}
  			else {
  				throw new Exception(I18nManager.getText("error.gpsload.unknown"));
  			}
  		}
  		else
  		{
  			// Pass input stream to try to parse the xml
  			try
  			{
  				XmlFileLoader xmlLoader = new XmlFileLoader(_app);
  				SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
  				saxParser.parse(process.getInputStream(), xmlLoader);
  				handler = xmlLoader.getHandler();
  				if (handler == null) {
  					errorMessage = "Null handler";
  				}
  			}
  			catch (Exception e) {
  				errorMessage = e.getMessage();
  			}
  
  			// Read the error stream to see if there's a better error message there
  			BufferedReader r = new BufferedReader(new InputStreamReader(process.getErrorStream()));
  			while ((line = r.readLine()) != null) {
  				errorMessage2 += line + "\n";
  			}
  			// Close error stream
  			try {
  				r.close();
  			} catch (Exception e) {}
  
  			if (errorMessage2.length() > 0) {errorMessage = errorMessage2;}
  			if (errorMessage.length() > 0) {throw new Exception(errorMessage);}
  
  			// Use a loader to finish off
  			new FileTypeLoader(_app).loadData(handler, getSourceInfo(), false, null);
  		}
  	}
  
  
  	/**
  	 * Get the commands to call
  	 * @return String array containing commands
  	 */
  	private String[] getCommandArray()
  	{
  		ArrayList<String> commandList = new ArrayList<String>();
  		// Firstly the command for gpsbabel itself
  		final String command = getConfig().getConfigString(Config.KEY_GPSBABEL_PATH);
  		commandList.add(command);
  		// Then whether to load waypoints or track points
  		final boolean loadWaypoints = _waypointCheckbox.isSelected();
  		final boolean loadTrack = _trackCheckbox.isSelected();
  		if (loadWaypoints) {
  			commandList.add("-w");
  		}
  		if (loadTrack) {
  			commandList.add("-t");
  		}
  		// Input format
  		commandList.add("-i");
  		commandList.add(getInputFormat());
  		// File path
  		commandList.add("-f");
  		commandList.add(getFilePath());
  		// Filters, if any
  		final String filter = _filterPanel.getFilterString();
  		if (filter != null && !filter.equals(""))
  		{
  			for (String arg : filter.split(" "))
  			{
  				if (arg.length() > 0) {
  					commandList.add(arg);
  				}
  			}
  		}
  		// Output format
  		commandList.add("-o");
  		commandList.add("gpx");
  		// Where to
  		commandList.add("-F");
  		String whereTo = "-";
  		// Do we want to save the gpx straight to file?
  		if (_saveCheckbox.isSelected())
  		{
  			// Select file to save to
  			String configDir = getConfig().getConfigString(Config.KEY_TRACK_DIR);
  			_saveFile = GpxExporter.chooseGpxFile(_parentFrame, "", configDir);
  			if (_saveFile != null) {
  				whereTo = _saveFile.getAbsolutePath();
  			}
  		}
  		commandList.add(whereTo);
  		// Convert to string array
  		String[] args = new String[] {};
  		return commandList.toArray(args);
  	}
  
  	/**
  	 * @return SourceInfo object corresponding to the load
  	 */
  	protected abstract SourceInfo getSourceInfo();
  
  	/**
  	 * @return complete file path or device path for gpsbabel call
  	 */
  	protected abstract String getFilePath();
  
  	/**
  	 * @return file name or device name
  	 */
  	protected abstract String getInputFormat();
  
  	/**
  	 * Save any config values necessary
  	 */
  	protected abstract void saveConfigValues();
  }
  