/* * SonarQube Java * Copyright (C) 2012-2016 SonarSource SA * mailto:contact AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.java.viewer; import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.Application; import javafx.geometry.Orientation; import javafx.scene.Scene; import javafx.scene.control.SplitPane; import javafx.scene.control.TextArea; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; import javafx.util.Duration; import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleSpans; import org.fxmisc.richtext.StyleSpansBuilder; import org.sonar.java.ast.api.JavaKeyword; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Viewer extends Application { private static final String DEFAULT_SOURCE_CODE = "/viewer/default.java"; private final CodeArea codeArea = new CodeArea(); final TextArea textArea = new TextArea(); final WebView webView = new WebView(); private static final String KEYWORD_PATTERN = "\\b(" + String.join("|", javaKeywords()) + ")\\b"; private static final String PAREN_PATTERN = "\\(|\\)"; private static final String BRACE_PATTERN = "\\{|\\}"; private static final String BRACKET_PATTERN = "\\[|\\]"; private static final String SEMICOLON_PATTERN = "\\;"; private static final String STRING_PATTERN = "\"([^\"\\\\]|\\\\.)*\""; private static final String COMMENT_PATTERN = "//[^\n]*" + "|" + "/\\*(.|\\R)*?\\*/"; private static final String[] RECOGNIZED_SYNTAX = {"KEYWORD", "PAREN", "BRACE", "BRACKET", "SEMICOLON", "STRING", "COMMENT"}; private static final Pattern PATTERN = Pattern.compile( "(?<KEYWORD>" + KEYWORD_PATTERN + ")" + "|(?<PAREN>" + PAREN_PATTERN + ")" + "|(?<BRACE>" + BRACE_PATTERN + ")" + "|(?<BRACKET>" + BRACKET_PATTERN + ")" + "|(?<SEMICOLON>" + SEMICOLON_PATTERN + ")" + "|(?<STRING>" + STRING_PATTERN + ")" + "|(?<COMMENT>" + COMMENT_PATTERN + ")"); private String lastAnalysed = ""; public static void main(String[] args) { launch(args); } private static String defaultFileContent() { String result; try { Path path = Paths.get(Viewer.class.getResource(DEFAULT_SOURCE_CODE).toURI()); result = new String(Files.readAllBytes(path)); } catch (URISyntaxException | IOException e) { e.printStackTrace(); result = "// Unable to read default file:\\n\\n"; } return result; } private static String[] javaKeywords() { return Arrays.stream(JavaKeyword.values()) .map(JavaKeyword::getValue) .toArray(String[]::new); } @Override public void start(Stage primaryStage) throws Exception { setupLayout(); primaryStage.setTitle("SonarQube Java Analyzer - Viewer"); codeArea.insertText(0, defaultFileContent()); SplitPane verticalSplitPane = new SplitPane(); verticalSplitPane.setOrientation(Orientation.VERTICAL); verticalSplitPane.getItems().addAll(codeArea, textArea); SplitPane splitPane = new SplitPane(); splitPane.getItems().addAll(verticalSplitPane, webView); WebEngine webEngine = webView.getEngine(); webEngine.load(Viewer.class.getResource("/viewer/viewer.html").toExternalForm()); webEngine.setUserStyleSheetLocation(Viewer.class.getResource("/viewer/viewer.css").toExternalForm()); webView.setContextMenuEnabled(false); primaryStage.setScene(new Scene(splitPane, 1200, 800)); primaryStage.show(); Timeline timeline = new Timeline(new KeyFrame(Duration.millis(500), ae -> checkForUpdate())); timeline.setCycleCount(Animation.INDEFINITE); timeline.play(); } private void checkForUpdate() { String text = codeArea.getText(); if (!text.equals(lastAnalysed)) { lastAnalysed = text; analyse(text); } } protected void analyse(String source){ // new CFGViewer(this).analyse(source); new EGViewer(this).analyse(source); // new TreeViewer(this).analyse(source); } private void setupLayout() { codeArea.getStylesheets().add(Viewer.class.getResource("/viewer/java-keywords.css").toExternalForm()); codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); codeArea.richChanges() .filter(ch -> !ch.getInserted().equals(ch.getRemoved())) .subscribe(change -> { codeArea.setStyleSpans(0, computeHighlighting(codeArea.getText())); }); textArea.setStyle("-fx-font-family: monospace;"); } private static StyleSpans<Collection<String>> computeHighlighting(String text) { Matcher matcher = PATTERN.matcher(text); int lastKwEnd = 0; StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); while (matcher.find()) { String styleClass = null; for (String syntax : RECOGNIZED_SYNTAX) { if (matcher.group(syntax) != null) { styleClass = syntax.toLowerCase(); break; } } 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(); } }