diff --git a/README.md b/README.md
index 0d7c78d..5b33c1f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,11 @@
# dev-tools
-Common development tools in one app
+Common development tools in one app.
+* [Json Editor](#json_editor)
+* [UUID/Password Generator](#generator)
+* [Hash Calculator](#hash_calculator)
+* [Epoch Converter](#epoch_converter)
+* [Regular Expression Tester](#regex)
+* [Rest API Tester](#rest_api)
## Installation
This is a Maven JavaFX application.
@@ -13,6 +19,8 @@ or download the latest release.
## Tools List
+
+
### Json Editor
* Json Pretty Print with Highlighting
* Json Validation
@@ -20,12 +28,16 @@ or download the latest release.
![](./images/json_editor.png)
-### Generator
+
+
+### UUID/Password Generator
* UUID Generator
* Password Generator
![](./images/generator.png)
+
+
### Hash Calculator
* Hash Functions
* URL Encode/Decode
@@ -33,6 +45,8 @@ or download the latest release.
![](./images/hash_calculator.png)
+
+
### Epoch Converter
* Current Unix Epoch Time
* Timestamp to Human Date
@@ -40,6 +54,21 @@ or download the latest release.
![](./images/epoch_converter.png)
+
+
+### Regular Expression Tester
+* Regex Flags
+* Capturing Groups
+
+![](./images/regex.png)
+
+
+
+### Rest API Tester
+* Rest API Testing Client
+
+![](./images/rest_api.png)
+
## Contributing
If you find this project useful and want to contribute, please open an issue or create a PR.
diff --git a/images/epoch_converter.png b/images/epoch_converter.png
index 36c8927..417c0f5 100644
Binary files a/images/epoch_converter.png and b/images/epoch_converter.png differ
diff --git a/images/generator.png b/images/generator.png
index f2c0c98..f7f0efe 100644
Binary files a/images/generator.png and b/images/generator.png differ
diff --git a/images/hash_calculator.png b/images/hash_calculator.png
index c2ab7bc..eca5c99 100644
Binary files a/images/hash_calculator.png and b/images/hash_calculator.png differ
diff --git a/images/json_editor.png b/images/json_editor.png
index f5983e2..261b32c 100644
Binary files a/images/json_editor.png and b/images/json_editor.png differ
diff --git a/images/regex.png b/images/regex.png
new file mode 100644
index 0000000..363108e
Binary files /dev/null and b/images/regex.png differ
diff --git a/images/rest_api.png b/images/rest_api.png
new file mode 100644
index 0000000..0a3b7f2
Binary files /dev/null and b/images/rest_api.png differ
diff --git a/src/main/java/com/github/reugn/devtools/Main.java b/src/main/java/com/github/reugn/devtools/Main.java
index 8c3813a..dd77e06 100644
--- a/src/main/java/com/github/reugn/devtools/Main.java
+++ b/src/main/java/com/github/reugn/devtools/Main.java
@@ -20,7 +20,7 @@ public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/main.fxml"));
primaryStage.setTitle("Dev-tools");
primaryStage.getIcons().add(new Image("/images/icons8-toolbox-64.png"));
- Scene scene = new Scene(root, 800, 500);
+ Scene scene = new Scene(root, 900, 500);
scene.getStylesheets().addAll("/css/main.css", "/css/json-highlighting.css");
primaryStage.setScene(scene);
primaryStage.show();
diff --git a/src/main/java/com/github/reugn/devtools/controllers/EpochController.java b/src/main/java/com/github/reugn/devtools/controllers/EpochController.java
index a6da954..0f6d377 100644
--- a/src/main/java/com/github/reugn/devtools/controllers/EpochController.java
+++ b/src/main/java/com/github/reugn/devtools/controllers/EpochController.java
@@ -1,46 +1,56 @@
package com.github.reugn.devtools.controllers;
import com.github.reugn.devtools.services.EpochService;
+import com.github.reugn.devtools.utils.Elements;
import com.github.reugn.devtools.utils.Logger;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.control.TextArea;
-import javafx.scene.control.TextField;
-import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
+import javafx.scene.control.*;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ResourceBundle;
+import java.util.TimeZone;
public class EpochController implements Initializable, Logger {
- public Label currentEpochLabel;
-
- public TextField currentEpoch;
-
- public Button currentEpochRefreshButton;
-
- public TextField tsToHumanField;
-
- public Button tsToHumanButton;
-
- public TextArea tsToHumanResult;
-
- public Button humanToTsButton;
-
- public TextArea humanToTsResult;
-
- public TextField epochYear;
- public TextField epochMonth;
- public TextField epochDay;
- public TextField epochHour;
- public TextField epochMinute;
- public TextField epochSecond;
+ @FXML
+ private Label currentEpochLabel;
+ @FXML
+ private TextField currentEpoch;
+ @FXML
+ private Button currentEpochRefreshButton;
+ @FXML
+ private TextField tsToHumanField;
+ @FXML
+ private Button tsToHumanButton;
+ @FXML
+ private TextArea tsToHumanResult;
+ @FXML
+ private Button humanToTsButton;
+ @FXML
+ private TextArea humanToTsResult;
+ @FXML
+ private TextField epochYear;
+ @FXML
+ private TextField epochMonth;
+ @FXML
+ private TextField epochDay;
+ @FXML
+ private TextField epochHour;
+ @FXML
+ private TextField epochMinute;
+ @FXML
+ private TextField epochSecond;
+ @FXML
+ private ComboBox timeZoneComboBox;
+ private int timeZoneComboBoxIndex;
@FXML
private void handleRefreshEpoch(final ActionEvent event) {
@@ -55,8 +65,7 @@ private void handleTsToHumanEpoch(final ActionEvent event) {
String result = EpochService.toHumanEpoch(dt);
tsToHumanResult.setText(result);
} catch (Exception e) {
- tsToHumanField.setBorder(new Border(new BorderStroke(Color.RED,
- BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT)));
+ tsToHumanField.setBorder(Elements.alertBorder);
tsToHumanResult.setText("");
}
}
@@ -71,13 +80,30 @@ private void handleHumanToTsEpoch(final ActionEvent event) {
int hour = EpochService.validate(epochHour, 0, 24);
int minute = EpochService.validate(epochMinute, 0, 59);
int second = EpochService.validate(epochSecond, 0, 59);
- String result = EpochService.toTsEpoch(year, month, day, hour, minute, second);
+ String timeZone = timeZoneComboBox.getSelectionModel().getSelectedItem();
+ String result = EpochService.toTsEpoch(year, month, day, hour, minute, second, timeZone);
humanToTsResult.setText(result);
} catch (Exception e) {
humanToTsResult.setText("");
}
}
+ @FXML
+ private void handleTimeZoneSearch(KeyEvent keyEvent) {
+ String key = keyEvent.getText();
+ if (key.length() == 0) return;
+ int i = 0;
+ for (String item : timeZoneComboBox.getItems()) {
+ if (item.toLowerCase().startsWith(key) && i > timeZoneComboBoxIndex) {
+ timeZoneComboBox.setValue(item);
+ timeZoneComboBoxIndex = i;
+ return;
+ }
+ i++;
+ }
+ timeZoneComboBoxIndex = 0;
+ }
+
private void resetBorders() {
epochYear.setBorder(Border.EMPTY);
epochMonth.setBorder(Border.EMPTY);
@@ -89,11 +115,11 @@ private void resetBorders() {
@Override
public void initialize(URL location, ResourceBundle resources) {
- HBox.setMargin(currentEpochLabel, new Insets(20, 5, 15, 0));
- HBox.setMargin(currentEpoch, new Insets(15, 5, 15, 0));
- HBox.setMargin(currentEpochRefreshButton, new Insets(15, 5, 15, 0));
- HBox.setMargin(tsToHumanField, new Insets(15, 5, 15, 0));
- HBox.setMargin(tsToHumanButton, new Insets(15, 5, 15, 0));
+ HBox.setMargin(currentEpochLabel, new Insets(15, 5, 10, 0));
+ HBox.setMargin(currentEpoch, new Insets(10, 5, 10, 0));
+ HBox.setMargin(currentEpochRefreshButton, new Insets(10, 5, 10, 0));
+ HBox.setMargin(tsToHumanField, new Insets(10, 5, 10, 0));
+ HBox.setMargin(tsToHumanButton, new Insets(10, 5, 10, 0));
GridPane.setMargin(epochYear, new Insets(10, 5, 0, 0));
GridPane.setMargin(epochMonth, new Insets(10, 5, 0, 0));
@@ -102,6 +128,7 @@ public void initialize(URL location, ResourceBundle resources) {
GridPane.setMargin(epochMinute, new Insets(10, 5, 0, 0));
GridPane.setMargin(epochSecond, new Insets(10, 5, 0, 0));
GridPane.setMargin(humanToTsButton, new Insets(10, 5, 0, 0));
+ GridPane.setMargin(timeZoneComboBox, new Insets(10, 5, 0, 0));
long now = System.currentTimeMillis();
currentEpoch.setText(Long.toString(now));
@@ -114,5 +141,9 @@ public void initialize(URL location, ResourceBundle resources) {
epochHour.setText(String.valueOf(date.getHour()));
epochMinute.setText(String.valueOf(date.getMinute()));
epochSecond.setText(String.valueOf(date.getSecond()));
+
+ timeZoneComboBox.getItems().setAll(TimeZone.getAvailableIDs());
+ timeZoneComboBox.setValue("UTC");
+ timeZoneComboBoxIndex = 0;
}
}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java b/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java
index 4fecd6b..dfc6102 100644
--- a/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java
+++ b/src/main/java/com/github/reugn/devtools/controllers/GeneratorController.java
@@ -1,5 +1,6 @@
package com.github.reugn.devtools.controllers;
+import com.github.reugn.devtools.utils.Elements;
import com.github.reugn.devtools.utils.Logger;
import com.github.reugn.devtools.utils.PasswordGenerator;
import javafx.event.ActionEvent;
@@ -7,8 +8,8 @@
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.*;
-import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.VBox;
import java.net.URL;
import java.security.InvalidParameterException;
@@ -19,37 +20,26 @@ public class GeneratorController implements Initializable, Logger {
@FXML
private ComboBox uuidAmount;
-
@FXML
private Label uuidAmountLabel;
-
@FXML
private CheckBox uuidUpperCase;
-
@FXML
private CheckBox uuidHyphens;
-
@FXML
private TextArea generatorResult;
-
@FXML
private CheckBox pwdLowChars;
-
@FXML
private CheckBox pwdDigits;
-
@FXML
private CheckBox pwdUpperChars;
-
@FXML
private CheckBox pwdSymbols;
-
@FXML
private TextField pwdLength;
-
@FXML
private Label pwdLengthLabel;
-
@FXML
private Button clearButton;
@@ -77,8 +67,7 @@ private void handleGeneratePasswordAction(final ActionEvent actionEvent) {
pwdLength.setBorder(Border.EMPTY);
length = validatePasswordLength();
} catch (Exception e) {
- pwdLength.setBorder(new Border(new BorderStroke(Color.RED,
- BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT)));
+ pwdLength.setBorder(Elements.alertBorder);
return;
}
PasswordGenerator generator = new PasswordGenerator.PasswordGeneratorBuilder()
diff --git a/src/main/java/com/github/reugn/devtools/controllers/HashController.java b/src/main/java/com/github/reugn/devtools/controllers/HashController.java
index d98228b..47802b7 100644
--- a/src/main/java/com/github/reugn/devtools/controllers/HashController.java
+++ b/src/main/java/com/github/reugn/devtools/controllers/HashController.java
@@ -48,6 +48,7 @@ private void handleClear(final ActionEvent event) {
@FXML
private void handleCalculate(final ActionEvent event) {
+ hashMessage.setText("");
try {
String enc;
String data = hashInput.getText();
diff --git a/src/main/java/com/github/reugn/devtools/controllers/JsonController.java b/src/main/java/com/github/reugn/devtools/controllers/JsonController.java
index f7046bc..9b9d9d6 100644
--- a/src/main/java/com/github/reugn/devtools/controllers/JsonController.java
+++ b/src/main/java/com/github/reugn/devtools/controllers/JsonController.java
@@ -1,180 +1,26 @@
package com.github.reugn.devtools.controllers;
-import com.github.reugn.devtools.services.JsonService;
-import com.github.reugn.devtools.utils.JsonSearchState;
-import com.github.reugn.devtools.utils.Logger;
-import javafx.event.ActionEvent;
import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
-import javafx.geometry.Insets;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.control.ToolBar;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.layout.HBox;
-import javafx.scene.paint.Color;
-import org.controlsfx.control.textfield.CustomTextField;
-import org.fxmisc.richtext.CodeArea;
-import org.fxmisc.richtext.model.StyleSpans;
-import org.fxmisc.richtext.model.StyleSpansBuilder;
import java.net.URL;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
import java.util.ResourceBundle;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-public class JsonController implements Initializable, Logger {
-
- public Button clearSpacesButton;
- public Button formatButton;
- public Button clearButton;
- public Button buttonCloseSearch;
- public ToolBar barSearch;
- public CustomTextField fieldSearch;
- public Button buttonSearchUp;
- public Button buttonSearchDown;
- public Label labelMatches;
-
- private JsonSearchState searchState;
-
- @FXML
- private Label jsonMessage;
-
- @FXML
- private CodeArea jsonArea;
-
- private static final Pattern JSON_REGEX = Pattern.compile("(?\\{|\\})|" +
- "(?\\\".*\\\")\\s*:\\s*|" +
- "(?\\\".*\\\")|" +
- "\\[(?.*)\\]|" +
- "(?\\d+.?\\d*)|" +
- "(?true|false)|" +
- "(?null)");
-
- @FXML
- private void handlePrettyPrint(final ActionEvent event) {
- String data = jsonArea.getText();
- try {
- String pretty = JsonService.format(data);
- jsonArea.replaceText(pretty);
- jsonMessage.setText("");
- } catch (Exception e) {
- jsonMessage.setText("Invalid JSON");
- }
- }
-
- private StyleSpans> computeHighlighting(String text) {
- Matcher matcher = JSON_REGEX.matcher(text);
- int lastKwEnd = 0;
- StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>();
- while (matcher.find()) {
- String styleClass
- = matcher.group("JSONPROPERTY") != null ? "json_property"
- : matcher.group("JSONARRAY") != null ? "json_array"
- : matcher.group("JSONCURLY") != null ? "json_curly"
- : matcher.group("JSONBOOL") != null ? "json_bool"
- : matcher.group("JSONNULL") != null ? "json_null"
- : matcher.group("JSONNUMBER") != null ? "json_number"
- : matcher.group("JSONVALUE") != null ? "json_value"
- : null;
- spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
- spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
- lastKwEnd = matcher.end();
- }
- spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
- return spansBuilder.create();
- }
-
- @FXML
- private void handleClearSpaces(final ActionEvent event) {
- String data = jsonArea.getText();
- try {
- String cleared = JsonService.clearSpaces(data);
- jsonArea.replaceText(cleared);
- jsonMessage.setText("");
- } catch (Exception e) {
- jsonMessage.setText("Invalid JSON");
- }
- }
-
- @FXML
- private void handleClear(final ActionEvent event) {
- jsonMessage.setText("");
- jsonArea.replaceText("");
- handleCloseSearchAction(event);
- }
-
- @FXML
- private void handleSearchBarEvent(final KeyEvent event) {
- if (event.isControlDown() && event.getCode() == KeyCode.F) {
- barSearch.setVisible(true);
- barSearch.setManaged(true);
- fieldSearch.requestFocus();
- } else if (event.getCode() == KeyCode.ESCAPE) {
- barSearch.setVisible(false);
- barSearch.setManaged(false);
- }
- }
-
- @FXML
- private void handleCloseSearchAction(final ActionEvent event) {
- jsonArea.deselect();
- fieldSearch.setText("");
- labelMatches.setText("");
- barSearch.setVisible(false);
- barSearch.setManaged(false);
- }
+public class JsonController extends TabPaneController {
@FXML
- private void handleSearchBarAction(final KeyEvent event) {
- if (event.getCode() == KeyCode.ENTER) {
- handleSearchDownAction(new ActionEvent());
- }
- }
-
- @FXML
- private void handleSearchUpAction(final ActionEvent event) {
- Optional span = getSearchState().prev();
- jsonArea.deselect();
- span.ifPresent(searchSpan -> jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo()));
- labelMatches.setText(getSearchState().toString());
- }
+ private static JsonController self;
- @FXML
- private void handleSearchDownAction(final ActionEvent event) {
- Optional span = getSearchState().next();
- jsonArea.deselect();
- span.ifPresent(searchSpan -> jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo()));
- labelMatches.setText(getSearchState().toString());
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ self = this;
}
- private JsonSearchState getSearchState() {
- if (searchState == null || !searchState.isValid(fieldSearch.getText(), jsonArea.getText())) {
- searchState = new JsonSearchState(fieldSearch.getText(), jsonArea.getText());
- }
- return searchState;
+ static JsonController instance() {
+ return self;
}
@Override
- public void initialize(URL location, ResourceBundle resources) {
- jsonArea.setPrefHeight(1024);
- jsonArea.setWrapText(true);
- jsonArea.textProperty().addListener((obs, oldText, newText) -> {
- jsonArea.setStyleSpans(0, computeHighlighting(newText));
- });
- jsonMessage.setPadding(new Insets(5));
- jsonMessage.setTextFill(Color.RED);
-
- HBox.setMargin(clearSpacesButton, new Insets(0, 5, 10, 0));
- HBox.setMargin(formatButton, new Insets(0, 5, 10, 0));
- HBox.setMargin(clearButton, new Insets(0, 5, 10, 0));
- HBox.setMargin(jsonMessage, new Insets(0, 5, 10, 0));
-
- barSearch.setVisible(false);
- barSearch.setManaged(false);
+ protected String getInnerResource() {
+ return "/views/json_tab.fxml";
}
}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java b/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java
new file mode 100644
index 0000000..2635dad
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/controllers/JsonTabController.java
@@ -0,0 +1,201 @@
+package com.github.reugn.devtools.controllers;
+
+import com.github.reugn.devtools.services.JsonService;
+import com.github.reugn.devtools.utils.JsonSearchState;
+import com.github.reugn.devtools.utils.Logger;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ToolBar;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
+import org.controlsfx.control.textfield.CustomTextField;
+import org.fxmisc.richtext.CodeArea;
+import org.fxmisc.richtext.model.StyleSpans;
+import org.fxmisc.richtext.model.StyleSpansBuilder;
+
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JsonTabController implements Initializable, Logger {
+
+ @FXML
+ private Button clearSpacesButton;
+ @FXML
+ private Button formatButton;
+ @FXML
+ private Button clearButton;
+ @FXML
+ private Button buttonCloseSearch;
+ @FXML
+ private ToolBar barSearch;
+ @FXML
+ private CustomTextField fieldSearch;
+ @FXML
+ private Button buttonSearchUp;
+ @FXML
+ private Button buttonSearchDown;
+ @FXML
+ private Label labelMatches;
+ @FXML
+ private Label jsonMessage;
+ @FXML
+ private CodeArea jsonArea;
+
+ private JsonSearchState searchState;
+
+ private static final Pattern JSON_REGEX = Pattern.compile("(?\\{|\\})|" +
+ "(?\\\".*\\\")\\s*:\\s*|" +
+ "(?\\\".*\\\")|" +
+ "\\[(?.*)\\]|" +
+ "(?\\d+.?\\d*)|" +
+ "(?true|false)|" +
+ "(?null)");
+
+ @FXML
+ private void handlePrettyPrint(final ActionEvent event) {
+ String data = jsonArea.getText();
+ try {
+ String pretty = JsonService.format(data);
+ JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem()
+ .setText(tabTitle(JsonService.clearSpaces(data)));
+ jsonArea.replaceText(pretty);
+ jsonMessage.setText("");
+ } catch (Exception e) {
+ jsonMessage.setText("Invalid JSON");
+ }
+ }
+
+ @FXML
+ private void handleClearSpaces(final ActionEvent event) {
+ String data = jsonArea.getText();
+ try {
+ String cleared = JsonService.clearSpaces(data);
+ JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText(tabTitle(cleared));
+ jsonArea.replaceText(cleared);
+ jsonMessage.setText("");
+ } catch (Exception e) {
+ jsonMessage.setText("Invalid JSON");
+ }
+ }
+
+ private static int tabTitleLength = 12;
+
+ private String tabTitle(String json) {
+ return json.length() > tabTitleLength ? json.substring(0, tabTitleLength) : json;
+ }
+
+ private StyleSpans> computeHighlighting(String text) {
+ Matcher matcher = JSON_REGEX.matcher(text);
+ int lastKwEnd = 0;
+ StyleSpansBuilder> spansBuilder = new StyleSpansBuilder<>();
+ while (matcher.find()) {
+ String styleClass
+ = matcher.group("JSONPROPERTY") != null ? "json_property"
+ : matcher.group("JSONARRAY") != null ? "json_array"
+ : matcher.group("JSONCURLY") != null ? "json_curly"
+ : matcher.group("JSONBOOL") != null ? "json_bool"
+ : matcher.group("JSONNULL") != null ? "json_null"
+ : matcher.group("JSONNUMBER") != null ? "json_number"
+ : matcher.group("JSONVALUE") != null ? "json_value"
+ : null;
+ spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
+ spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
+ lastKwEnd = matcher.end();
+ }
+ spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
+ return spansBuilder.create();
+ }
+
+ @FXML
+ private void handleClear(final ActionEvent event) {
+ jsonMessage.setText("");
+ jsonArea.replaceText("");
+ JsonController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText("New");
+ handleCloseSearchAction(event);
+ }
+
+ @FXML
+ private void handleSearchBarEvent(final KeyEvent event) {
+ if (event.isControlDown() && event.getCode() == KeyCode.F) {
+ barSearch.setVisible(true);
+ barSearch.setManaged(true);
+ fieldSearch.requestFocus();
+ } else if (event.getCode() == KeyCode.ESCAPE) {
+ barSearch.setVisible(false);
+ barSearch.setManaged(false);
+ }
+ }
+
+ @FXML
+ private void handleCloseSearchAction(final ActionEvent event) {
+ jsonArea.deselect();
+ fieldSearch.setText("");
+ labelMatches.setText("");
+ barSearch.setVisible(false);
+ barSearch.setManaged(false);
+ }
+
+ @FXML
+ private void handleSearchBarAction(final KeyEvent event) {
+ if (event.getCode() == KeyCode.ENTER) {
+ handleSearchDownAction(new ActionEvent());
+ }
+ }
+
+ @FXML
+ private void handleSearchUpAction(final ActionEvent event) {
+ doSearch(getSearchState().prev());
+ }
+
+ @FXML
+ private void handleSearchDownAction(final ActionEvent event) {
+ doSearch(getSearchState().next());
+ }
+
+ private void doSearch(Optional span) {
+ jsonArea.deselect();
+ span.ifPresent(searchSpan -> {
+ jsonArea.moveTo(searchSpan.getFrom());
+ jsonArea.requestFollowCaret();
+ jsonArea.selectRange(searchSpan.getFrom(), searchSpan.getTo());
+ });
+ labelMatches.setText(getSearchState().toString());
+ }
+
+ private JsonSearchState getSearchState() {
+ if (searchState == null || !searchState.isValid(fieldSearch.getText(), jsonArea.getText())) {
+ searchState = new JsonSearchState(fieldSearch.getText(), jsonArea.getText());
+ }
+ return searchState;
+ }
+
+ @Override
+ public void initialize(URL location, ResourceBundle resources) {
+ jsonArea.setPrefHeight(1024);
+ jsonArea.setWrapText(true);
+ jsonArea.textProperty().addListener((obs, oldText, newText) -> {
+ jsonArea.setStyleSpans(0, computeHighlighting(newText));
+ });
+ jsonMessage.setPadding(new Insets(5));
+ jsonMessage.setTextFill(Color.RED);
+
+ HBox.setMargin(clearSpacesButton, new Insets(0, 5, 10, 0));
+ HBox.setMargin(formatButton, new Insets(0, 5, 10, 0));
+ HBox.setMargin(clearButton, new Insets(0, 5, 10, 0));
+ HBox.setMargin(jsonMessage, new Insets(0, 5, 10, 0));
+
+ barSearch.setVisible(false);
+ barSearch.setManaged(false);
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/RegexController.java b/src/main/java/com/github/reugn/devtools/controllers/RegexController.java
new file mode 100644
index 0000000..87a0e22
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/controllers/RegexController.java
@@ -0,0 +1,123 @@
+package com.github.reugn.devtools.controllers;
+
+import com.github.reugn.devtools.models.RegexResult;
+import com.github.reugn.devtools.services.RegexService;
+import com.github.reugn.devtools.utils.Elements;
+import com.github.reugn.devtools.utils.Logger;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
+import javafx.util.Pair;
+import org.controlsfx.control.CheckComboBox;
+import org.fxmisc.richtext.CodeArea;
+
+import java.net.URL;
+import java.util.List;
+import java.util.ResourceBundle;
+
+public class RegexController implements Initializable, Logger {
+
+ @FXML
+ private TextField regexExpression;
+ @FXML
+ private Button regexCalculateButton;
+ @FXML
+ private Button regexClearButton;
+ @FXML
+ private Label regexMessage;
+ @FXML
+ private CodeArea regexTarget;
+ @FXML
+ private TextArea regexResult;
+ @FXML
+ private Label regexLabel;
+ @FXML
+ private CheckComboBox regexFlagsComboBox;
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ regexExpression.setPrefWidth(512);
+ regexTarget.setPrefHeight(256);
+ HBox.setMargin(regexExpression, new Insets(10, 5, 10, 0));
+ HBox.setMargin(regexCalculateButton, new Insets(10, 5, 10, 0));
+ HBox.setMargin(regexClearButton, new Insets(10, 5, 10, 0));
+ HBox.setMargin(regexFlagsComboBox, new Insets(10, 5, 10, 0));
+
+ regexLabel.setPadding(new Insets(15, 5, 10, 0));
+ regexMessage.setPadding(new Insets(15, 5, 10, 0));
+ regexMessage.setTextFill(Color.RED);
+
+ regexFlagsComboBox.getItems().addAll("global", "multiline", "insensitive", "unicode");
+ regexFlagsComboBox.setTitle("Flags");
+ regexFlagsComboBox.getCheckModel().checkIndices(0);
+ }
+
+ @FXML
+ public void handleKeyMatch(KeyEvent keyEvent) {
+ if (keyEvent.getCode().equals(KeyCode.ENTER)) {
+ doMatch();
+ }
+ }
+
+ @FXML
+ public void handleMatch(ActionEvent actionEvent) {
+ doMatch();
+ }
+
+ private void doMatch() {
+ regexMessage.setText("");
+ if (validateInput()) {
+ try {
+ RegexResult result = RegexService.match(regexExpression.getText(), regexTarget.getText(),
+ regexFlagsComboBox.getCheckModel().getCheckedItems());
+ regexResult.setText(result.getMatchSummary());
+ List> l = result.getFullMatchIndexes();
+ if (!l.isEmpty()) {
+ regexTarget.deselect();
+ regexTarget.moveTo(l.get(0).getKey());
+ regexTarget.requestFollowCaret();
+ regexTarget.selectRange(l.get(0).getKey(), l.get(0).getValue());
+ }
+ } catch (Exception e) {
+ regexMessage.setText("Invalid regex");
+ }
+ }
+ }
+
+ private boolean validateInput() {
+ resetBorders();
+ boolean isValid = true;
+ if (regexExpression.getText().isEmpty()) {
+ regexExpression.setBorder(Elements.alertBorder);
+ isValid = false;
+ }
+ if (regexTarget.getText().isEmpty()) {
+ regexTarget.setBorder(Elements.alertBorder);
+ isValid = false;
+ }
+ return isValid;
+ }
+
+ private void resetBorders() {
+ regexExpression.setBorder(Border.EMPTY);
+ regexTarget.setBorder(Elements.codeAreaBorder);
+ }
+
+ @FXML
+ public void handleClear(ActionEvent actionEvent) {
+ resetBorders();
+ regexExpression.setText("");
+ regexTarget.replaceText("");
+ regexResult.setText("");
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java b/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java
new file mode 100644
index 0000000..554aa52
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/controllers/RestAPIController.java
@@ -0,0 +1,26 @@
+package com.github.reugn.devtools.controllers;
+
+import javafx.fxml.FXML;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class RestAPIController extends TabPaneController {
+
+ @FXML
+ private static RestAPIController self;
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ self = this;
+ }
+
+ static RestAPIController instance() {
+ return self;
+ }
+
+ @Override
+ protected String getInnerResource() {
+ return "/views/rest_api_tab.fxml";
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java b/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java
new file mode 100644
index 0000000..f6323b2
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/controllers/RestAPITabController.java
@@ -0,0 +1,124 @@
+package com.github.reugn.devtools.controllers;
+
+import com.github.reugn.devtools.models.RestResponse;
+import com.github.reugn.devtools.services.RestService;
+import com.github.reugn.devtools.utils.Elements;
+import com.github.reugn.devtools.utils.Logger;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.fxml.Initializable;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.layout.Border;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.util.Pair;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.stream.Collectors;
+
+public class RestAPITabController implements Initializable, Logger {
+
+ @FXML
+ private ComboBox methodComboBox;
+ @FXML
+ private TextField uriTextField;
+ @FXML
+ private Button sendButton;
+ @FXML
+ private VBox requestHeadersVBox;
+ @FXML
+ private TextArea requestBodyTextArea;
+ @FXML
+ private TextArea responseBodyTextArea;
+ @FXML
+ private TextArea responseHeadersTextArea;
+ @FXML
+ private Label responseStatusLabel;
+
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+ uriTextField.setPrefWidth(512);
+ HBox.setMargin(methodComboBox, new Insets(10, 5, 10, 5));
+ HBox.setMargin(uriTextField, new Insets(10, 5, 10, 0));
+ HBox.setMargin(sendButton, new Insets(10, 5, 10, 0));
+
+ methodComboBox.getItems().setAll("GET", "POST", "PUT", "DELETE");
+ methodComboBox.setValue("GET");
+
+ requestHeadersVBox.setPadding(new Insets(10));
+ addHeader();
+ }
+
+ @FXML
+ private void handleSend(ActionEvent actionEvent) {
+ clear();
+ if (!validateInput()) return;
+ RestAPIController.instance().innerTabPane.getSelectionModel().getSelectedItem().setText(tabTitle());
+ try {
+ Map headers = requestHeadersVBox.getChildren().stream().map(n -> {
+ List h = ((HBox) n).getChildren();
+ return new Pair<>(((TextField) h.get(0)).getText(), ((TextField) h.get(1)).getText());
+ }).filter(p -> !p.getKey().isEmpty() && !p.getValue().isEmpty())
+ .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
+ RestResponse response = RestService.request(methodComboBox.getValue(),
+ uriTextField.getText(),
+ headers,
+ requestBodyTextArea.getText());
+ responseBodyTextArea.setText(response.getBody());
+ responseHeadersTextArea.setText(response.getHeaders());
+ responseStatusLabel.setText("STATUS: " + response.getStatus() + ", TIME: " + response.getTime() + "ms");
+ } catch (Exception e) {
+ responseStatusLabel.setText(e.getClass().getName() + ": " + e.getMessage());
+ }
+ }
+
+ private void clear() {
+ responseStatusLabel.setText("");
+ responseBodyTextArea.setText("");
+ responseHeadersTextArea.setText("");
+ }
+
+ private boolean validateInput() {
+ uriTextField.setBorder(Border.EMPTY);
+ if (uriTextField.getText().isEmpty()) {
+ uriTextField.setBorder(Elements.alertBorder);
+ return false;
+ }
+ return true;
+ }
+
+ private static int tabTitleLength = 12;
+
+ private String tabTitle() {
+ String pUrl = uriTextField.getText().length() > tabTitleLength
+ ? uriTextField.getText().substring(0, tabTitleLength)
+ : uriTextField.getText();
+ return methodComboBox.getValue() + " " + pUrl;
+ }
+
+ @FXML
+ private void handleAddHeader(ActionEvent actionEvent) {
+ addHeader();
+ }
+
+ private void addHeader() {
+ try {
+ Node n = FXMLLoader.load(this.getClass().getResource("/views/rest_api_header.fxml"));
+ requestHeadersVBox.getChildren().add(n);
+ } catch (Exception e) {
+ }
+ }
+
+ @FXML
+ private void handleRemoveHeader(ActionEvent actionEvent) {
+ int hsize = requestHeadersVBox.getChildren().size();
+ if (hsize > 1)
+ requestHeadersVBox.getChildren().remove(hsize - 1);
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java b/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java
new file mode 100644
index 0000000..1c4851f
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/controllers/TabPaneController.java
@@ -0,0 +1,35 @@
+package com.github.reugn.devtools.controllers;
+
+import com.github.reugn.devtools.utils.Logger;
+import javafx.event.Event;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.fxml.Initializable;
+import javafx.scene.control.Tab;
+import javafx.scene.control.TabPane;
+
+abstract public class TabPaneController implements Initializable, Logger {
+
+ @FXML
+ protected Tab addNewTab;
+ @FXML
+ protected TabPane innerTabPane;
+
+ protected abstract String getInnerResource();
+
+ @FXML
+ protected void handleNewTab(Event event) {
+ if (addNewTab.isSelected()) {
+ int selected = innerTabPane.getSelectionModel().getSelectedIndex();
+ int n = innerTabPane.getTabs().size();
+ Tab plus = innerTabPane.getTabs().remove(n - 1);
+ Tab newTab = new Tab("New");
+ try {
+ newTab.setContent(FXMLLoader.load(this.getClass().getResource(getInnerResource())));
+ } catch (Exception e) {
+ }
+ innerTabPane.getTabs().addAll(newTab, plus);
+ innerTabPane.getSelectionModel().select(selected);
+ }
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/models/RegexResult.java b/src/main/java/com/github/reugn/devtools/models/RegexResult.java
new file mode 100644
index 0000000..5a23828
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/models/RegexResult.java
@@ -0,0 +1,23 @@
+package com.github.reugn.devtools.models;
+
+import javafx.util.Pair;
+
+import java.util.List;
+
+public class RegexResult {
+ private String matchSummary;
+ private List> fullMatchIndexes;
+
+ public RegexResult(String matchSummary, List> fullMatchIndexes) {
+ this.matchSummary = matchSummary;
+ this.fullMatchIndexes = fullMatchIndexes;
+ }
+
+ public String getMatchSummary() {
+ return matchSummary;
+ }
+
+ public List> getFullMatchIndexes() {
+ return fullMatchIndexes;
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/models/RestResponse.java b/src/main/java/com/github/reugn/devtools/models/RestResponse.java
new file mode 100644
index 0000000..9f0d712
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/models/RestResponse.java
@@ -0,0 +1,31 @@
+package com.github.reugn.devtools.models;
+
+public class RestResponse {
+ private int status;
+ private String body;
+ private String headers;
+ private long time;
+
+ public RestResponse(int status, String body, String headers, long time) {
+ this.status = status;
+ this.body = body;
+ this.headers = headers;
+ this.time = time;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public String getHeaders() {
+ return headers;
+ }
+
+ public long getTime() {
+ return time;
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/services/EpochService.java b/src/main/java/com/github/reugn/devtools/services/EpochService.java
index 40dcb76..e333960 100644
--- a/src/main/java/com/github/reugn/devtools/services/EpochService.java
+++ b/src/main/java/com/github/reugn/devtools/services/EpochService.java
@@ -1,13 +1,14 @@
package com.github.reugn.devtools.services;
+import com.github.reugn.devtools.utils.Elements;
import javafx.scene.control.TextField;
-import javafx.scene.layout.*;
-import javafx.scene.paint.Color;
import java.security.InvalidParameterException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
public class EpochService {
@@ -28,20 +29,30 @@ public static String toHumanEpoch(LocalDateTime dt) {
.format(dt.atZone(ZoneId.systemDefault()));
String formattedUTC = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.format(dt.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC));
- return formatted + "\n" + formattedUTC;
+ return "Local Time:\n" + formatted + "\nGMT:\n" + formattedUTC;
}
- public static String toTsEpoch(int year, int month, int dayOfMonth, int hour, int minute, int second) {
+ public static String toTsEpoch(int year, int month, int dayOfMonth, int hour, int minute, int second,
+ String timeZone) {
StringBuilder buff = new StringBuilder();
- ZoneOffset offset = OffsetDateTime.now().getOffset();
+ ZoneOffset offset = OffsetDateTime.now(ZoneId.of(timeZone)).getOffset();
LocalDateTime dt = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second);
buff.append("Epoch timestamp: ");
buff.append(dt.toInstant(offset).getEpochSecond()).append("\n");
buff.append("Timestamp in milliseconds: ");
buff.append(dt.toInstant(offset).toEpochMilli());
+ buff.append("\n");
+ buff.append("Time Zone: ").append(displayTimeZone(TimeZone.getTimeZone(timeZone)));
return buff.toString();
}
+ private static String displayTimeZone(TimeZone tz) {
+ long hours = TimeUnit.MILLISECONDS.toHours(tz.getRawOffset());
+ long minutes = Math.abs(TimeUnit.MILLISECONDS.toMinutes(tz.getRawOffset()) - TimeUnit.HOURS.toMinutes(hours));
+ return hours > 0 ? String.format("(GMT+%d:%02d) %s", hours, minutes, tz.getID())
+ : String.format("(GMT%d:%02d) %s", hours, minutes, tz.getID());
+ }
+
public static int validate(TextField f, int min, int max) {
int intVal;
try {
@@ -50,8 +61,7 @@ public static int validate(TextField f, int min, int max) {
throw new InvalidParameterException("Invalid input: " + f.getText());
}
} catch (Exception e) {
- f.setBorder(new Border(new BorderStroke(Color.RED,
- BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT)));
+ f.setBorder(Elements.alertBorder);
throw e;
}
return intVal;
diff --git a/src/main/java/com/github/reugn/devtools/services/RegexService.java b/src/main/java/com/github/reugn/devtools/services/RegexService.java
new file mode 100644
index 0000000..ce531c9
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/services/RegexService.java
@@ -0,0 +1,43 @@
+package com.github.reugn.devtools.services;
+
+import com.github.reugn.devtools.models.RegexResult;
+import javafx.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexService {
+
+ private RegexService() {
+ }
+
+ public static RegexResult match(String regex, String target, List flagList) {
+ Pattern pattern = Pattern.compile(regex, calculateFlags(flagList));
+ Matcher matcher = pattern.matcher(target);
+ StringBuilder builder = new StringBuilder();
+ boolean isGlobal = flagList.contains("global");
+ ArrayList> fullMatch = new ArrayList<>();
+ while (matcher.find()) {
+ for (int i = 0; i <= matcher.groupCount(); i++) {
+ builder.append(i)
+ .append(": ")
+ .append(target, matcher.start(i), matcher.end(i))
+ .append("\n");
+ }
+ builder.append("\n");
+ fullMatch.add(new Pair<>(matcher.start(0), matcher.end(0)));
+ if (!isGlobal) break;
+ }
+ return new RegexResult(builder.toString(), fullMatch);
+ }
+
+ private static int calculateFlags(List flagList) {
+ int flags = 0;
+ if (flagList.contains("multiline")) flags |= Pattern.MULTILINE;
+ if (flagList.contains("insensitive")) flags |= Pattern.CASE_INSENSITIVE;
+ if (flagList.contains("unicode")) flags |= Pattern.UNICODE_CASE;
+ return flags;
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/services/RestService.java b/src/main/java/com/github/reugn/devtools/services/RestService.java
new file mode 100644
index 0000000..f42caab
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/services/RestService.java
@@ -0,0 +1,93 @@
+package com.github.reugn.devtools.services;
+
+import com.github.reugn.devtools.models.RestResponse;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class RestService {
+
+ private RestService() {
+ }
+
+ private static int defaultConnectTimeout = 60000;
+
+ public static RestResponse request(String requestMethod, String uri, Map headers,
+ String body) throws Exception {
+ HttpURLConnection conn = (HttpURLConnection) buildURL(uri).openConnection();
+ conn.setRequestMethod(requestMethod);
+ for (Map.Entry h : headers.entrySet()) {
+ conn.setRequestProperty(h.getKey(), h.getValue());
+ }
+ conn.setConnectTimeout(defaultConnectTimeout);
+ conn.setInstanceFollowRedirects(false);
+
+ String responseBody = "";
+ int status;
+ long t1 = System.currentTimeMillis();
+ if (!body.isEmpty()) {
+ conn.setDoOutput(true);
+ setRequestBody(conn.getOutputStream(), body);
+ }
+ try {
+ status = conn.getResponseCode();
+ responseBody = getResponseBody(conn.getInputStream());
+ } catch (IOException e) {
+ status = conn.getResponseCode();
+ responseBody = getResponseBody(conn.getErrorStream());
+ }
+ long t2 = System.currentTimeMillis() - t1;
+
+ String responseHeaders = getResponseHeaders(conn.getHeaderFields());
+ conn.disconnect();
+ return new RestResponse(status, responseBody, responseHeaders, t2);
+ }
+
+ private static void setRequestBody(OutputStream os, String body) throws IOException {
+ OutputStreamWriter outStreamWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
+ outStreamWriter.write(body);
+ outStreamWriter.flush();
+ outStreamWriter.close();
+ os.close();
+ }
+
+ private static String getResponseBody(InputStream is) throws IOException {
+ BufferedReader in = new BufferedReader(new InputStreamReader(is));
+ String line;
+ StringBuilder buff = new StringBuilder();
+ while ((line = in.readLine()) != null) {
+ buff.append(line);
+ }
+ in.close();
+ return buff.toString();
+ }
+
+ private static String getResponseHeaders(Map> fields) {
+ return fields.entrySet().stream()
+ .map(e -> {
+ if (e.getKey() == null)
+ return String.join(", ", e.getValue());
+ else
+ return e.getKey() + ": " + String.join(", ", e.getValue());
+ })
+ .collect(Collectors.joining("\n"));
+ }
+
+ private static URL buildURL(String uri) throws MalformedURLException {
+ URL url;
+ try {
+ url = new URL(uri);
+ } catch (MalformedURLException e) {
+ if (uri.startsWith("http"))
+ throw e;
+ return buildURL("http://" + uri);
+ }
+ return url;
+ }
+}
diff --git a/src/main/java/com/github/reugn/devtools/utils/Elements.java b/src/main/java/com/github/reugn/devtools/utils/Elements.java
new file mode 100644
index 0000000..df4c047
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/utils/Elements.java
@@ -0,0 +1,13 @@
+package com.github.reugn.devtools.utils;
+
+import javafx.scene.layout.*;
+import javafx.scene.paint.Color;
+
+public class Elements {
+
+ public static Border alertBorder = new Border(new BorderStroke(Color.rgb(0x80, 0x00, 0x00),
+ BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT));
+
+ public static Border codeAreaBorder = new Border(new BorderStroke(Color.rgb(0xb8, 0xb8, 0xb8),
+ BorderStrokeStyle.SOLID, new CornerRadii(3), BorderWidths.DEFAULT));
+}
diff --git a/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java b/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java
new file mode 100644
index 0000000..893632b
--- /dev/null
+++ b/src/main/java/com/github/reugn/devtools/utils/HttpHeadersTextField.java
@@ -0,0 +1,77 @@
+package com.github.reugn.devtools.utils;
+
+import com.google.common.net.HttpHeaders;
+import javafx.geometry.Side;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.CustomMenuItem;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+
+import java.lang.reflect.Field;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class HttpHeadersTextField extends TextField {
+
+ private static final SortedSet entries;
+ private ContextMenu entriesPopup;
+ private final int maxEntries = 10;
+
+ static {
+ entries = new TreeSet<>();
+ Field[] declaredFields = HttpHeaders.class.getDeclaredFields();
+ for (Field field : declaredFields) {
+ if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
+ try {
+ entries.add((String) field.get(null));
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+
+ public HttpHeadersTextField() {
+ super();
+ entriesPopup = new ContextMenu();
+ textProperty().addListener((observableValue, s, s2) -> {
+ if (getText().length() == 0) {
+ entriesPopup.hide();
+ } else {
+ LinkedList searchResult = new LinkedList<>(entries.subSet(getText(),
+ getText() + Character.MAX_VALUE));
+ if (entries.size() > 0) {
+ populatePopup(searchResult);
+ if (!entriesPopup.isShowing()) {
+ entriesPopup.show(HttpHeadersTextField.this, Side.BOTTOM, 0, 0);
+ }
+ } else {
+ entriesPopup.hide();
+ }
+ }
+ });
+ focusedProperty().addListener((observableValue, aBoolean, aBoolean2) -> entriesPopup.hide());
+ }
+
+ public SortedSet getEntries() {
+ return entries;
+ }
+
+ private void populatePopup(List searchResult) {
+ List menuItems = new LinkedList<>();
+ int count = Math.min(searchResult.size(), maxEntries);
+ for (int i = 0; i < count; i++) {
+ final String result = searchResult.get(i);
+ Label entryLabel = new Label(result);
+ CustomMenuItem item = new CustomMenuItem(entryLabel, true);
+ item.setOnAction(actionEvent -> {
+ setText(result);
+ entriesPopup.hide();
+ });
+ menuItems.add(item);
+ }
+ entriesPopup.getItems().clear();
+ entriesPopup.getItems().addAll(menuItems);
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/css/main.css b/src/main/resources/css/main.css
index 1ebba25..7ba06cc 100644
--- a/src/main/resources/css/main.css
+++ b/src/main/resources/css/main.css
@@ -5,20 +5,43 @@
Label.uc {
-fx-font-size: 18px;
-fx-font-style: italic;
+ -fx-font-weight: bold;
-fx-text-fill: grey;
- -fx-text-alignment: center;
- -fx-fill-width: true;
}
.row {
-fx-padding: 5 5 5 5;
}
+.regexField {
+ -fx-text-fill: #001a4d;
+ -fx-font-weight: bold;
+}
+
+CodeArea {
+ -fx-border-color: #b8b8b8;
+ -fx-background-color: white;
+ -fx-background-radius: 3px;
+ -fx-border-radius: 3px;
+ -fx-padding: 5;
+}
+
+Label.info {
+ -fx-font-size: 14px;
+ -fx-text-fill: #001a4d;
+ -fx-font-style: italic;
+ -fx-padding: 5 5 5 5;
+}
+
.resultLabel {
-fx-font-size: 14px;
-fx-text-fill: grey;
}
+TabPane.inner {
+ -fx-border-color: #d3d3d3;
+}
+
Button.refresh {
-fx-graphic: url('/images/icons8-refresh-16.png');
}
diff --git a/src/main/resources/views/epoch.fxml b/src/main/resources/views/epoch.fxml
index ea4b084..79cf2bb 100644
--- a/src/main/resources/views/epoch.fxml
+++ b/src/main/resources/views/epoch.fxml
@@ -35,6 +35,7 @@
+
@@ -46,6 +47,8 @@
GridPane.rowIndex="0">
Human date to Timestamp
+
diff --git a/src/main/resources/views/json.fxml b/src/main/resources/views/json.fxml
index 535afa1..9fe2c97 100644
--- a/src/main/resources/views/json.fxml
+++ b/src/main/resources/views/json.fxml
@@ -1,47 +1,19 @@
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/views/json_tab.fxml b/src/main/resources/views/json_tab.fxml
new file mode 100644
index 0000000..4c45bce
--- /dev/null
+++ b/src/main/resources/views/json_tab.fxml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/views/main.fxml b/src/main/resources/views/main.fxml
index 97af7c3..35635c1 100644
--- a/src/main/resources/views/main.fxml
+++ b/src/main/resources/views/main.fxml
@@ -34,18 +34,24 @@
-
+
-
+
-
+
-
+
+
+
+
+
+
+
diff --git a/src/main/resources/views/regex.fxml b/src/main/resources/views/regex.fxml
new file mode 100644
index 0000000..60e1981
--- /dev/null
+++ b/src/main/resources/views/regex.fxml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/views/rest_api.fxml b/src/main/resources/views/rest_api.fxml
new file mode 100644
index 0000000..1afd52d
--- /dev/null
+++ b/src/main/resources/views/rest_api.fxml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/views/rest_api_header.fxml b/src/main/resources/views/rest_api_header.fxml
new file mode 100644
index 0000000..9f5fbf5
--- /dev/null
+++ b/src/main/resources/views/rest_api_header.fxml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/views/rest_api_tab.fxml b/src/main/resources/views/rest_api_tab.fxml
new file mode 100644
index 0000000..c0fe92a
--- /dev/null
+++ b/src/main/resources/views/rest_api_tab.fxml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file