Package slash.navigation.babel

Source Code of slash.navigation.babel.BabelFormat

/*
    This file is part of RouteConverter.

    RouteConverter 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 2 of the License, or
    (at your option) any later version.

    RouteConverter 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 RouteConverter; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Copyright (C) 2007 Christian Pesch. All Rights Reserved.
*/

package slash.navigation.babel;

import slash.common.type.CompactCalendar;
import slash.navigation.base.BaseNavigationFormat;
import slash.navigation.base.ParserContext;
import slash.navigation.base.ParserContextImpl;
import slash.navigation.base.RouteCharacteristics;
import slash.navigation.common.NavigationPosition;
import slash.navigation.gpx.Gpx10Format;
import slash.navigation.gpx.GpxPosition;
import slash.navigation.gpx.GpxRoute;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import static java.io.File.createTempFile;
import static java.lang.Thread.sleep;
import static java.util.Arrays.asList;
import static slash.common.io.Directories.getTemporaryDirectory;
import static slash.common.io.Externalization.extractFile;
import static slash.common.io.InputOutput.DEFAULT_BUFFER_SIZE;
import static slash.common.io.InputOutput.copy;
import static slash.common.system.Platform.*;
import static slash.navigation.base.RouteCharacteristics.*;

/**
* The base of all GPSBabel based formats.
*
* @author Christian Pesch, Axel Uhl
*/

public abstract class BabelFormat extends BaseNavigationFormat<GpxRoute> {
    private static final Logger log = Logger.getLogger(BabelFormat.class.getName());
    private static final Preferences preferences = Preferences.userNodeForPackage(BabelFormat.class);
    private static final String BABEL_PATH_PREFERENCE = "babelPath";
    private static final String BABEL_INTERFACE_FORMAT_NAME = "gpx";
    private static final String[] ROUTE_WAYPOINTS_TRACKS = new String[]{"-r", "-w", "-t"};
    private Gpx10Format gpxFormat;

    private Gpx10Format getGpxFormat() {
        if (gpxFormat == null)
            gpxFormat = createGpxFormat();
        return gpxFormat;
    }

    protected Gpx10Format createGpxFormat() {
        return new Gpx10Format(false, true);
    }

    public static String getBabelPathPreference() {
        return preferences.get(BABEL_PATH_PREFERENCE, "");
    }

    public static void setBabelPathPreference(String babelPathPreference) {
        preferences.put(BABEL_PATH_PREFERENCE, babelPathPreference);
    }

    private int getReadCommandExecutionTimeoutPreference() {
        return preferences.getInt("readCommandExecutionTimeout", 10000);
    }

    private int getWriteCommandExecutionTimeOutPreference() {
        return preferences.getInt("writeCommandExecutionTimeout", 30000);
    }

    protected abstract String getFormatName();

    public boolean isWritingRouteCharacteristics() {
        return false;
    }

    protected abstract boolean isStreamingCapable();

    protected String[] getGlobalOptions() {
        return ROUTE_WAYPOINTS_TRACKS;
    }

    @SuppressWarnings({"UnusedDeclaration"})
    protected String getFormatOptions(GpxRoute route) {
        return "";
    }

    protected List<RouteCharacteristics> getBabelCharacteristics() {
        return asList(Route, Track, Waypoints);
    }

    public int getMaximumPositionCount() {
        return UNLIMITED_MAXIMUM_POSITION_COUNT;
    }

    @SuppressWarnings({"unchecked"})
    public <P extends NavigationPosition> GpxRoute createRoute(RouteCharacteristics characteristics, String name, List<P> positions) {
        return new GpxRoute(new Gpx10Format(), characteristics, name, null, (List<GpxPosition>) positions);
    }

    // stream

    private Process execute(String babel, String sourceFormat, String targetFormat, String[] globalFlags) throws IOException {
        List<String> args = new ArrayList<String>();
        args.add(babel);
        args.addAll(asList(globalFlags));
        args.addAll(asList("-i", sourceFormat, "-f", "-",
                "-o", targetFormat, "-F", "-"));
        log.info("Executing '" + args + "'");

        args = considerShellScriptForBabel(babel, args);

        try {
            return Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
        } catch (IOException e) {
            throw new BabelException("Cannot execute '" + args + "'", babel, e);
        }
    }

    private Thread observeProcess(final Process process, final int commandExecutionTimeout) {
        return new Thread(new Runnable() {
            public void run() {
                try {
                    sleep(commandExecutionTimeout);
                } catch (InterruptedException e) {
                    // intentionally left empty
                }
                try {
                    int exitValue = process.exitValue();
                    log.fine("gpsbabel process terminated with exit value " + exitValue);
                } catch (IllegalThreadStateException itse) {
                    log.warning("gpsbabel process for format " + getFormatName() + " didn't terminate after " + commandExecutionTimeout + "ms; destroying it");
                    process.destroy();
                }
            }
        }, "BabelObserver");
    }

    private void pumpStream(final InputStream input, final OutputStream output, final String streamName, final boolean closeOutput) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    try {
                        byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
                        int count = 0;
                        while (count >= 0) {
                            count = input.read(buffer);
                            if (count > 0) {
                                output.write(buffer, 0, count);
                                String output = new String(buffer).trim();
                                log.fine("Read " + count + " bytes of " + streamName + " from gpsbabel process: '" + output + "'");
                            }
                        }
                    } finally {
                        input.close();
                        if (closeOutput)
                            output.close();
                    }
                } catch (IOException e) {
                    log.fine("Could not pump " + streamName + " of gpsbabel process: " + e);
                }
            }
        }, "BabelStreamPumper-" + streamName).start();
    }

    private Process startBabel(InputStream source, String sourceFormat, String targetFormat, String[] commandLineFlags) throws IOException {
        String babel = findBabel();
        Process process = execute(babel, sourceFormat, targetFormat, commandLineFlags);
        pumpStream(source, process.getOutputStream(), "input", true);
        pumpStream(process.getErrorStream(), System.err, "error", false);
        return process;
    }

    private void readStream(InputStream source, CompactCalendar startDate, ParserContext<GpxRoute> context) throws Exception {
        Process process = startBabel(source, getFormatName(), BABEL_INTERFACE_FORMAT_NAME, ROUTE_WAYPOINTS_TRACKS);
        Thread observer = observeProcess(process, getReadCommandExecutionTimeoutPreference());
        observer.start();
        InputStream target = process.getInputStream();
        getGpxFormat().read(target, startDate, context);
        observer.interrupt();
        target.close();
    }

    // temp file

    private String escapeFilePathWithSpaces(String filePath) {
        if (isLinux() || isMac()) {
            filePath = "\"" + filePath + "\"";
        }
        return filePath;
    }

    private boolean startBabel(File source, String sourceFormat,
                               File target, String targetFormat,
                               String[] globalFlags, String formatFlags,
                               int timeout) throws IOException {
        String babel = findBabel();
        List<String> args = new ArrayList<String>();
        args.add(babel);
        args.addAll(asList(globalFlags));
        args.addAll(asList("-i", sourceFormat,
                "-f", escapeFilePathWithSpaces(source.getAbsolutePath()),
                "-o", escapeFilePathWithSpaces(targetFormat + formatFlags),
                "-F", escapeFilePathWithSpaces(target.getAbsolutePath())));
        log.info("Executing '" + args + "'");

        args = considerShellScriptForBabel(babel, args);

        int exitValue = execute(babel, args, timeout);
        log.info("Executed '" + args + "' with exit value: " + exitValue + " target exists: " + target.exists());
        return exitValue == 0;
    }

    private static final int COMMAND_EXECUTION_RECHECK_INTERVAL = 250;

    private int execute(String babelPath, List<String> args, int timeout) throws IOException {
        Process process;
        try {
            process = Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
        } catch (IOException e) {
            throw new BabelException("Cannot execute '" + args + "'", babelPath, e);
        }

        InputStream inputStream = new BufferedInputStream(process.getInputStream());
        InputStream errorStream = new BufferedInputStream(process.getErrorStream());

        boolean hasExitValue = false;
        int exitValue = -1;

        while (!hasExitValue) {
            try {
                while (inputStream.available() > 0 || errorStream.available() > 0) {
                    readStream(inputStream, "input");
                    readStream(errorStream, "error");
                }
            } catch (IOException e) {
                log.severe("Couldn't read response: " + e);
            }

            try {
                exitValue = process.exitValue();
                hasExitValue = true;
            } catch (IllegalThreadStateException e) {
                try {
                    sleep(COMMAND_EXECUTION_RECHECK_INTERVAL);
                    timeout = timeout - COMMAND_EXECUTION_RECHECK_INTERVAL;
                    if (timeout < 0 && timeout >= -COMMAND_EXECUTION_RECHECK_INTERVAL) {
                        log.severe("Command doesn't terminate. Shutting down args...");
                        process.destroy();
                    } else if (timeout < 0) {
                        log.severe("Command still doesn't terminate");
                        sleep(COMMAND_EXECUTION_RECHECK_INTERVAL);
                    }
                } catch (InterruptedException e1) {
                    // doesn't matter
                }
            }
        }

        if (!hasExitValue) {
            log.severe("Command doesn't return exit value. Shutting down args...");
            process.destroy();
        }

        try {
            readStream(inputStream, "input");
            readStream(errorStream, "error");
        } catch (IOException e) {
            log.severe("Couldn't read final response: " + e);
        }

        log.info("Executed '" + process + "' with exit value: " + exitValue);
        return exitValue;
    }

    private void readStream(InputStream inputStream, String streamName) throws IOException {
        byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
        int count = 0;
        while (inputStream.available() > 0 && count < buffer.length) {
            buffer[count++] = (byte) inputStream.read();
        }
        String output = new String(buffer).trim();
        log.fine("Read " + count + " bytes of " + streamName + " output: '" + output + "'");
    }

    private void readFile(InputStream source, CompactCalendar startDate, ParserContext<GpxRoute> context) throws Exception {
        File sourceFile = null, targetFile = null;
        try {
            sourceFile = createTempFile("babel-read-source", "." + getFormatName(), getTemporaryDirectory());
            copy(source, new FileOutputStream(sourceFile));
            targetFile = createTempFile("babel-read-target", "." + BABEL_INTERFACE_FORMAT_NAME, getTemporaryDirectory());
            boolean successful = startBabel(sourceFile, getFormatName(), targetFile, BABEL_INTERFACE_FORMAT_NAME, ROUTE_WAYPOINTS_TRACKS, "", getReadCommandExecutionTimeoutPreference());
            if (successful) {
                InputStream target = new IllegalCharacterFilterInputStream(new FileInputStream(targetFile));
                getGpxFormat().read(target, startDate, context);
                target.close();
                log.fine("Successfully converted " + sourceFile + " to " + targetFile);
            }
        } finally {
            delete(sourceFile);
            delete(targetFile);
        }
    }

    // both

    private File checkIfBabelExists(String path) {
        File file = new File(path);
        return file.exists() ? file : null;
    }

    private String findBabel() throws IOException {
        // 1. check if there is a preference and try to find its file
        File babelFile = getBabelPathPreference() != null ? new File(getBabelPathPreference()) : null;
        if (babelFile == null || !babelFile.exists()) {
            babelFile = null;
        }

        // 2a. look for "c:\Program Files\GPSBabel\gpsbabel.exe"
        if (babelFile == null && isWindows()) {
            babelFile = checkIfBabelExists(System.getenv("ProgramFiles") + "\\GPSBabel\\gpsbabel.exe");
        }

        // 2b. look for "c:\Program Files (x86)\GPSBabel\gpsbabel.exe"
        if (babelFile == null && isWindows()) {
            babelFile = checkIfBabelExists(System.getenv("ProgramFiles(x86)") + "\\GPSBabel\\gpsbabel.exe");
        }

        // 3. look for "/usr/bin/gpsbabel" in path
        if (babelFile == null && !isWindows()) {
            babelFile = checkIfBabelExists("/usr/bin/gpsbabel");
        }

        // 4. extract from classpath into temp directrory and execute there
        if (babelFile == null) {
            String path = getOperationSystem() + "/" + getArchitecture() + "/";
            if (isWindows()) {
                extractFile(path + "libexpat.dll");
                babelFile = extractFile(path + "gpsbabel.exe");
            } else if (isLinux() || isMac()) {
                babelFile = extractFile(path + "gpsbabel");
            }
        }

        // 4. look for unqualified "gpsbabel"
        return babelFile != null ? babelFile.getAbsolutePath() : "gpsbabel";
    }

    private List<String> considerShellScriptForBabel(String babel, List<String> args) throws IOException {
        if (isLinux() || isMac()) {
            File shellScript = createShellScript(babel, args);
            args = asList("/bin/sh", shellScript.getAbsolutePath());
        }
        return args;
    }

    private File createShellScript(String babelPath, List<String> args) throws IOException {
        File temp = createTempFile("gpsbabel", ".sh", getTemporaryDirectory());
        temp.deleteOnExit();
        BufferedWriter writer = new BufferedWriter(new FileWriter(temp));
        writer.write("#!/bin/sh");
        writer.newLine();
        writer.write("`which chmod` a+x \"" + babelPath + "\"");
        writer.newLine();
        for (String arg : args) {
            writer.write(arg);
            writer.write(" ");
        }
        writer.newLine();
        writer.flush();
        writer.close();
        return temp;
    }

    // filter/sanitizing after reading

    private List<GpxRoute> filterValidRoutes(List<GpxRoute> routes) {
        if (routes == null)
            return null;

        List<GpxRoute> result = new ArrayList<GpxRoute>();
        for (GpxRoute aRoute : routes) {
            GpxRoute route = sanitizeRoute(aRoute);
            if (isValidRoute(route))
                result.add(route);
        }
        return result.size() > 0 ? result : null;
    }

    protected boolean isValidRoute(GpxRoute route) {
        return true;
    }

    protected GpxRoute sanitizeRoute(GpxRoute route) {
        return route;
    }

    protected void delete(File file) {
        if (file != null && file.exists()) {
            if (!file.delete())
                log.warning("Cannot delete babel file " + file);
        }
    }


    public void read(InputStream source, CompactCalendar startDate, ParserContext<GpxRoute> context) throws Exception {
        ParserContext<GpxRoute> gpxContext = new ParserContextImpl<GpxRoute>();
        if (isStreamingCapable()) {
            readStream(source, startDate, gpxContext);
        } else {
            readFile(source, startDate, gpxContext);
        }
        List<GpxRoute> result = filterValidRoutes(gpxContext.getRoutes());
        if (result != null && result.size() > 0) {
            context.appendRoutes(result);
            log.fine("Successfully converted " + getName() + " to " + BABEL_INTERFACE_FORMAT_NAME);
        }
    }

    public void write(GpxRoute route, OutputStream target, int startIndex, int endIndex) throws IOException {
        File sourceFile = null, targetFile = null;
        try {
            sourceFile = createTempFile("babel-write-source", "." + BABEL_INTERFACE_FORMAT_NAME, getTemporaryDirectory());
            getGpxFormat().write(route, new FileOutputStream(sourceFile), startIndex, endIndex, getBabelCharacteristics());
            targetFile = createTempFile("babel-write-target", getExtension(), getTemporaryDirectory());

            boolean successful = startBabel(sourceFile, BABEL_INTERFACE_FORMAT_NAME, targetFile, getFormatName(), getGlobalOptions(), getFormatOptions(route), getWriteCommandExecutionTimeOutPreference());
            if (!successful)
                throw new IOException("Could not convert " + sourceFile + " to " + targetFile);

            copy(new FileInputStream(targetFile), target);
            log.fine("Successfully converted " + sourceFile + " to " + targetFile);
        } finally {
            delete(sourceFile);
            delete(targetFile);
        }
    }

    public void write(List<GpxRoute> routes, OutputStream target) throws IOException {
        File sourceFile = null, targetFile = null;
        try {
            sourceFile = createTempFile("babel-write-all-source", "." + BABEL_INTERFACE_FORMAT_NAME, getTemporaryDirectory());
            getGpxFormat().write(routes, new FileOutputStream(sourceFile));
            targetFile = createTempFile("babel-write-all-target", getExtension(), getTemporaryDirectory());

            boolean successful = startBabel(sourceFile, BABEL_INTERFACE_FORMAT_NAME, targetFile, getFormatName(), getGlobalOptions(), getFormatOptions(routes.get(0)), getWriteCommandExecutionTimeOutPreference());
            if (!successful)
                throw new IOException("Could not convert " + sourceFile + " to " + targetFile);

            copy(new FileInputStream(targetFile), target);
            log.fine("Successfully converted " + sourceFile + " to " + targetFile);
        } finally {
            delete(sourceFile);
            delete(targetFile);
        }
    }
}
TOP

Related Classes of slash.navigation.babel.BabelFormat

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.