Refactor Highlighting and add Formatting frameworks for future Extension API

This commit is contained in:
Rohit Awate 2018-07-30 17:02:46 +05:30
parent 197c06b227
commit e7537e339d
12 changed files with 246 additions and 107 deletions

View file

@ -17,9 +17,10 @@
package com.rohitawate.everest.controllers;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea.HighlightMode;
import com.rohitawate.everest.controllers.codearea.highlighters.HighlighterFactory;
import com.rohitawate.everest.controllers.state.ComposerState;
import com.rohitawate.everest.controllers.state.FieldState;
import com.rohitawate.everest.format.FormatterFactory;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager;
import javafx.fxml.FXML;
@ -71,21 +72,20 @@ public class BodyTabController implements Initializable {
rawInputTypeBox.valueProperty().addListener(change -> {
String type = rawInputTypeBox.getValue();
HighlightMode mode;
switch (type) {
case "JSON":
mode = HighlightMode.JSON;
break;
case "XML":
mode = HighlightMode.XML;
break;
case "HTML":
mode = HighlightMode.HTML;
break;
default:
mode = HighlightMode.PLAIN;
if (type.equals("JSON")) {
try {
rawInputArea.setText(rawInputArea.getText(),
FormatterFactory.getHighlighter(type),
HighlighterFactory.getHighlighter(type));
} catch (IOException e) {
Services.loggingService.logWarning("Response could not be parsed.", e, LocalDateTime.now());
}
rawInputArea.setMode(mode);
return;
}
rawInputArea.setHighlighter(HighlighterFactory.getHighlighter(type));
});
try {
@ -185,25 +185,10 @@ public class BodyTabController implements Initializable {
}
private void setRawTab(ComposerState state) {
HighlightMode mode;
if (state.rawBodyType != null && state.rawBody != null) {
switch (state.rawBodyType) {
case "JSON":
mode = HighlightMode.JSON;
break;
case "XML":
mode = HighlightMode.XML;
break;
case "HTML":
mode = HighlightMode.HTML;
break;
default:
mode = HighlightMode.PLAIN;
}
rawInputArea.setText(state.rawBody, mode);
rawInputArea.setText(state.rawBody, HighlighterFactory.getHighlighter(state.rawBodyType));
} else {
rawInputArea.setText("", HighlightMode.PLAIN);
rawInputArea.setHighlighter(HighlighterFactory.getHighlighter("PLAIN TEXT"));
}
}
}

View file

@ -15,18 +15,17 @@
*/
package com.rohitawate.everest.controllers;
import com.fasterxml.jackson.databind.JsonNode;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import com.jfoenix.controls.JFXSnackbar;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea;
import com.rohitawate.everest.controllers.codearea.EverestCodeArea.HighlightMode;
import com.rohitawate.everest.controllers.codearea.highlighters.HighlighterFactory;
import com.rohitawate.everest.controllers.state.ComposerState;
import com.rohitawate.everest.controllers.state.DashboardState;
import com.rohitawate.everest.controllers.state.FieldState;
import com.rohitawate.everest.exceptions.RedirectException;
import com.rohitawate.everest.exceptions.UnreliableResponseException;
import com.rohitawate.everest.misc.EverestUtilities;
import com.rohitawate.everest.format.FormatterFactory;
import com.rohitawate.everest.misc.Services;
import com.rohitawate.everest.misc.ThemeManager;
import com.rohitawate.everest.models.requests.DELETERequest;
@ -175,26 +174,20 @@ public class DashboardController implements Initializable {
responseTypeBox.valueProperty().addListener(change -> {
String type = responseTypeBox.getValue();
HighlightMode mode;
switch (type) {
case "JSON":
if (type.equals("JSON")) {
try {
JsonNode node = EverestUtilities.jsonMapper.readTree(responseArea.getText());
responseArea.setText(EverestUtilities.jsonMapper.writeValueAsString(node), HighlightMode.JSON);
responseArea.setText(responseArea.getText(),
FormatterFactory.getHighlighter(type),
HighlighterFactory.getHighlighter(type));
} catch (IOException e) {
Services.loggingService.logWarning("Response could not be parsed.", e, LocalDateTime.now());
}
return;
case "XML":
mode = HighlightMode.XML;
break;
case "HTML":
mode = HighlightMode.XML;
break;
default:
mode = HighlightMode.PLAIN;
}
responseArea.setMode(mode);
responseArea.setHighlighter(HighlighterFactory.getHighlighter(type));
});
visualizer = new Visualizer();
@ -444,25 +437,22 @@ public class DashboardController implements Initializable {
visualizer.clear();
try {
String simplifiedContentType;
if (contentType != null) {
// Selects only the part preceding the ';', skipping the character encoding
contentType = contentType.split(";")[0];
switch (contentType.toLowerCase()) {
case "application/json":
responseTypeBox.setValue("JSON");
JsonNode node = EverestUtilities.jsonMapper.readTree(body);
responseArea.setText(EverestUtilities.jsonMapper.writeValueAsString(node), HighlightMode.JSON);
simplifiedContentType = "JSON";
visualizerTab.setDisable(false);
visualizer.populate(node);
visualizer.populate(body);
break;
case "application/xml":
responseTypeBox.setValue("XML");
responseArea.setText(body, HighlightMode.XML);
simplifiedContentType = "XML";
break;
case "text/html":
responseTypeBox.setValue("HTML");
responseArea.setText(body, HighlightMode.HTML);
simplifiedContentType = "HTML";
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
snackbar.show("Open link in browser?", "YES", 5000, e -> {
snackbar.close();
@ -477,19 +467,24 @@ public class DashboardController implements Initializable {
}
break;
default:
responseTypeBox.setValue("PLAIN TEXT");
responseArea.setText(body, HighlightMode.PLAIN);
simplifiedContentType = "PLAIN TEXT";
}
} else {
responseTypeBox.setValue("PLAIN");
responseArea.setText("No body found in the response.", HighlightMode.PLAIN);
simplifiedContentType = "PLAIN TEXT";
}
responseArea.setText(body,
FormatterFactory.getHighlighter(simplifiedContentType),
HighlighterFactory.getHighlighter(simplifiedContentType));
responseTypeBox.setValue(simplifiedContentType);
} catch (Exception e) {
snackbar.show("Response could not be parsed.", 5000);
Services.loggingService.logSevere("Response could not be parsed.", e, LocalDateTime.now());
showLayer(ResponseLayer.ERROR);
String errorMessage = "Response could not be parsed.";
snackbar.show(errorMessage, 5000);
Services.loggingService.logSevere(errorMessage, e, LocalDateTime.now());
errorTitle.setText("Parsing Error");
errorDetails.setText("Everest could not parse the response.");
errorDetails.setText(errorMessage);
showLayer(ResponseLayer.ERROR);
}
}

View file

@ -17,9 +17,11 @@
package com.rohitawate.everest.controllers;
import com.fasterxml.jackson.databind.JsonNode;
import com.rohitawate.everest.misc.EverestUtilities;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -37,8 +39,9 @@ class Visualizer extends ScrollPane {
setFitToWidth(true);
}
void populate(JsonNode node) {
this.populate(new TreeItem<>(), "root", node);
void populate(String body) throws IOException {
JsonNode tree = EverestUtilities.jsonMapper.readTree(body);
this.populate(new TreeItem<>(), "root", tree);
}
private void populate(TreeItem<HBox> rootItem, String rootName, JsonNode root) {

View file

@ -17,63 +17,62 @@
package com.rohitawate.everest.controllers.codearea;
import com.rohitawate.everest.controllers.codearea.highlighters.Highlighter;
import com.rohitawate.everest.controllers.codearea.highlighters.JSONHighlighter;
import com.rohitawate.everest.controllers.codearea.highlighters.PlaintextHighlighter;
import com.rohitawate.everest.controllers.codearea.highlighters.XMLHighlighter;
import com.rohitawate.everest.format.Formatter;
import com.rohitawate.everest.settings.Settings;
import javafx.geometry.Insets;
import org.fxmisc.richtext.CodeArea;
import java.io.IOException;
import java.time.Duration;
public class EverestCodeArea extends CodeArea {
public enum HighlightMode {
JSON, XML, HTML, PLAIN
}
private Highlighter highlighter;
private static JSONHighlighter jsonHighlighter;
private static XMLHighlighter xmlHighlighter;
private static PlaintextHighlighter plaintextHighlighter;
public EverestCodeArea() {
this.getStylesheets().add(getClass().getResource("/css/syntax/Moondust.css").toString());
this.getStyleClass().add("everest-code-area");
this.setWrapText(Settings.editorWrapText);
this.setPadding(new Insets(5));
jsonHighlighter = new JSONHighlighter();
xmlHighlighter = new XMLHighlighter();
plaintextHighlighter = new PlaintextHighlighter();
setMode(HighlightMode.PLAIN);
this.multiPlainChanges()
.successionEnds(Duration.ofMillis(1))
.subscribe(ignore -> this.setStyleSpans(0, highlighter.computeHighlighting(getText())));
.subscribe(ignore -> highlight());
}
public void setMode(HighlightMode mode) {
switch (mode) {
case JSON:
highlighter = jsonHighlighter;
break;
case XML:
case HTML:
highlighter = xmlHighlighter;
break;
default:
highlighter = plaintextHighlighter;
}
// Re-computes the highlighting for the new mode
private void highlight() {
this.setStyleSpans(0, highlighter.computeHighlighting(getText()));
}
public void setText(String text, HighlightMode mode) {
public void setHighlighter(Highlighter highlighter) {
this.highlighter = highlighter;
// Re-computes the highlighting using the new Highlighter
this.highlight();
}
/**
* Sets the text and then computes the highlighting.
*/
public void setText(String text, Highlighter highlighter) {
clear();
appendText(text);
setMode(mode);
setHighlighter(highlighter);
}
/**
* Formats the text with the provided Formatter if it is not null,
* sets the text and then computes the highlighting.
*
* @throws IOException If the formatter fails to format the text given a syntactic error.
*/
public void setText(String text, Formatter formatter, Highlighter highlighter) throws IOException {
clear();
String formattedText = text;
if (formatter != null)
formattedText = formatter.format(text);
appendText(formattedText);
setHighlighter(highlighter);
}
}

View file

@ -20,6 +20,15 @@ import org.fxmisc.richtext.model.StyleSpans;
import java.util.Collection;
/**
* Highlights strings in various data formats.
*/
public interface Highlighter {
/**
* Returns the StyleSpans needed for highlighting the given text which
* can then be used by EverestCodeArea.
*
* @param text The string to highlight
*/
StyleSpans<Collection<String>> computeHighlighting(String text);
}

View file

@ -0,0 +1,47 @@
package com.rohitawate.everest.controllers.codearea.highlighters;
import com.rohitawate.everest.exceptions.DuplicateHighlighterException;
import java.util.HashMap;
/**
* Provides Highlighters for computing syntax highlighting
* of specific data formats.
* <p>
* Everest, by default, comes with Highlighters for JSON, XML (HTML uses the same) and PLAIN TEXT.
*/
public class HighlighterFactory {
private static HashMap<String, Highlighter> highlighters;
static {
highlighters = new HashMap<>();
highlighters.put("JSON", new JSONHighlighter());
XMLHighlighter xmlHighlighter = new XMLHighlighter();
highlighters.put("XML", xmlHighlighter);
highlighters.put("HTML", xmlHighlighter);
highlighters.put("PLAIN TEXT", new PlaintextHighlighter());
}
public static Highlighter getHighlighter(String name) {
return highlighters.get(name);
}
/**
* Provisional method for the future Extension API which will enable
* developers to write and load custom Highlighters.
*
* @param name The display name for the Highlighter.
* @param highlighter The Highlighter object
* @throws DuplicateHighlighterException If a Highlighter is already loaded by Everest with the same name.
*/
public static void addHighlighter(String name, Highlighter highlighter)
throws DuplicateHighlighterException {
if (highlighters.containsKey(name)) {
throw new DuplicateHighlighterException("Highlighter already exists for the following type: " + name);
}
highlighters.put(name, highlighter);
}
}

View file

@ -0,0 +1,7 @@
package com.rohitawate.everest.exceptions;
public class DuplicateFormatterException extends Exception {
public DuplicateFormatterException(String message) {
super(message);
}
}

View file

@ -0,0 +1,7 @@
package com.rohitawate.everest.exceptions;
public class DuplicateHighlighterException extends Exception {
public DuplicateHighlighterException(String message) {
super(message);
}
}

View file

@ -0,0 +1,16 @@
package com.rohitawate.everest.format;
import java.io.IOException;
/**
* Formats strings in various data formats.
*/
public interface Formatter {
/**
* Returns a string formatted appropriate to the data format.
*
* @param unformatted The unformatted string
* @throws IOException If the Formatter fails to format the given string.
*/
String format(String unformatted) throws IOException;
}

View file

@ -0,0 +1,42 @@
package com.rohitawate.everest.format;
import com.rohitawate.everest.exceptions.DuplicateFormatterException;
import com.rohitawate.everest.exceptions.DuplicateHighlighterException;
import java.util.HashMap;
/**
* Provides Formatters for formatting strings of specific data formats.
* <p>
* Everest, by default, comes with a Formatter for JSON.
*/
public class FormatterFactory {
private static HashMap<String, Formatter> formatters;
static {
formatters = new HashMap<>();
formatters.put("JSON", new JSONFormatter());
}
public static Formatter getHighlighter(String type) {
return formatters.get(type);
}
/**
* Provisional method for the future Extension API which will enable
* developers to write and load custom Formatters.
*
* @param name The display name for the Formatter.
* @param formatter The Formatter object.
* @throws DuplicateHighlighterException If a Formatter is already loaded by Everest with the same name.
*/
public static void addFormatter(String name, Formatter formatter)
throws DuplicateFormatterException {
if (formatters.containsKey(name)) {
throw new DuplicateFormatterException("Formatter already exists for the following type: " + name);
}
formatters.put(name, formatter);
}
}

View file

@ -0,0 +1,30 @@
package com.rohitawate.everest.format;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
/**
* Formats JSON strings using Jackson's ObjectMapper.
*/
public class JSONFormatter implements Formatter {
private static ObjectMapper mapper;
JSONFormatter() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
}
@Override
public String format(String unformatted) throws IOException {
JsonNode tree = mapper.readTree(unformatted);
return mapper.writeValueAsString(tree);
}
@Override
public String toString() {
return this.getClass().getCanonicalName();
}
}

View file

@ -50,7 +50,6 @@ public class RequestManagersPool {
GETRequestManager newManager = new GETRequestManager();
getManagers.add(newManager);
System.out.println(getManagers.size());
return newManager;
}