for (int i = 0; i < menuCount; i++) {
                    final JMenu menu = menuBar.getMenu(i);
                    final char c = (char) menu.getMnemonic();
                    if (Character.toLowerCase(keystroke.getKeyCode()) == Character.toLowerCase(c)) {
                        JOptionPane.showMessageDialog(parentComponent, menu.getText(), TextUtils.getText("used_in_menu"), JOptionPane.WARNING_MESSAGE);
                        return false;
                    }
                }

/*
 *  Freeplane - mind map editor
 *
 *  This file author is Dimitry
 *
 */
package org.freeplane.core.ui;

import java.awt.Component;
import java.awt.Event;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JDialog;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.tree.DefaultMutableTreeNode;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.resources.components.GrabKeyDialog;
import org.freeplane.core.resources.components.IKeystrokeValidator;
import org.freeplane.core.ui.IEditHandler.FirstAction;
import org.freeplane.core.ui.IndexedTree.Node;
import org.freeplane.core.ui.components.FreeplaneMenuBar;
import org.freeplane.core.ui.components.IKeyBindingManager;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.MenuUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.mode.Controller;

/**
 * @author Dimitry Polivaev
 * 20.04.2009
 */
public class AccelerateableAction implements IFreeplaneAction {

    private class KeystrokeValidator implements IKeystrokeValidator {

        private final Component parentComponent;

        private final Object key;

        private final JMenuItem editedItem;

        private KeystrokeValidator(Component parentComponent, Object key, JMenuItem editedItem) {
            this.parentComponent = parentComponent;
            this.key = key;
            this.editedItem = editedItem;
        }

        private boolean checkForOverwriteShortcut(final DefaultMutableTreeNode menubarNode, final KeyStroke keystroke) {
            final Node priorAssigned = MenuUtils.findAssignedMenuItemNodeRecursively(menubarNode, keystroke);
            if (priorAssigned == null || editedItem.equals(priorAssigned.getUserObject())) {
                return true;
            }
            return replaceOrCancel(priorAssigned, ((JMenuItem) priorAssigned.getUserObject()).getText());
        }

        private boolean replaceOrCancel(Node menuItemNode, String oldMenuItemTitle) {
            if (askForReplaceShortcutViaDialog(oldMenuItemTitle, parentComponent)) {
                menuBuilder.setAccelerator(menuItemNode, null);
                final String shortcutKey = menuBuilder.getShortcutKey(menuItemNode.getKey().toString());
                ResourceController.getResourceController().setProperty(shortcutKey, "");
                return true;
            } else {
                return false;
            }
        }

        public boolean isValid(final KeyStroke keystroke, final Character keyChar) {
            if (keystroke == null) {
                return true;
            }
            final Node menuBarNode = menuBuilder.getMenuBar(menuBuilder.get(key));
            if (menuBarNode == null) {
                return true;
            }
            if (keyChar != KeyEvent.CHAR_UNDEFINED && (keystroke.getModifiers() & (Event.ALT_MASK | Event.CTRL_MASK | Event.META_MASK)) == 0) {
                final String keyTypeActionString = ResourceController.getResourceController().getProperty("key_type_action", FirstAction.EDIT_CURRENT.toString());
                FirstAction keyTypeAction = FirstAction.valueOf(keyTypeActionString);
                return FirstAction.IGNORE.equals(keyTypeAction);
            }
            if ((keystroke.getModifiers() & (Event.ALT_MASK | Event.CTRL_MASK | Event.META_MASK)) == Event.ALT_MASK) {
                final JMenuBar menuBar = (JMenuBar) menuBarNode.getUserObject();
                final int menuCount = menuBar.getMenuCount();

            }
            if (!checkForOverwriteShortcut(menuBarNode, keystroke)) {
                return false;
            }
            final KeyStroke derivedKS = FreeplaneMenuBar.derive(keystroke, keyChar);
            if (derivedKS == keystroke) {
                return true;
            }
            return checkForOverwriteShortcut(menuBarNode, derivedKS);
        }
    }

    private final MenuBuilder menuBuilder;

    final private AFreeplaneAction originalAction;

    private static JDialog setAcceleratorOnNextClickActionDialog;

    private static KeyStroke acceleratorForNextClickedAction;

    public static boolean isNewAcceleratorOnNextClickEnabled() {
        return setAcceleratorOnNextClickActionDialog != null;
    }

    private static final String SET_ACCELERATOR_ON_NEXT_CLICK_ACTION = "set_accelerator_on_next_click_action";

    static void setNewAcceleratorOnNextClick(KeyStroke accelerator) {
        if (AccelerateableAction.isNewAcceleratorOnNextClickEnabled()) {
            return;
        }
        acceleratorForNextClickedAction = accelerator;
        String title = TextUtils.getText("SetAccelerator.dialogTitle");
        String text = TextUtils.getText(SET_ACCELERATOR_ON_NEXT_CLICK_ACTION);
        if (accelerator != null)
            text = TextUtils.format("SetAccelerator.keystrokeDetected", toString(accelerator)) + "\n" + text;
        final Frame frame = Controller.getCurrentController().getViewController().getFrame();
        setAcceleratorOnNextClickActionDialog = UITools.createCancelDialog(frame, title, text);
        getAcceleratorOnNextClickActionDialog().addComponentListener(new ComponentAdapter() {

            @Override
            public void componentHidden(final ComponentEvent e) {
                setAcceleratorOnNextClickActionDialog = null;
                acceleratorForNextClickedAction = null;
            }
        });
        getAcceleratorOnNextClickActionDialog().setVisible(true);
    }

    public AccelerateableAction(final MenuBuilder menuBuilder, final AFreeplaneAction originalAction) {
        super();
        this.menuBuilder = menuBuilder;
        this.originalAction = originalAction;
    }

    public void actionPerformed(final ActionEvent e) {
        final boolean newAcceleratorOnNextClickEnabled = AccelerateableAction.isNewAcceleratorOnNextClickEnabled();
        final KeyStroke newAccelerator = acceleratorForNextClickedAction;
        if (newAcceleratorOnNextClickEnabled) {
            getAcceleratorOnNextClickActionDialog().setVisible(false);
        }
        final Object source = e.getSource();
        if ((newAcceleratorOnNextClickEnabled || 0 != (e.getModifiers() & ActionEvent.CTRL_MASK)) && source instanceof IKeyBindingManager && !((IKeyBindingManager) source).isKeyBindingProcessed() && source instanceof JMenuItem) {
            final JMenuItem item = (JMenuItem) source;
            newAccelerator(item, newAccelerator);
            return;
        }
        originalAction.actionPerformed(e);
    }

    public static JDialog getAcceleratorOnNextClickActionDialog() {
        return setAcceleratorOnNextClickActionDialog;
    }

    public static KeyStroke getAcceleratorForNextClick() {
        return acceleratorForNextClickedAction;
    }

    public void addPropertyChangeListener(final PropertyChangeListener listener) {
        originalAction.addPropertyChangeListener(listener);
    }

    public void afterMapChange(final Object newMap) {
        originalAction.afterMapChange(newMap);
    }

    public Object getValue(final String key) {
        return originalAction.getValue(key);
    }

    public boolean isEnabled() {
        return originalAction.isEnabled();
    }

    public boolean isSelected() {
        return originalAction.isSelected();
    }

    public void setSelected(boolean newValue) {
        originalAction.setSelected(newValue);
    }

    public void newAccelerator(final JMenuItem editedItem, final KeyStroke newAccelerator) {
        final Object key = menuBuilder.getKeyByUserObject(editedItem);
        final String shortcutKey = menuBuilder.getShortcutKey(key.toString());
        final String oldShortcut = ResourceController.getResourceController().getProperty(shortcutKey);
        if (newAccelerator == null || !new KeystrokeValidator(editedItem, key, editedItem).isValid(newAccelerator, newAccelerator.getKeyChar())) {
            final GrabKeyDialog grabKeyDialog = new GrabKeyDialog(oldShortcut);
            final IKeystrokeValidator validator = new KeystrokeValidator(grabKeyDialog, key, editedItem);
            grabKeyDialog.setValidator(validator);
            grabKeyDialog.setVisible(true);
            if (grabKeyDialog.isOK()) {
                final String shortcut = grabKeyDialog.getShortcut();
                final KeyStroke accelerator = UITools.getKeyStroke(shortcut);
                menuBuilder.setAccelerator((Node) menuBuilder.get(key), accelerator);
                ResourceController.getResourceController().setProperty(shortcutKey, shortcut);
                LogUtils.info("created shortcut '" + shortcut + "' for menuitem '" + key + "', shortcutKey '" + shortcutKey + "' (" + editedItem.getText() + ")");
            }
        } else {
            if (oldShortcut != null) {
                final int replace = JOptionPane.showConfirmDialog(editedItem, oldShortcut, TextUtils.getText("remove_shortcut_question"), JOptionPane.YES_NO_OPTION);
                if (replace != JOptionPane.YES_OPTION) {
                    return;
                }
            }
            menuBuilder.setAccelerator((Node) menuBuilder.get(key), newAccelerator);
            ResourceController.getResourceController().setProperty(shortcutKey, toString(newAccelerator));
            LogUtils.info("created shortcut '" + toString(newAccelerator) + "' for menuitem '" + key + "', shortcutKey '" + shortcutKey + "' (" + editedItem.getText() + ")");
        }
    }

    private static String toString(final KeyStroke newAccelerator) {
        return newAccelerator.toString().replaceFirst("pressed ", "");
    }

    private static boolean askForReplaceShortcutViaDialog(String oldMenuItemTitle, Component parentComponent) {
        final int replace = JOptionPane.showConfirmDialog(parentComponent, TextUtils.format("replace_shortcut_question", oldMenuItemTitle), TextUtils.format("replace_shortcut_title"), JOptionPane.YES_NO_OPTION);
        return replace == JOptionPane.YES_OPTION;
    }

    public void putValue(final String key, final Object value) {
        originalAction.putValue(key, value);
    }

    public void removePropertyChangeListener(final PropertyChangeListener listener) {
        originalAction.removePropertyChangeListener(listener);
    }

    public void setEnabled(final boolean b) {
        originalAction.setEnabled(b);
    }

    public String getIconKey() {
        return originalAction.getIconKey();
    }
}


public static void main(String[] args) {
        launch(args);
    }

package demo.javafx_samples.src.Ensemble8.src.samples.java.ensemble.samples.charts.line.stock;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * A simulated stock line chart.
 *
 * @sampleName Stock Line Chart
 * @preview preview.png
 * @see javafx.scene.chart.LineChart
 * @see javafx.scene.chart.NumberAxis
 * @docUrl http://example.com Using JavaFX Charts Tutorial
 */
public class StockLineChartApp extends Application {

    private LineChart<Number, Number> chart;

    private XYChart.Series<Number, Number> hourDataSeries;

    private XYChart.Series<Number, Number> minuteDataSeries;

    private NumberAxis xAxis;

    private Timeline animation;

    private double hours = 0;

    private double minutes = 0;

    private double timeInHours = 0;

    private double prevY = 10;

    private double y = 10;

    public StockLineChartApp() {
        // create timeline to add new data every 60th of second
        animation = new Timeline();
        animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000 / 60), (ActionEvent actionEvent) -> {
            // 6 minutes data per frame
            for (int count = 0; count < 6; count++) {
                nextTime();
                plotTime();
            }
        }));
        animation.setCycleCount(Animation.INDEFINITE);
    }

    public Parent createContent() {
        xAxis = new NumberAxis(0, 24, 3);
        final NumberAxis yAxis = new NumberAxis(0, 100, 10);
        chart = new LineChart<>(xAxis, yAxis);
        // setup chart
        chart.getStylesheets().add(StockLineChartApp.class.getResource("StockLineChart.css").toExternalForm());
        chart.setCreateSymbols(false);
        chart.setAnimated(false);
        chart.setLegendVisible(false);
        chart.setTitle("ACME Company Stock");
        xAxis.setLabel("Time");
        xAxis.setForceZeroInRange(false);
        yAxis.setLabel("Share Price");
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "$", null));
        // add starting data
        hourDataSeries = new XYChart.Series<>();
        hourDataSeries.setName("Hourly Data");
        minuteDataSeries = new XYChart.Series<>();
        minuteDataSeries.setName("Minute Data");
        // create some starting data
        hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
        minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
        for (double m = 0; m < (60); m++) {
            nextTime();
            plotTime();
        }
        chart.getData().add(minuteDataSeries);
        chart.getData().add(hourDataSeries);
        return chart;
    }

    private void nextTime() {
        if (minutes == 59) {
            hours++;
            minutes = 0;
        } else {
            minutes++;
        }
        timeInHours = hours + ((1d / 60d) * minutes);
    }

    private void plotTime() {
        if ((timeInHours % 1) == 0) {
            // change of hour
            double oldY = y;
            y = prevY - 10 + (Math.random() * 20);
            prevY = oldY;
            while (y < 10 || y > 90) {
                y = y - 10 + (Math.random() * 20);
            }
            hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
            // after 25hours delete old data
            if (timeInHours > 25) {
                hourDataSeries.getData().remove(0);
            }
            // every hour after 24 move range 1 hour
            if (timeInHours > 24) {
                xAxis.setLowerBound(xAxis.getLowerBound() + 1);
                xAxis.setUpperBound(xAxis.getUpperBound() + 1);
            }
        }
        double min = (timeInHours % 1);
        double randomPickVariance = Math.random();
        if (randomPickVariance < 0.3) {
            double minY = prevY + ((y - prevY) * min) - 4 + (Math.random() * 8);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.7) {
            double minY = prevY + ((y - prevY) * min) - 6 + (Math.random() * 12);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.95) {
            double minY = prevY + ((y - prevY) * min) - 10 + (Math.random() * 20);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else {
            double minY = prevY + ((y - prevY) * min) - 15 + (Math.random() * 30);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        }
        // after 25hours delete old data
        if (timeInHours > 25) {
            minuteDataSeries.getData().remove(0);
        }
    }

    public void play() {
        animation.play();
    }

    @Override
    public void stop() {
        animation.pause();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(createContent()));
        primaryStage.show();
        play();
    }

    /**
     * Java main for when running without JavaFX launcher
     * @param args command line arguments
     */


for (double m = 0; m < (60); m++) {
            nextTime();
            plotTime();
        }

package demo.javafx_samples.src.Ensemble8.src.samples.java.ensemble.samples.charts.line.stock;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * A simulated stock line chart.
 *
 * @sampleName Stock Line Chart
 * @preview preview.png
 * @see javafx.scene.chart.LineChart
 * @see javafx.scene.chart.NumberAxis
 * @docUrl http://example.com Using JavaFX Charts Tutorial
 */
public class StockLineChartApp extends Application {

    private LineChart<Number, Number> chart;

    private XYChart.Series<Number, Number> hourDataSeries;

    private XYChart.Series<Number, Number> minuteDataSeries;

    private NumberAxis xAxis;

    private Timeline animation;

    private double hours = 0;

    private double minutes = 0;

    private double timeInHours = 0;

    private double prevY = 10;

    private double y = 10;

    public StockLineChartApp() {
        // create timeline to add new data every 60th of second
        animation = new Timeline();
        animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000 / 60), (ActionEvent actionEvent) -> {
            // 6 minutes data per frame
            for (int count = 0; count < 6; count++) {
                nextTime();
                plotTime();
            }
        }));
        animation.setCycleCount(Animation.INDEFINITE);
    }

    public Parent createContent() {
        xAxis = new NumberAxis(0, 24, 3);
        final NumberAxis yAxis = new NumberAxis(0, 100, 10);
        chart = new LineChart<>(xAxis, yAxis);
        // setup chart
        chart.getStylesheets().add(StockLineChartApp.class.getResource("StockLineChart.css").toExternalForm());
        chart.setCreateSymbols(false);
        chart.setAnimated(false);
        chart.setLegendVisible(false);
        chart.setTitle("ACME Company Stock");
        xAxis.setLabel("Time");
        xAxis.setForceZeroInRange(false);
        yAxis.setLabel("Share Price");
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "$", null));
        // add starting data
        hourDataSeries = new XYChart.Series<>();
        hourDataSeries.setName("Hourly Data");
        minuteDataSeries = new XYChart.Series<>();
        minuteDataSeries.setName("Minute Data");
        // create some starting data
        hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
        minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));

        chart.getData().add(minuteDataSeries);
        chart.getData().add(hourDataSeries);
        return chart;
    }

    private void nextTime() {
        if (minutes == 59) {
            hours++;
            minutes = 0;
        } else {
            minutes++;
        }
        timeInHours = hours + ((1d / 60d) * minutes);
    }

    private void plotTime() {
        if ((timeInHours % 1) == 0) {
            // change of hour
            double oldY = y;
            y = prevY - 10 + (Math.random() * 20);
            prevY = oldY;
            while (y < 10 || y > 90) {
                y = y - 10 + (Math.random() * 20);
            }
            hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
            // after 25hours delete old data
            if (timeInHours > 25) {
                hourDataSeries.getData().remove(0);
            }
            // every hour after 24 move range 1 hour
            if (timeInHours > 24) {
                xAxis.setLowerBound(xAxis.getLowerBound() + 1);
                xAxis.setUpperBound(xAxis.getUpperBound() + 1);
            }
        }
        double min = (timeInHours % 1);
        double randomPickVariance = Math.random();
        if (randomPickVariance < 0.3) {
            double minY = prevY + ((y - prevY) * min) - 4 + (Math.random() * 8);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.7) {
            double minY = prevY + ((y - prevY) * min) - 6 + (Math.random() * 12);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.95) {
            double minY = prevY + ((y - prevY) * min) - 10 + (Math.random() * 20);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else {
            double minY = prevY + ((y - prevY) * min) - 15 + (Math.random() * 30);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        }
        // after 25hours delete old data
        if (timeInHours > 25) {
            minuteDataSeries.getData().remove(0);
        }
    }

    public void play() {
        animation.play();
    }

    @Override
    public void stop() {
        animation.pause();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(createContent()));
        primaryStage.show();
        play();
    }

    /**
     * Java main for when running without JavaFX launcher
     * @param args command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}


package demo.javafx_samples.src.Ensemble8.src.samples.java.ensemble.samples.charts.line.stock;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * A simulated stock line chart.
 *
 * @sampleName Stock Line Chart
 * @preview preview.png
 * @see javafx.scene.chart.LineChart
 * @see javafx.scene.chart.NumberAxis
 * @docUrl http://example.com Using JavaFX Charts Tutorial
 */
public class StockLineChartApp extends Application {

    private LineChart<Number, Number> chart;

    private XYChart.Series<Number, Number> hourDataSeries;

    private XYChart.Series<Number, Number> minuteDataSeries;

    private NumberAxis xAxis;

    private Timeline animation;

    private double hours = 0;

    private double minutes = 0;

    private double timeInHours = 0;

    private double prevY = 10;

    private double y = 10;

    public StockLineChartApp() {
        // create timeline to add new data every 60th of second
        animation = new Timeline();
        animation.getKeyFrames().add(new KeyFrame(Duration.millis(1000 / 60), (ActionEvent actionEvent) -> {
            // 6 minutes data per frame
            for (int count = 0; count < 6; count++) {
                nextTime();
                plotTime();
            }
        }));
        animation.setCycleCount(Animation.INDEFINITE);
    }

    public Parent createContent() {
        xAxis = new NumberAxis(0, 24, 3);
        final NumberAxis yAxis = new NumberAxis(0, 100, 10);
        chart = new LineChart<>(xAxis, yAxis);
        // setup chart
        chart.getStylesheets().add(StockLineChartApp.class.getResource("StockLineChart.css").toExternalForm());
        chart.setCreateSymbols(false);
        chart.setAnimated(false);
        chart.setLegendVisible(false);
        chart.setTitle("ACME Company Stock");
        xAxis.setLabel("Time");
        xAxis.setForceZeroInRange(false);
        yAxis.setLabel("Share Price");
        yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "$", null));
        // add starting data
        hourDataSeries = new XYChart.Series<>();
        hourDataSeries.setName("Hourly Data");
        minuteDataSeries = new XYChart.Series<>();
        minuteDataSeries.setName("Minute Data");
        // create some starting data
        hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
        minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
        for (double m = 0; m < (60); m++) {
            nextTime();
            plotTime();
        }
        chart.getData().add(minuteDataSeries);
        chart.getData().add(hourDataSeries);
        return chart;
    }

    private void nextTime() {
        if (minutes == 59) {
            hours++;
            minutes = 0;
        } else {
            minutes++;
        }
        timeInHours = hours + ((1d / 60d) * minutes);
    }

    private void plotTime() {
        if ((timeInHours % 1) == 0) {
            // change of hour
            double oldY = y;
            y = prevY - 10 + (Math.random() * 20);
            prevY = oldY;

            hourDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, prevY));
            // after 25hours delete old data
            if (timeInHours > 25) {
                hourDataSeries.getData().remove(0);
            }
            // every hour after 24 move range 1 hour
            if (timeInHours > 24) {
                xAxis.setLowerBound(xAxis.getLowerBound() + 1);
                xAxis.setUpperBound(xAxis.getUpperBound() + 1);
            }
        }
        double min = (timeInHours % 1);
        double randomPickVariance = Math.random();
        if (randomPickVariance < 0.3) {
            double minY = prevY + ((y - prevY) * min) - 4 + (Math.random() * 8);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.7) {
            double minY = prevY + ((y - prevY) * min) - 6 + (Math.random() * 12);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else if (randomPickVariance < 0.95) {
            double minY = prevY + ((y - prevY) * min) - 10 + (Math.random() * 20);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        } else {
            double minY = prevY + ((y - prevY) * min) - 15 + (Math.random() * 30);
            minuteDataSeries.getData().add(new XYChart.Data<Number, Number>(timeInHours, minY));
        }
        // after 25hours delete old data
        if (timeInHours > 25) {
            minuteDataSeries.getData().remove(0);
        }
    }

    public void play() {
        animation.play();
    }

    @Override
    public void stop() {
        animation.pause();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setScene(new Scene(createContent()));
        primaryStage.show();
        play();
    }

    /**
     * Java main for when running without JavaFX launcher
     * @param args command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}


public void reconnect() {
        reset();
        connect();
    }

package org.java_websocket.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.java_websocket.AbstractWebSocket;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.enums.Opcode;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.protocols.IProtocol;

/**
 * A subclass must implement at least <var>onOpen</var>, <var>onClose</var>, and
 * <var>onMessage</var> to be useful. At runtime the user is expected to establish a connection via
 * {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded
 * methods and to {@link #send(String)} data to the server.
 */
public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket {

    /**
     * The URI this channel is supposed to connect to.
     */
    protected URI uri = null;

    /**
     * The underlying engine
     */
    private WebSocketImpl engine = null;

    private Socket socket = null;

    private SocketFactory socketFactory = null;

    /**
     * The used OutputStream
     */
    private OutputStream ostream;

    /**
     * The used proxy, if any
     */
    private Proxy proxy = Proxy.NO_PROXY;

    /**
     * The thread to write outgoing message
     */
    private Thread writeThread;

    /**
     * The thread to connect and read message
     */
    private Thread connectReadThread;

    /**
     * The draft to use
     */
    private Draft draft;

    /**
     * The additional headers to use
     */
    private Map<String, String> headers;

    private CountDownLatch connectLatch = new CountDownLatch(1);

    private CountDownLatch closeLatch = new CountDownLatch(1);

    /**
     * The socket timeout value to be used in milliseconds.
     */
    private int connectTimeout = 0;

    /**
     * DNS resolver that translates a URI to an InetAddress
     *
     * @see InetAddress
     * @since 1.4.1
     */
    private DnsResolver dnsResolver = null;

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri the server URI to connect to
     */
    public WebSocketClient(URI serverUri) {
        this(serverUri, new Draft_6455());
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri     the server URI to connect to
     * @param protocolDraft The draft which should be used for this connection
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft) {
        this(serverUri, protocolDraft, null, 0);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri   the server URI to connect to
     * @param httpHeaders Additional HTTP-Headers
     * @since 1.3.8
     */
    public WebSocketClient(URI serverUri, Map<String, String> httpHeaders) {
        this(serverUri, new Draft_6455(), httpHeaders);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri     the server URI to connect to
     * @param protocolDraft The draft which should be used for this connection
     * @param httpHeaders   Additional HTTP-Headers
     * @since 1.3.8
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft, Map<String, String> httpHeaders) {
        this(serverUri, protocolDraft, httpHeaders, 0);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri      the server URI to connect to
     * @param protocolDraft  The draft which should be used for this connection
     * @param httpHeaders    Additional HTTP-Headers
     * @param connectTimeout The Timeout for the connection
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft, Map<String, String> httpHeaders, int connectTimeout) {
        if (serverUri == null) {
            throw new IllegalArgumentException();
        } else if (protocolDraft == null) {
            throw new IllegalArgumentException("null as draft is permitted for `WebSocketServer` only!");
        }
        this.uri = serverUri;
        this.draft = protocolDraft;
        this.dnsResolver = new DnsResolver() {

            @Override
            public InetAddress resolve(URI uri) throws UnknownHostException {
                return InetAddress.getByName(uri.getHost());
            }
        };
        if (httpHeaders != null) {
            headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            headers.putAll(httpHeaders);
        }
        this.connectTimeout = connectTimeout;
        setTcpNoDelay(false);
        setReuseAddr(false);
        this.engine = new WebSocketImpl(this, protocolDraft);
    }

    /**
     * Returns the URI that this WebSocketClient is connected to.
     *
     * @return the URI connected to
     */
    public URI getURI() {
        return uri;
    }

    /**
     * Returns the protocol version this channel uses.
 For more infos see
     * http://example.com
     *
     * @return The draft used for this client
     */
    public Draft getDraft() {
        return draft;
    }

    /**
     * Returns the socket to allow Hostname Verification
     *
     * @return the socket used for this connection
     */
    public Socket getSocket() {
        return socket;
    }

    /**
     * @param key   Name of the header to add.
     * @param value Value of the header to add.
     * @since 1.4.1 Adds an additional header to be sent in the handshake.
 If the connection is
     * already made, adding headers has no effect, unless reconnect is called, which then a new
     * handshake is sent.
 If a header with the same key already exists, it is overridden.
     */
    public void addHeader(String key, String value) {
        if (headers == null) {
            headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        }
        headers.put(key, value);
    }

    /**
     * @param key Name of the header to remove.
     * @return the previous value associated with key, or null if there was no mapping for key.
     * @since 1.4.1 Removes a header from the handshake to be sent, if header key exists.

     */
    public String removeHeader(String key) {
        if (headers == null) {
            return null;
        }
        return headers.remove(key);
    }

    /**
     * @since 1.4.1 Clears all previously put headers.
     */
    public void clearHeaders() {
        headers = null;
    }

    /**
     * Sets a custom DNS resolver.
     *
     * @param dnsResolver The DnsResolver to use.
     * @since 1.4.1
     */
    public void setDnsResolver(DnsResolver dnsResolver) {
        this.dnsResolver = dnsResolver;
    }

    /**
     * Reinitiates the websocket connection. This method does not block.
     *
     * @since 1.3.8
     */

/**
     * Same as reconnect but blocks until the websocket reconnected or failed to do
     * so.

     *
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     * @since 1.3.8
     */
    public boolean reconnectBlocking() throws InterruptedException {
        reset();
        return connectBlocking();
    }

    /**
     * Reset everything relevant to allow a reconnect
     *
     * @since 1.3.8
     */
    private void reset() {
        Thread current = Thread.currentThread();
        if (current == writeThread || current == connectReadThread) {
            throw new IllegalStateException("You cannot initialize a reconnect out of the websocket thread. Use reconnect in another thread to ensure a successful cleanup.");
        }
        try {
            closeBlocking();
            if (writeThread != null) {
                this.writeThread.interrupt();
                this.writeThread = null;
            }
            if (connectReadThread != null) {
                this.connectReadThread.interrupt();
                this.connectReadThread = null;
            }
            this.draft.reset();
            if (this.socket != null) {
                this.socket.close();
                this.socket = null;
            }
        } catch (Exception e) {
            onError(e);
            engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
            return;
        }
        connectLatch = new CountDownLatch(1);
        closeLatch = new CountDownLatch(1);
        this.engine = new WebSocketImpl(this, this.draft);
    }

    /**
     * Initiates the websocket connection. This method does not block.
     */
    public void connect() {
        if (connectReadThread != null) {
            throw new IllegalStateException("WebSocketClient objects are not reuseable");
        }
        connectReadThread = new Thread(this);
        connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
        connectReadThread.start();
    }

    /**
     * Same as connect but blocks until the websocket connected or failed to do so.

     *
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public boolean connectBlocking() throws InterruptedException {
        connect();
        connectLatch.await();
        return engine.isOpen();
    }

    /**
     * Same as connect but blocks with a timeout until the websocket connected or failed
     * to do so.

     *
     * @param timeout  The connect timeout
     * @param timeUnit The timeout time unit
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public boolean connectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException {
        connect();
        return connectLatch.await(timeout, timeUnit) && engine.isOpen();
    }

    /**
     * Initiates the websocket close handshake. This method does not block
 In oder to make sure
     * the connection is closed use closeBlocking
     */
    public void close() {
        if (writeThread != null) {
            engine.close(CloseFrame.NORMAL);
        }
    }

    /**
     * Same as close but blocks until the websocket closed or failed to do so.

     *
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public void closeBlocking() throws InterruptedException {
        close();
        closeLatch.await();
    }

    /**
     * Sends <var>text</var> to the connected websocket server.
     *
     * @param text The string which will be transmitted.
     */
    public void send(String text) {
        engine.send(text);
    }

    /**
     * Sends binary <var> data</var> to the connected webSocket server.
     *
     * @param data The byte-Array of data to send to the WebSocket server.
     */
    public void send(byte[] data) {
        engine.send(data);
    }

    @Override
    public <T> T getAttachment() {
        return engine.getAttachment();
    }

    @Override
    public <T> void setAttachment(T attachment) {
        engine.setAttachment(attachment);
    }

    @Override
    protected Collection<WebSocket> getConnections() {
        return Collections.singletonList((WebSocket) engine);
    }

    @Override
    public void sendPing() {
        engine.sendPing();
    }

    public void run() {
        InputStream istream;
        try {
            boolean upgradeSocketToSSLSocket = prepareSocket();
            socket.setTcpNoDelay(isTcpNoDelay());
            socket.setReuseAddress(isReuseAddr());
            if (!socket.isConnected()) {
                InetSocketAddress addr = dnsResolver == null ? InetSocketAddress.createUnresolved(uri.getHost(), getPort()) : new InetSocketAddress(dnsResolver.resolve(uri), this.getPort());
                socket.connect(addr, connectTimeout);
            }
            // if the socket is set by others we don't apply any TLS wrapper
            if (upgradeSocketToSSLSocket && "wss".equals(uri.getScheme())) {
                upgradeSocketToSSL();
            }
            if (socket instanceof SSLSocket) {
                SSLSocket sslSocket = (SSLSocket) socket;
                SSLParameters sslParameters = sslSocket.getSSLParameters();
                onSetSSLParameters(sslParameters);
                sslSocket.setSSLParameters(sslParameters);
            }
            istream = socket.getInputStream();
            ostream = socket.getOutputStream();
            sendHandshake();
        } catch (/*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */
        Exception e) {
            onWebsocketError(engine, e);
            engine.closeConnection(CloseFrame.NEVER_CONNECTED, e.getMessage());
            return;
        } catch (InternalError e) {
            if (e.getCause() instanceof InvocationTargetException && e.getCause().getCause() instanceof IOException) {
                IOException cause = (IOException) e.getCause().getCause();
                onWebsocketError(engine, cause);
                engine.closeConnection(CloseFrame.NEVER_CONNECTED, cause.getMessage());
                return;
            }
            throw e;
        }
        writeThread = new Thread(new WebsocketWriteThread(this));
        writeThread.start();
        byte[] rawbuffer = new byte[WebSocketImpl.RCVBUF];
        int readBytes;
        try {
            while (!isClosing() && !isClosed() && (readBytes = istream.read(rawbuffer)) != -1) {
                engine.decode(ByteBuffer.wrap(rawbuffer, 0, readBytes));
            }
            engine.eot();
        } catch (IOException e) {
            handleIOException(e);
        } catch (RuntimeException e) {
            // this catch case covers internal errors only and indicates a bug in this websocket implementation
            onError(e);
            engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
        }
        connectReadThread = null;
    }

    private void upgradeSocketToSSL() throws NoSuchAlgorithmException, KeyManagementException, IOException {
        SSLSocketFactory factory;
        // Prioritise the provided socketfactory
        // Helps when using web debuggers like Fiddler Classic
        if (socketFactory instanceof SSLSocketFactory) {
            factory = (SSLSocketFactory) socketFactory;
        } else {
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            factory = sslContext.getSocketFactory();
        }
        socket = factory.createSocket(socket, uri.getHost(), getPort(), true);
    }

    private boolean prepareSocket() throws IOException {
        boolean upgradeSocketToSSLSocket = false;
        // Prioritise a proxy over a socket factory and apply the socketfactory later
        if (proxy != Proxy.NO_PROXY) {
            socket = new Socket(proxy);
            upgradeSocketToSSLSocket = true;
        } else if (socketFactory != null) {
            socket = socketFactory.createSocket();
        } else if (socket == null) {
            socket = new Socket(proxy);
            upgradeSocketToSSLSocket = true;
        } else if (socket.isClosed()) {
            throw new IOException();
        }
        return upgradeSocketToSSLSocket;
    }

    /**
     * Apply specific SSLParameters If you override this method make sure to always call
     * super.onSetSSLParameters() to ensure the hostname validation is active
     *
     * @param sslParameters the SSLParameters which will be used for the SSLSocket
     */
    protected void onSetSSLParameters(SSLParameters sslParameters) {
        // If you run into problem on Android (NoSuchMethodException), check out the wiki http://example.com
        // Perform hostname validation
        sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
    }

    /**
     * Extract the specified port
     *
     * @return the specified port or the default port for the specific scheme
     */
    private int getPort() {
        int port = uri.getPort();
        String scheme = uri.getScheme();
        if ("wss".equals(scheme)) {
            return port == -1 ? WebSocketImpl.DEFAULT_WSS_PORT : port;
        } else if ("ws".equals(scheme)) {
            return port == -1 ? WebSocketImpl.DEFAULT_PORT : port;
        } else {
            throw new IllegalArgumentException("unknown scheme: " + scheme);
        }
    }

    /**
     * Create and send the handshake to the other endpoint
     *
     * @throws InvalidHandshakeException a invalid handshake was created
     */
    private void sendHandshake() throws InvalidHandshakeException {
        String path;
        String part1 = uri.getRawPath();
        String part2 = uri.getRawQuery();
        if (part1 == null || part1.length() == 0) {
            path = "/";
        } else {
            path = part1;
        }
        if (part2 != null) {
            path += '?' + part2;
        }
        int port = getPort();
        String host = uri.getHost() + ((port != WebSocketImpl.DEFAULT_PORT && port != WebSocketImpl.DEFAULT_WSS_PORT) ? ":" + port : "");
        HandshakeImpl1Client handshake = new HandshakeImpl1Client();
        handshake.setResourceDescriptor(path);
        handshake.put("Host", host);
        if (headers != null) {
            for (Map.Entry<String, String> kv : headers.entrySet()) {
                handshake.put(kv.getKey(), kv.getValue());
            }
        }
        engine.startHandshake(handshake);
    }

    /**
     * This represents the state of the connection.
     */
    public ReadyState getReadyState() {
        return engine.getReadyState();
    }

    @Override
    public final void onWebsocketMessage(WebSocket conn, String message) {
        onMessage(message);
    }

    @Override
    public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) {
        onMessage(blob);
    }

    @Override
    public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
        startConnectionLostTimer();
        onOpen((ServerHandshake) handshake);
        connectLatch.countDown();
    }

    @Override
    public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) {
        stopConnectionLostTimer();
        if (writeThread != null) {
            writeThread.interrupt();
        }
        onClose(code, reason, remote);
        connectLatch.countDown();
        closeLatch.countDown();
    }

    @Override
    public final void onWebsocketError(WebSocket conn, Exception ex) {
        onError(ex);
    }

    @Override
    public final void onWriteDemand(WebSocket conn) {
        // nothing to do
    }

    @Override
    public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) {
        onCloseInitiated(code, reason);
    }

    @Override
    public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) {
        onClosing(code, reason, remote);
    }

    /**
     * Send when this peer sends a close handshake
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     */
    public void onCloseInitiated(int code, String reason) {
        // To overwrite
    }

    /**
     * Called as soon as no further frames are accepted
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     * @param remote Returns whether or not the closing of the connection was initiated by the remote
     *               host.
     */
    public void onClosing(int code, String reason, boolean remote) {
        // To overwrite
    }

    /**
     * Getter for the engine
     *
     * @return the engine
     */
    public WebSocket getConnection() {
        return engine;
    }

    @Override
    public InetSocketAddress getLocalSocketAddress(WebSocket conn) {
        if (socket != null) {
            return (InetSocketAddress) socket.getLocalSocketAddress();
        }
        return null;
    }

    @Override
    public InetSocketAddress getRemoteSocketAddress(WebSocket conn) {
        if (socket != null) {
            return (InetSocketAddress) socket.getRemoteSocketAddress();
        }
        return null;
    }

    /**
     * Called after an opening handshake has been performed and the given websocket is ready to be
     * written on.
     *
     * @param handshakedata The handshake of the websocket instance
     */
    public abstract void onOpen(ServerHandshake handshakedata);

    /**
     * Callback for string messages received from the remote host
     *
     * @param message The UTF-8 decoded message that was received.
     * @see #onMessage(ByteBuffer)
     */
    public abstract void onMessage(String message);

    /**
     * Called after the websocket connection has been closed.
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     * @param remote Returns whether or not the closing of the connection was initiated by the remote
     *               host.
     */
    public abstract void onClose(int code, String reason, boolean remote);

    /**
     * Called when errors occurs. If an error causes the websocket connection to fail {@link
     * #onClose(int, String, boolean)} will be called additionally.
 This method will be called
     * primarily because of IO or protocol errors.
 If the given exception is an RuntimeException
     * that probably means that you encountered a bug.

     *
     * @param ex The exception causing this error
     */
    public abstract void onError(Exception ex);

    /**
     * Callback for binary messages received from the remote host
     *
     * @param bytes The binary message that was received.
     * @see #onMessage(String)
     */
    public void onMessage(ByteBuffer bytes) {
        // To overwrite
    }

    private class WebsocketWriteThread implements Runnable {

        private final WebSocketClient webSocketClient;

        WebsocketWriteThread(WebSocketClient webSocketClient) {
            this.webSocketClient = webSocketClient;
        }

        @Override
        public void run() {
            Thread.currentThread().setName("WebSocketWriteThread-" + Thread.currentThread().getId());
            try {
                runWriteData();
            } catch (IOException e) {
                handleIOException(e);
            } finally {
                closeSocket();
                writeThread = null;
            }
        }

        /**
         * Write the data into the outstream
         *
         * @throws IOException if write or flush did not work
         */
        private void runWriteData() throws IOException {
            try {
                while (!Thread.interrupted()) {
                    ByteBuffer buffer = engine.outQueue.take();
                    ostream.write(buffer.array(), 0, buffer.limit());
                    ostream.flush();
                }
            } catch (InterruptedException e) {
                for (ByteBuffer buffer : engine.outQueue) {
                    ostream.write(buffer.array(), 0, buffer.limit());
                    ostream.flush();
                }
                Thread.currentThread().interrupt();
            }
        }

        /**
         * Closing the socket
         */
        private void closeSocket() {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ex) {
                onWebsocketError(webSocketClient, ex);
            }
        }
    }

    /**
     * Method to set a proxy for this connection
     *
     * @param proxy the proxy to use for this websocket client
     */
    public void setProxy(Proxy proxy) {
        if (proxy == null) {
            throw new IllegalArgumentException();
        }
        this.proxy = proxy;
    }

    /**
     * Accepts bound and unbound sockets.
 This method must be called before connect.
     * If the given socket is not yet bound it will be bound to the uri specified in the constructor.
     *
     * @param socket The socket which should be used for the connection
     * @deprecated use setSocketFactory
     */
    @Deprecated
    public void setSocket(Socket socket) {
        if (this.socket != null) {
            throw new IllegalStateException("socket has already been set");
        }
        this.socket = socket;
    }

    /**
     * Accepts a SocketFactory.
 This method must be called before connect. The socket
     * will be bound to the uri specified in the constructor.
     *
     * @param socketFactory The socket factory which should be used for the connection.
     */
    public void setSocketFactory(SocketFactory socketFactory) {
        this.socketFactory = socketFactory;
    }

    @Override
    public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
        engine.sendFragmentedFrame(op, buffer, fin);
    }

    @Override
    public boolean isOpen() {
        return engine.isOpen();
    }

    @Override
    public boolean isFlushAndClose() {
        return engine.isFlushAndClose();
    }

    @Override
    public boolean isClosed() {
        return engine.isClosed();
    }

    @Override
    public boolean isClosing() {
        return engine.isClosing();
    }

    @Override
    public boolean hasBufferedData() {
        return engine.hasBufferedData();
    }

    @Override
    public void close(int code) {
        engine.close(code);
    }

    @Override
    public void close(int code, String message) {
        engine.close(code, message);
    }

    @Override
    public void closeConnection(int code, String message) {
        engine.closeConnection(code, message);
    }

    @Override
    public void send(ByteBuffer bytes) {
        engine.send(bytes);
    }

    @Override
    public void sendFrame(Framedata framedata) {
        engine.sendFrame(framedata);
    }

    @Override
    public void sendFrame(Collection<Framedata> frames) {
        engine.sendFrame(frames);
    }

    @Override
    public InetSocketAddress getLocalSocketAddress() {
        return engine.getLocalSocketAddress();
    }

    @Override
    public InetSocketAddress getRemoteSocketAddress() {
        return engine.getRemoteSocketAddress();
    }

    @Override
    public String getResourceDescriptor() {
        return uri.getPath();
    }

    @Override
    public boolean hasSSLSupport() {
        return socket instanceof SSLSocket;
    }

    @Override
    public SSLSession getSSLSession() {
        if (!hasSSLSupport()) {
            throw new IllegalArgumentException("This websocket uses ws instead of wss. No SSLSession available.");
        }
        return ((SSLSocket) socket).getSession();
    }

    @Override
    public IProtocol getProtocol() {
        return engine.getProtocol();
    }

    /**
     * Method to give some additional info for specific IOExceptions
     *
     * @param e the IOException causing a eot.
     */
    private void handleIOException(IOException e) {
        if (e instanceof SSLException) {
            onError(e);
        }
        engine.eot();
    }
}


while (!Thread.interrupted()) {
                    ByteBuffer buffer = engine.outQueue.take();
                    ostream.write(buffer.array(), 0, buffer.limit());
                    ostream.flush();
                }

package org.java_websocket.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.java_websocket.AbstractWebSocket;
import org.java_websocket.WebSocket;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.enums.Opcode;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.Framedata;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.protocols.IProtocol;

/**
 * A subclass must implement at least <var>onOpen</var>, <var>onClose</var>, and
 * <var>onMessage</var> to be useful. At runtime the user is expected to establish a connection via
 * {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded
 * methods and to {@link #send(String)} data to the server.
 */
public abstract class WebSocketClient extends AbstractWebSocket implements Runnable, WebSocket {

    /**
     * The URI this channel is supposed to connect to.
     */
    protected URI uri = null;

    /**
     * The underlying engine
     */
    private WebSocketImpl engine = null;

    private Socket socket = null;

    private SocketFactory socketFactory = null;

    /**
     * The used OutputStream
     */
    private OutputStream ostream;

    /**
     * The used proxy, if any
     */
    private Proxy proxy = Proxy.NO_PROXY;

    /**
     * The thread to write outgoing message
     */
    private Thread writeThread;

    /**
     * The thread to connect and read message
     */
    private Thread connectReadThread;

    /**
     * The draft to use
     */
    private Draft draft;

    /**
     * The additional headers to use
     */
    private Map<String, String> headers;

    private CountDownLatch connectLatch = new CountDownLatch(1);

    private CountDownLatch closeLatch = new CountDownLatch(1);

    /**
     * The socket timeout value to be used in milliseconds.
     */
    private int connectTimeout = 0;

    /**
     * DNS resolver that translates a URI to an InetAddress
     *
     * @see InetAddress
     * @since 1.4.1
     */
    private DnsResolver dnsResolver = null;

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri the server URI to connect to
     */
    public WebSocketClient(URI serverUri) {
        this(serverUri, new Draft_6455());
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri     the server URI to connect to
     * @param protocolDraft The draft which should be used for this connection
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft) {
        this(serverUri, protocolDraft, null, 0);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri   the server URI to connect to
     * @param httpHeaders Additional HTTP-Headers
     * @since 1.3.8
     */
    public WebSocketClient(URI serverUri, Map<String, String> httpHeaders) {
        this(serverUri, new Draft_6455(), httpHeaders);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri     the server URI to connect to
     * @param protocolDraft The draft which should be used for this connection
     * @param httpHeaders   Additional HTTP-Headers
     * @since 1.3.8
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft, Map<String, String> httpHeaders) {
        this(serverUri, protocolDraft, httpHeaders, 0);
    }

    /**
     * Constructs a WebSocketClient instance and sets it to the connect to the specified URI. The
     * channel does not attampt to connect automatically. The connection will be established once you
     * call <var>connect</var>.
     *
     * @param serverUri      the server URI to connect to
     * @param protocolDraft  The draft which should be used for this connection
     * @param httpHeaders    Additional HTTP-Headers
     * @param connectTimeout The Timeout for the connection
     */
    public WebSocketClient(URI serverUri, Draft protocolDraft, Map<String, String> httpHeaders, int connectTimeout) {
        if (serverUri == null) {
            throw new IllegalArgumentException();
        } else if (protocolDraft == null) {
            throw new IllegalArgumentException("null as draft is permitted for `WebSocketServer` only!");
        }
        this.uri = serverUri;
        this.draft = protocolDraft;
        this.dnsResolver = new DnsResolver() {

            @Override
            public InetAddress resolve(URI uri) throws UnknownHostException {
                return InetAddress.getByName(uri.getHost());
            }
        };
        if (httpHeaders != null) {
            headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            headers.putAll(httpHeaders);
        }
        this.connectTimeout = connectTimeout;
        setTcpNoDelay(false);
        setReuseAddr(false);
        this.engine = new WebSocketImpl(this, protocolDraft);
    }

    /**
     * Returns the URI that this WebSocketClient is connected to.
     *
     * @return the URI connected to
     */
    public URI getURI() {
        return uri;
    }

    /**
     * Returns the protocol version this channel uses.
 For more infos see
     * http://example.com
     *
     * @return The draft used for this client
     */
    public Draft getDraft() {
        return draft;
    }

    /**
     * Returns the socket to allow Hostname Verification
     *
     * @return the socket used for this connection
     */
    public Socket getSocket() {
        return socket;
    }

    /**
     * @param key   Name of the header to add.
     * @param value Value of the header to add.
     * @since 1.4.1 Adds an additional header to be sent in the handshake.
 If the connection is
     * already made, adding headers has no effect, unless reconnect is called, which then a new
     * handshake is sent.
 If a header with the same key already exists, it is overridden.
     */
    public void addHeader(String key, String value) {
        if (headers == null) {
            headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
        }
        headers.put(key, value);
    }

    /**
     * @param key Name of the header to remove.
     * @return the previous value associated with key, or null if there was no mapping for key.
     * @since 1.4.1 Removes a header from the handshake to be sent, if header key exists.

     */
    public String removeHeader(String key) {
        if (headers == null) {
            return null;
        }
        return headers.remove(key);
    }

    /**
     * @since 1.4.1 Clears all previously put headers.
     */
    public void clearHeaders() {
        headers = null;
    }

    /**
     * Sets a custom DNS resolver.
     *
     * @param dnsResolver The DnsResolver to use.
     * @since 1.4.1
     */
    public void setDnsResolver(DnsResolver dnsResolver) {
        this.dnsResolver = dnsResolver;
    }

    /**
     * Reinitiates the websocket connection. This method does not block.
     *
     * @since 1.3.8
     */
    public void reconnect() {
        reset();
        connect();
    }

    /**
     * Same as reconnect but blocks until the websocket reconnected or failed to do
     * so.

     *
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     * @since 1.3.8
     */
    public boolean reconnectBlocking() throws InterruptedException {
        reset();
        return connectBlocking();
    }

    /**
     * Reset everything relevant to allow a reconnect
     *
     * @since 1.3.8
     */
    private void reset() {
        Thread current = Thread.currentThread();
        if (current == writeThread || current == connectReadThread) {
            throw new IllegalStateException("You cannot initialize a reconnect out of the websocket thread. Use reconnect in another thread to ensure a successful cleanup.");
        }
        try {
            closeBlocking();
            if (writeThread != null) {
                this.writeThread.interrupt();
                this.writeThread = null;
            }
            if (connectReadThread != null) {
                this.connectReadThread.interrupt();
                this.connectReadThread = null;
            }
            this.draft.reset();
            if (this.socket != null) {
                this.socket.close();
                this.socket = null;
            }
        } catch (Exception e) {
            onError(e);
            engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
            return;
        }
        connectLatch = new CountDownLatch(1);
        closeLatch = new CountDownLatch(1);
        this.engine = new WebSocketImpl(this, this.draft);
    }

    /**
     * Initiates the websocket connection. This method does not block.
     */
    public void connect() {
        if (connectReadThread != null) {
            throw new IllegalStateException("WebSocketClient objects are not reuseable");
        }
        connectReadThread = new Thread(this);
        connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
        connectReadThread.start();
    }

    /**
     * Same as connect but blocks until the websocket connected or failed to do so.

     *
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public boolean connectBlocking() throws InterruptedException {
        connect();
        connectLatch.await();
        return engine.isOpen();
    }

    /**
     * Same as connect but blocks with a timeout until the websocket connected or failed
     * to do so.

     *
     * @param timeout  The connect timeout
     * @param timeUnit The timeout time unit
     * @return Returns whether it succeeded or not.
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public boolean connectBlocking(long timeout, TimeUnit timeUnit) throws InterruptedException {
        connect();
        return connectLatch.await(timeout, timeUnit) && engine.isOpen();
    }

    /**
     * Initiates the websocket close handshake. This method does not block
 In oder to make sure
     * the connection is closed use closeBlocking
     */
    public void close() {
        if (writeThread != null) {
            engine.close(CloseFrame.NORMAL);
        }
    }

    /**
     * Same as close but blocks until the websocket closed or failed to do so.

     *
     * @throws InterruptedException Thrown when the threads get interrupted
     */
    public void closeBlocking() throws InterruptedException {
        close();
        closeLatch.await();
    }

    /**
     * Sends <var>text</var> to the connected websocket server.
     *
     * @param text The string which will be transmitted.
     */
    public void send(String text) {
        engine.send(text);
    }

    /**
     * Sends binary <var> data</var> to the connected webSocket server.
     *
     * @param data The byte-Array of data to send to the WebSocket server.
     */
    public void send(byte[] data) {
        engine.send(data);
    }

    @Override
    public <T> T getAttachment() {
        return engine.getAttachment();
    }

    @Override
    public <T> void setAttachment(T attachment) {
        engine.setAttachment(attachment);
    }

    @Override
    protected Collection<WebSocket> getConnections() {
        return Collections.singletonList((WebSocket) engine);
    }

    @Override
    public void sendPing() {
        engine.sendPing();
    }

    public void run() {
        InputStream istream;
        try {
            boolean upgradeSocketToSSLSocket = prepareSocket();
            socket.setTcpNoDelay(isTcpNoDelay());
            socket.setReuseAddress(isReuseAddr());
            if (!socket.isConnected()) {
                InetSocketAddress addr = dnsResolver == null ? InetSocketAddress.createUnresolved(uri.getHost(), getPort()) : new InetSocketAddress(dnsResolver.resolve(uri), this.getPort());
                socket.connect(addr, connectTimeout);
            }
            // if the socket is set by others we don't apply any TLS wrapper
            if (upgradeSocketToSSLSocket && "wss".equals(uri.getScheme())) {
                upgradeSocketToSSL();
            }
            if (socket instanceof SSLSocket) {
                SSLSocket sslSocket = (SSLSocket) socket;
                SSLParameters sslParameters = sslSocket.getSSLParameters();
                onSetSSLParameters(sslParameters);
                sslSocket.setSSLParameters(sslParameters);
            }
            istream = socket.getInputStream();
            ostream = socket.getOutputStream();
            sendHandshake();
        } catch (/*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */
        Exception e) {
            onWebsocketError(engine, e);
            engine.closeConnection(CloseFrame.NEVER_CONNECTED, e.getMessage());
            return;
        } catch (InternalError e) {
            if (e.getCause() instanceof InvocationTargetException && e.getCause().getCause() instanceof IOException) {
                IOException cause = (IOException) e.getCause().getCause();
                onWebsocketError(engine, cause);
                engine.closeConnection(CloseFrame.NEVER_CONNECTED, cause.getMessage());
                return;
            }
            throw e;
        }
        writeThread = new Thread(new WebsocketWriteThread(this));
        writeThread.start();
        byte[] rawbuffer = new byte[WebSocketImpl.RCVBUF];
        int readBytes;
        try {
            while (!isClosing() && !isClosed() && (readBytes = istream.read(rawbuffer)) != -1) {
                engine.decode(ByteBuffer.wrap(rawbuffer, 0, readBytes));
            }
            engine.eot();
        } catch (IOException e) {
            handleIOException(e);
        } catch (RuntimeException e) {
            // this catch case covers internal errors only and indicates a bug in this websocket implementation
            onError(e);
            engine.closeConnection(CloseFrame.ABNORMAL_CLOSE, e.getMessage());
        }
        connectReadThread = null;
    }

    private void upgradeSocketToSSL() throws NoSuchAlgorithmException, KeyManagementException, IOException {
        SSLSocketFactory factory;
        // Prioritise the provided socketfactory
        // Helps when using web debuggers like Fiddler Classic
        if (socketFactory instanceof SSLSocketFactory) {
            factory = (SSLSocketFactory) socketFactory;
        } else {
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            factory = sslContext.getSocketFactory();
        }
        socket = factory.createSocket(socket, uri.getHost(), getPort(), true);
    }

    private boolean prepareSocket() throws IOException {
        boolean upgradeSocketToSSLSocket = false;
        // Prioritise a proxy over a socket factory and apply the socketfactory later
        if (proxy != Proxy.NO_PROXY) {
            socket = new Socket(proxy);
            upgradeSocketToSSLSocket = true;
        } else if (socketFactory != null) {
            socket = socketFactory.createSocket();
        } else if (socket == null) {
            socket = new Socket(proxy);
            upgradeSocketToSSLSocket = true;
        } else if (socket.isClosed()) {
            throw new IOException();
        }
        return upgradeSocketToSSLSocket;
    }

    /**
     * Apply specific SSLParameters If you override this method make sure to always call
     * super.onSetSSLParameters() to ensure the hostname validation is active
     *
     * @param sslParameters the SSLParameters which will be used for the SSLSocket
     */
    protected void onSetSSLParameters(SSLParameters sslParameters) {
        // If you run into problem on Android (NoSuchMethodException), check out the wiki http://example.com
        // Perform hostname validation
        sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
    }

    /**
     * Extract the specified port
     *
     * @return the specified port or the default port for the specific scheme
     */
    private int getPort() {
        int port = uri.getPort();
        String scheme = uri.getScheme();
        if ("wss".equals(scheme)) {
            return port == -1 ? WebSocketImpl.DEFAULT_WSS_PORT : port;
        } else if ("ws".equals(scheme)) {
            return port == -1 ? WebSocketImpl.DEFAULT_PORT : port;
        } else {
            throw new IllegalArgumentException("unknown scheme: " + scheme);
        }
    }

    /**
     * Create and send the handshake to the other endpoint
     *
     * @throws InvalidHandshakeException a invalid handshake was created
     */
    private void sendHandshake() throws InvalidHandshakeException {
        String path;
        String part1 = uri.getRawPath();
        String part2 = uri.getRawQuery();
        if (part1 == null || part1.length() == 0) {
            path = "/";
        } else {
            path = part1;
        }
        if (part2 != null) {
            path += '?' + part2;
        }
        int port = getPort();
        String host = uri.getHost() + ((port != WebSocketImpl.DEFAULT_PORT && port != WebSocketImpl.DEFAULT_WSS_PORT) ? ":" + port : "");
        HandshakeImpl1Client handshake = new HandshakeImpl1Client();
        handshake.setResourceDescriptor(path);
        handshake.put("Host", host);
        if (headers != null) {
            for (Map.Entry<String, String> kv : headers.entrySet()) {
                handshake.put(kv.getKey(), kv.getValue());
            }
        }
        engine.startHandshake(handshake);
    }

    /**
     * This represents the state of the connection.
     */
    public ReadyState getReadyState() {
        return engine.getReadyState();
    }

    @Override
    public final void onWebsocketMessage(WebSocket conn, String message) {
        onMessage(message);
    }

    @Override
    public final void onWebsocketMessage(WebSocket conn, ByteBuffer blob) {
        onMessage(blob);
    }

    @Override
    public final void onWebsocketOpen(WebSocket conn, Handshakedata handshake) {
        startConnectionLostTimer();
        onOpen((ServerHandshake) handshake);
        connectLatch.countDown();
    }

    @Override
    public final void onWebsocketClose(WebSocket conn, int code, String reason, boolean remote) {
        stopConnectionLostTimer();
        if (writeThread != null) {
            writeThread.interrupt();
        }
        onClose(code, reason, remote);
        connectLatch.countDown();
        closeLatch.countDown();
    }

    @Override
    public final void onWebsocketError(WebSocket conn, Exception ex) {
        onError(ex);
    }

    @Override
    public final void onWriteDemand(WebSocket conn) {
        // nothing to do
    }

    @Override
    public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) {
        onCloseInitiated(code, reason);
    }

    @Override
    public void onWebsocketClosing(WebSocket conn, int code, String reason, boolean remote) {
        onClosing(code, reason, remote);
    }

    /**
     * Send when this peer sends a close handshake
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     */
    public void onCloseInitiated(int code, String reason) {
        // To overwrite
    }

    /**
     * Called as soon as no further frames are accepted
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     * @param remote Returns whether or not the closing of the connection was initiated by the remote
     *               host.
     */
    public void onClosing(int code, String reason, boolean remote) {
        // To overwrite
    }

    /**
     * Getter for the engine
     *
     * @return the engine
     */
    public WebSocket getConnection() {
        return engine;
    }

    @Override
    public InetSocketAddress getLocalSocketAddress(WebSocket conn) {
        if (socket != null) {
            return (InetSocketAddress) socket.getLocalSocketAddress();
        }
        return null;
    }

    @Override
    public InetSocketAddress getRemoteSocketAddress(WebSocket conn) {
        if (socket != null) {
            return (InetSocketAddress) socket.getRemoteSocketAddress();
        }
        return null;
    }

    /**
     * Called after an opening handshake has been performed and the given websocket is ready to be
     * written on.
     *
     * @param handshakedata The handshake of the websocket instance
     */
    public abstract void onOpen(ServerHandshake handshakedata);

    /**
     * Callback for string messages received from the remote host
     *
     * @param message The UTF-8 decoded message that was received.
     * @see #onMessage(ByteBuffer)
     */
    public abstract void onMessage(String message);

    /**
     * Called after the websocket connection has been closed.
     *
     * @param code   The codes can be looked up here: {@link CloseFrame}
     * @param reason Additional information string
     * @param remote Returns whether or not the closing of the connection was initiated by the remote
     *               host.
     */
    public abstract void onClose(int code, String reason, boolean remote);

    /**
     * Called when errors occurs. If an error causes the websocket connection to fail {@link
     * #onClose(int, String, boolean)} will be called additionally.
 This method will be called
     * primarily because of IO or protocol errors.
 If the given exception is an RuntimeException
     * that probably means that you encountered a bug.

     *
     * @param ex The exception causing this error
     */
    public abstract void onError(Exception ex);

    /**
     * Callback for binary messages received from the remote host
     *
     * @param bytes The binary message that was received.
     * @see #onMessage(String)
     */
    public void onMessage(ByteBuffer bytes) {
        // To overwrite
    }

    private class WebsocketWriteThread implements Runnable {

        private final WebSocketClient webSocketClient;

        WebsocketWriteThread(WebSocketClient webSocketClient) {
            this.webSocketClient = webSocketClient;
        }

        @Override
        public void run() {
            Thread.currentThread().setName("WebSocketWriteThread-" + Thread.currentThread().getId());
            try {
                runWriteData();
            } catch (IOException e) {
                handleIOException(e);
            } finally {
                closeSocket();
                writeThread = null;
            }
        }

        /**
         * Write the data into the outstream
         *
         * @throws IOException if write or flush did not work
         */
        private void runWriteData() throws IOException {
            try {

} catch (InterruptedException e) {
                for (ByteBuffer buffer : engine.outQueue) {
                    ostream.write(buffer.array(), 0, buffer.limit());
                    ostream.flush();
                }
                Thread.currentThread().interrupt();
            }
        }

        /**
         * Closing the socket
         */
        private void closeSocket() {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ex) {
                onWebsocketError(webSocketClient, ex);
            }
        }
    }

    /**
     * Method to set a proxy for this connection
     *
     * @param proxy the proxy to use for this websocket client
     */
    public void setProxy(Proxy proxy) {
        if (proxy == null) {
            throw new IllegalArgumentException();
        }
        this.proxy = proxy;
    }

    /**
     * Accepts bound and unbound sockets.
 This method must be called before connect.
     * If the given socket is not yet bound it will be bound to the uri specified in the constructor.
     *
     * @param socket The socket which should be used for the connection
     * @deprecated use setSocketFactory
     */
    @Deprecated
    public void setSocket(Socket socket) {
        if (this.socket != null) {
            throw new IllegalStateException("socket has already been set");
        }
        this.socket = socket;
    }

    /**
     * Accepts a SocketFactory.
 This method must be called before connect. The socket
     * will be bound to the uri specified in the constructor.
     *
     * @param socketFactory The socket factory which should be used for the connection.
     */
    public void setSocketFactory(SocketFactory socketFactory) {
        this.socketFactory = socketFactory;
    }

    @Override
    public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
        engine.sendFragmentedFrame(op, buffer, fin);
    }

    @Override
    public boolean isOpen() {
        return engine.isOpen();
    }

    @Override
    public boolean isFlushAndClose() {
        return engine.isFlushAndClose();
    }

    @Override
    public boolean isClosed() {
        return engine.isClosed();
    }

    @Override
    public boolean isClosing() {
        return engine.isClosing();
    }

    @Override
    public boolean hasBufferedData() {
        return engine.hasBufferedData();
    }

    @Override
    public void close(int code) {
        engine.close(code);
    }

    @Override
    public void close(int code, String message) {
        engine.close(code, message);
    }

    @Override
    public void closeConnection(int code, String message) {
        engine.closeConnection(code, message);
    }

    @Override
    public void send(ByteBuffer bytes) {
        engine.send(bytes);
    }

    @Override
    public void sendFrame(Framedata framedata) {
        engine.sendFrame(framedata);
    }

    @Override
    public void sendFrame(Collection<Framedata> frames) {
        engine.sendFrame(frames);
    }

    @Override
    public InetSocketAddress getLocalSocketAddress() {
        return engine.getLocalSocketAddress();
    }

    @Override
    public InetSocketAddress getRemoteSocketAddress() {
        return engine.getRemoteSocketAddress();
    }

    @Override
    public String getResourceDescriptor() {
        return uri.getPath();
    }

    @Override
    public boolean hasSSLSupport() {
        return socket instanceof SSLSocket;
    }

    @Override
    public SSLSession getSSLSession() {
        if (!hasSSLSupport()) {
            throw new IllegalArgumentException("This websocket uses ws instead of wss. No SSLSession available.");
        }
        return ((SSLSocket) socket).getSession();
    }

    @Override
    public IProtocol getProtocol() {
        return engine.getProtocol();
    }

    /**
     * Method to give some additional info for specific IOExceptions
     *
     * @param e the IOException causing a eot.
     */
    private void handleIOException(IOException e) {
        if (e instanceof SSLException) {
            onError(e);
        }
        engine.eot();
    }
}