de.rkl.tools.tzconv.TimezoneConverter.java Source code

Java tutorial

Introduction

Here is the source code for de.rkl.tools.tzconv.TimezoneConverter.java

Source

/**
 * This file is part of timezoneConverterApplication.
 * <p>
 * timezoneConverterApplication is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * <p>
 * timezoneConverterApplication 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.
 * <p>
 * You should have received a copy of the GNU General Public License
 * along with timezoneConverterApplication.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.rkl.tools.tzconv;

import de.rkl.tools.tzconv.configuration.PreferencesProvider;
import de.rkl.tools.tzconv.model.ApplicationModel;
import de.rkl.tools.tzconv.view.ZoneIdSelectionDialog;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringWriter;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Optional;

import static de.rkl.tools.tzconv.configuration.Constants.BEAN_NAME_DATE_FORMATTER;
import static de.rkl.tools.tzconv.configuration.Constants.BEAN_NAME_DATE_TIME_FORMATTER;
import static de.rkl.tools.tzconv.model.ApplicationModel.DEFAULT_INITIAL_HOUR;
import static de.rkl.tools.tzconv.model.ApplicationModel.DEFAULT_INITIAL_MINUTE;
import static javafx.collections.FXCollections.observableArrayList;

/**
 * @author Raimund Klein <raimund.klein@gmx.de>
 */
@SuppressWarnings("WeakerAccess")
@ComponentScan
public class TimezoneConverter extends Application {
    private static final int DEFAULT_BOX_SPACING = 5;

    private ApplicationModel applicationModel;

    private PreferencesProvider preferencesProvider;

    private ZoneIdSelectionDialog zoneIdSelectionDialog;
    private VelocityEngine velocityEngine;
    private DateTimeFormatter dateFormatter;
    private DateTimeFormatter dateTimeFormatter;

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

    private ApplicationContext initializeSpringContext() {
        final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.scan(TimezoneConverter.class.getPackage().getName());
        context.refresh();
        return context;
    }

    @Override
    public void stop() throws Exception {
        preferencesProvider.flushPreferences();
    }

    @Override
    public void start(final Stage primaryStage) throws Exception {
        initializeSpringContextAndBeans();
        final VBox rootBox = new VBox(DEFAULT_BOX_SPACING, createDateTimeBox(), createTemplateBox(),
                createMainTextArea(), createZoneIdSelectionButtonBox(),
                createCopyToClipboard(createMainTextArea()));
        primaryStage.setScene(new Scene(rootBox, 700, 1000));
        primaryStage.show();
    }

    private Node createTemplateBox() {
        return new HBox(DEFAULT_BOX_SPACING, createTemplateCheckbox(), createTemplateFileSelection(),
                createTemplateNameField());
    }

    private Node createTemplateNameField() {
        final TextField templateNameField = new TextField();
        templateNameField.setEditable(false);
        final File templateFile = applicationModel.templateFile.getValue();
        templateNameField.setText(templateFile == null ? StringUtils.EMPTY : templateFile.getName());
        applicationModel.templateFile.addListener((observable, oldValue, newValue) -> {
            templateNameField.setText(newValue == null ? StringUtils.EMPTY : newValue.getName());
        });
        return templateNameField;
    }

    private Node createTemplateFileSelection() {
        final Button templateFileChooserButton = new Button();
        templateFileChooserButton.setText("Select template...");
        templateFileChooserButton.setOnAction(
                event -> applicationModel.templateFile.setValue(createTemplateFileChooser().showOpenDialog(null)));
        return templateFileChooserButton;
    }

    private FileChooser createTemplateFileChooser() {
        final FileChooser templateFileChooser = new FileChooser();
        templateFileChooser.setTitle("Select template");
        templateFileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Velocity Template", "*.vm"));
        templateFileChooser.initialFileNameProperty().bind(applicationModel.templateFile.asString());
        return templateFileChooser;
    }

    private Node createTemplateCheckbox() {
        final CheckBox templateCheckBox = new CheckBox("Use text template");
        templateCheckBox.setSelected(applicationModel.templateFile.getValue() != null);
        applicationModel.templateFile.addListener((observable, oldValue, newValue) -> {
            templateCheckBox.setSelected(newValue != null);
        });
        templateCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (oldValue)
                applicationModel.templateFile.set(null);
        });
        return templateCheckBox;
    }

    private void initializeSpringContextAndBeans() {
        final ApplicationContext springContext = initializeSpringContext();
        applicationModel = springContext.getBean(ApplicationModel.class);
        preferencesProvider = springContext.getBean(PreferencesProvider.class);
        zoneIdSelectionDialog = springContext.getBean(ZoneIdSelectionDialog.class);
        velocityEngine = springContext.getBean(VelocityEngine.class);
        dateFormatter = springContext.getBean(BEAN_NAME_DATE_FORMATTER, DateTimeFormatter.class);
        dateTimeFormatter = springContext.getBean(BEAN_NAME_DATE_TIME_FORMATTER, DateTimeFormatter.class);
    }

    private Node createZoneIdSelectionButtonBox() {
        final Button zoneIdSelection = new Button("Select zone IDs to be included in text");
        zoneIdSelection.setOnAction(this::openZoneIdSelectionDialog);
        final HBox buttonBox = new HBox(DEFAULT_BOX_SPACING, zoneIdSelection);
        buttonBox.alignmentProperty().setValue(Pos.CENTER);
        return buttonBox;
    }

    private void openZoneIdSelectionDialog(@SuppressWarnings("UnusedParameters") final ActionEvent actionEvent) {
        final Optional<Collection<ZoneId>> selectedZoneIds = zoneIdSelectionDialog.showAndWait();
        if (selectedZoneIds.isPresent()) {
            applicationModel.selectedZoneIds.setAll(selectedZoneIds.get());
            applicationModel.sortSelectedZoneIds();
        }
    }

    private Node createCopyToClipboard(final TextInputControl mainArea) {
        final Button copyToClipboard = new Button("Copy to Clipboard");
        copyToClipboard.setOnAction(event -> {
            final StringSelection stringSelection = new StringSelection(mainArea.textProperty().getValue());
            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, stringSelection);
        });
        final HBox copyToClipboardBox = new HBox(copyToClipboard);
        copyToClipboardBox.alignmentProperty().setValue(Pos.CENTER);
        return copyToClipboardBox;
    }

    private TextArea createMainTextArea() {
        final TextArea mainArea = new TextArea();
        mainArea.setEditable(false);
        updateTextAreaContent(mainArea, applicationModel.mainDateTime.getValue());
        applicationModel.mainDateTime.addListener((observable, oldValue, newValue) -> {
            updateTextAreaContent(mainArea, newValue);
        });
        applicationModel.selectedZoneIds.addListener((observable, oldValue, newValue) -> {
            updateTextAreaContent(mainArea, applicationModel.mainDateTime.getValue());
        });
        applicationModel.templateFile.addListener((observable, oldValue, newValue) -> {
            updateTextAreaContent(mainArea, applicationModel.mainDateTime.getValue());
        });
        return mainArea;
    }

    private void updateTextAreaContent(final TextArea mainArea, final ZonedDateTime dateTime) {
        final StringWriter mainAreaWriter = new StringWriter();
        fillFromTemplate(mainAreaWriter, dateTime);
        mainArea.setText(mainAreaWriter.toString());
    }

    private void fillFromTemplate(final StringWriter mainAreaWriter, final ZonedDateTime dateTime) {
        final File templateFileObject = applicationModel.templateFile.getValue();
        if (templateFileObject == null) {
            mainAreaWriter.append(formatConvertedDateTimes(dateTime));
        } else {
            try {
                velocityEngine.evaluate(initVelocityContext(dateTime), mainAreaWriter, templateFileObject.getName(),
                        new FileReader(templateFileObject));
            } catch (FileNotFoundException e) {
                final Alert fileNotFoundAlert = new Alert(Alert.AlertType.ERROR);
                fileNotFoundAlert.setHeaderText("Template file not found");
                fileNotFoundAlert.setContentText(String.valueOf(e));
                fileNotFoundAlert.showAndWait();
            }
        }
    }

    private VelocityContext initVelocityContext(final ZonedDateTime dateTime) {
        final VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("originalDate", formatOriginalDate(dateTime));
        velocityContext.put("convertedTimes", formatConvertedDateTimes(dateTime));
        return velocityContext;
    }

    private String formatOriginalDate(final ZonedDateTime dateTime) {
        return dateTime.format(dateFormatter);
    }

    private String formatConvertedDateTimes(final ZonedDateTime localDateTime) {
        final StringBuilder convertedDateTimeBuilder = new StringBuilder();
        applicationModel.selectedZoneIds.forEach(zoneId -> {
            convertedDateTimeBuilder.append(localDateTime.withZoneSameInstant(zoneId).format(dateTimeFormatter));
            convertedDateTimeBuilder.append('\n');
        });
        return convertedDateTimeBuilder.toString();
    }

    private HBox createDateTimeBox() {
        return new HBox(DEFAULT_BOX_SPACING, createDatePicker(), createHourSpinner(), createMinuteSpinner(),
                createTimezoneComboBox());
    }

    private ComboBox<ZoneId> createTimezoneComboBox() {
        final ComboBox<ZoneId> zoneIdBox = new ComboBox<>(
                observableArrayList(ApplicationModel.ZONE_OFFSETS_2_ZONE_IDS.values()));
        zoneIdBox.setEditable(false);
        zoneIdBox.setValue(applicationModel.mainDateTime.getValue().getZone());
        zoneIdBox.setOnAction(event -> applicationModel.mainDateTime
                .setValue(applicationModel.mainDateTime.getValue().withZoneSameLocal(zoneIdBox.getValue())));
        return zoneIdBox;
    }

    private Spinner<Integer> createMinuteSpinner() {
        final Spinner<Integer> minuteSpinner = new Spinner<>(0, 45, DEFAULT_INITIAL_MINUTE, 15);
        minuteSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
            applicationModel.mainDateTime.setValue(applicationModel.mainDateTime.getValue().withMinute(newValue));
        });
        return minuteSpinner;
    }

    private Spinner<Integer> createHourSpinner() {
        final Spinner<Integer> hourSpinner = new Spinner<>(0, 23, DEFAULT_INITIAL_HOUR);
        hourSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
            applicationModel.mainDateTime.setValue(applicationModel.mainDateTime.getValue().withHour(newValue));
        });
        return hourSpinner;
    }

    private DatePicker createDatePicker() {
        final DatePicker datePicker = new DatePicker(applicationModel.mainDateTime.getValue().toLocalDate());
        datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
            applicationModel.mainDateTime.setValue(applicationModel.mainDateTime.getValue().with(newValue));
        });
        return datePicker;
    }

}