3
\$\begingroup\$

Simple tool to save a web page's source. A result of trying to learn and apply an MVC approach. The FXML is generated via Scene Builder, but included for completion.

I welcome any general feedback on how to improve anything but in particular:

  1. There are many exceptions, current impression is that current handling is not ideal. What is the conventional wisdom on handling a plethora of io/connection errors?
  2. API and methods utilized in reading and writing were changed several times. Thanks to @rolfl's answer to a related question, currently refactored to employ Java 8 streams, is this the most appropriate for purpose? Can it be cleaner? Faster?
  3. Any conventions that should be followed, especially in regards to MVC, FXML.

The following is the source, with a line in the main class commented out so this may be executed if desired.

Main Class:

import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;

public class HTMLSave extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        Parent root = null;
        try {
            root = FXMLLoader.load(getClass().getResource("HTML_Save.fxml"));
        } catch (IOException ioe) {
            System.err.println("Could not load FXML");
        }

        Scene scene = new Scene(root, Color.TRANSPARENT);
        scene.getStylesheets().add("html-save.css");

        stage.setScene(scene);
        stage.setTitle("HTML Saver");
        //stage.getIcons().add(new Image(getClass().getResourceAsStream("icon.png")));
        stage.show();
    }
}

Controller Class:

import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.stream.Collectors;


import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;

public class HTMLSaveController implements Initializable {
    @FXML
    private Button saveButton;
    @FXML
    private Button openButton;
    @FXML
    private TextField htmlField;
    @FXML
    private TextField filenameField;
    @FXML
    private ProgressBar progressBar;

    private String destinationFile;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        System.out.println("HTML Saver view successfully loaded.");
    }

    public void handleSaveRequest() {
        String filename = filenameField.getText();
        String url = htmlField.getText();
        if (url.isEmpty()) {
            errorAlert("URL missing", "Please paste in website address");
        } else if (filename.isEmpty()) {
            errorAlert("No filename", "Please enter a file name to save html");
        } else {
            destinationFile = filename + ".html";
            try (Writer destination = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destinationFile), "UTF-8"))) {
                savePageSource(url, destination);
            } catch (IOException ioe) {
                System.err.println("Could not write to " + filename + '.');
            }
        }
    }

    public void handleOpenRequest() {
        try {
            Desktop.getDesktop().open(new File(destinationFile));
        } catch (IOException ioe) {
            System.err.println(destinationFile + " not found.");
        }
    }

    private void errorAlert(String message, String infoMessage) {
        Alert alert = new Alert(AlertType.ERROR);
        alert.setTitle("Error");
        alert.setHeaderText(message);
        alert.setContentText(infoMessage);
        alert.showAndWait();
    }

    private void savePageSource(String url, Writer destination) throws IOException {
        Task saveTask = new Task() {
            @Override
            protected Void call() throws IOException {
                updateProgress(0, 100);
                openButton.setDisable(true);

                try (BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(url).openStream()))) {
                    progressBar.setCursor(Cursor.WAIT);
                    destination.write(reader.lines().collect(Collectors.joining("\n")));

                } catch (IOException ioe) {
                    throw new UncheckedIOException(ioe);
                } 
                progressBar.setCursor(Cursor.DEFAULT);
                updateProgress(1, 1);
                openButton.setDisable(false);
                return null;
            }
        };

        progressBar.progressProperty().bind(saveTask.progressProperty());
        Thread savingThread = new Thread(saveTask);
        savingThread.start();
        try {
            savingThread.join();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
    }
}

CSS:

.label {
    -fx-text-fill: rgb(166, 226, 46);
    -fx-font-weight: bold;
}
.button {
    -fx-text-fill: rgb(166, 226, 46);
    -fx-background-color: rgb(39, 40, 34);
    -fx-border-radius: 20;
    -fx-background-radius: 20;
    -fx-padding: 5;
}
.button:hover {
    -fx-text-fill: rgb(249, 38, 114);
}
.root{
    -fx-font-family: "Courier New";
    -fx-background: rgb(61, 61, 59);
    -fx-base: rgb(39, 40, 34);
}
.text-field:focused {
    -fx-background-color: #a9a9a9 , white , white;
    -fx-background-insets: 0 -1 -1 -1, 0 0 0 0, 0 -1 3 -1;
}
.progress-bar > .bar {
    -fx-background-color: linear-gradient(
        from 0px .75em to .75em 0px,
        repeat,
        rgb(253, 151, 31) 0%,
        rgb(253, 151, 31) 49%,
        derive(rgb(253, 151, 31), 30%) 50%,
        derive(rgb(253, 151, 31), 30%) 99%
    );
}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="174.0" prefWidth="474.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="HTMLSaveController">
  <columnConstraints>
    <ColumnConstraints hgrow="SOMETIMES" maxWidth="309.0" minWidth="10.0" prefWidth="203.0" />
    <ColumnConstraints hgrow="SOMETIMES" maxWidth="398.0" minWidth="10.0" prefWidth="271.0" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints maxHeight="121.0" minHeight="10.0" prefHeight="114.0" vgrow="SOMETIMES" />
    <RowConstraints maxHeight="181.0" minHeight="10.0" prefHeight="129.0" vgrow="SOMETIMES" />
    <RowConstraints maxHeight="293.0" minHeight="10.0" prefHeight="121.0" vgrow="SOMETIMES" />
  </rowConstraints>
   <children>
      <Button fx:id="saveButton" defaultButton="true" mnemonicParsing="false" onAction="#handleSaveRequest" prefHeight="39.0" prefWidth="73.0" text="Save" textFill="#36d721" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="2" GridPane.valignment="TOP">
         <font>
            <Font size="18.0" />
         </font>
         <cursor>
            <Cursor fx:constant="HAND" />
         </cursor></Button>
      <Label text="Paste Address: ">
         <font>
            <Font size="20.0" />
         </font></Label>
      <Label text="Enter Filename: " GridPane.rowIndex="1">
         <font>
            <Font size="20.0" />
         </font></Label>
      <TextField fx:id="htmlField" GridPane.columnIndex="1" />
      <TextField fx:id="filenameField" GridPane.columnIndex="1" GridPane.rowIndex="1" />
      <VBox prefHeight="123.0" prefWidth="205.0" GridPane.rowIndex="2">
         <children>
            <Button fx:id="openButton" disable="true" mnemonicParsing="false" onAction="#handleOpenRequest" prefHeight="39.0" prefWidth="81.0" text="Open" textFill="#6f2929">
               <font>
                  <Font size="18.0" />
               </font>
               <cursor>
                  <Cursor fx:constant="HAND" />
               </cursor>
            </Button>
            <ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0">
               <cursor>
                  <Cursor fx:constant="DEFAULT" />
               </cursor>
            </ProgressBar>
         </children>
      </VBox>
   </children>
</GridPane>

The result:

Application sample run

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.