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

asked Dec 2, 2015 at 5:31
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.