diff --git a/src/main/java/lu/jpt/csparqltest/Main.java b/src/main/java/lu/jpt/csparqltest/Main.java index e2a7007..bea83d6 100644 --- a/src/main/java/lu/jpt/csparqltest/Main.java +++ b/src/main/java/lu/jpt/csparqltest/Main.java @@ -6,21 +6,22 @@ import org.apache.log4j.PropertyConfigurator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.larkc.csparql.common.utils.CsparqlUtils; -import eu.larkc.csparql.common.utils.ReasonerChainingType; import eu.larkc.csparql.core.engine.ConsoleFormatter; import eu.larkc.csparql.core.engine.CsparqlEngine; import eu.larkc.csparql.core.engine.CsparqlEngineImpl; import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy; +import lu.jpt.csparqltest.gui.TextObserverWindow; import lu.jpt.csparqltest.rentacar.RentACarSimulation; -import lu.jpt.csparqltest.util.RandomHelper; import lu.jpt.csparqltest.util.TestStreamGenerator; public class Main { private static Logger logger = LoggerFactory.getLogger(Main.class); + public static TextObserverWindow observerWindow; public static void main(String[] args) { + Main.observerWindow = new TextObserverWindow("Result observer window"); + // Determines the time the engine will be run for final long millisecondsToRun = 50 * 1000; @@ -71,7 +72,7 @@ public class Main { testGeneratorThread.start(); // Now build a query to run - interchangeable - String query = Main.getNOTA(); + String query = Main.getSPO(); //String query = RentACarSimulation.getEventUsingBackgroundKnowledge(); // Create a result proxy by registering the query at the engine @@ -99,6 +100,7 @@ public class Main { // Add ConsoleFormatter as observer so it gets notified of every query result resultProxy.addObserver(new ConsoleFormatter()); + resultProxy.addObserver(Main.observerWindow); // Let it all run for some time try { diff --git a/src/main/java/lu/jpt/csparqltest/gui/TextObserverWindow.java b/src/main/java/lu/jpt/csparqltest/gui/TextObserverWindow.java new file mode 100644 index 0000000..e8bdd9d --- /dev/null +++ b/src/main/java/lu/jpt/csparqltest/gui/TextObserverWindow.java @@ -0,0 +1,251 @@ +package lu.jpt.csparqltest.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Observable; +import java.util.Observer; +import java.util.StringTokenizer; + +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.text.AttributeSet; +import javax.swing.text.Document; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; + +import eu.larkc.csparql.cep.api.RdfQuadruple; +import eu.larkc.csparql.common.RDFTable; +import eu.larkc.csparql.common.RDFTuple; +import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy; + +/** + * Multi-purpose text window to allow for easy viewing of RdfStream quadruples, + * various logging or observing query results. + */ +public class TextObserverWindow extends JFrame implements Observer { + + private static final long serialVersionUID = 1L; + + private JTextPane textPane; + private volatile boolean autoScrollToBottom = true; + + + /** + * Constructor + * @param title of the window + */ + public TextObserverWindow(String title) { + this.initWindow(title); + } + + + /** + * Set whether the window is automatically scrolling to the bottom + * @param autoScrollToBottom + */ + public void setAutoScrollToBottom(boolean autoScrollToBottom) { + this.autoScrollToBottom = autoScrollToBottom; + } + + /** + * Show the given text in the window + * @param text + */ + public void showText(String text) { + this.showText(text, Color.BLACK); + } + + /** + * Show given text in the window using the given color + * @param text + * @param color + */ + public void showText(String text, Color color) { + this.appendToTextPane(text, true, color); + } + + /** + * Show the given token in the window without appending linebreaks + * @param text + */ + public void showToken(String token) { + this.showText(token, Color.BLACK); + } + + /** + * Show given token in the window using the given color without appending linebreaks + * @param text + * @param color + */ + public void showToken(String token, Color color) { + this.appendToTextPane(token, false, color); + } + + /** + * Show given RdfQuadruple in the window + * @param quad + */ + public void showQuadruple(RdfQuadruple quad) { + this.showQuadruple(quad, Color.BLACK); + } + + /** + * Show given RdfQuadruple in window using given color + * @param quad + * @param color + */ + public void showQuadruple(RdfQuadruple quad, Color color) { + this.appendToTextPane(quad.toString(), true, color); + } + + /** + * Internal method to set up the window and show it. + * @param title of the window + */ + private void initWindow(String title) { + // Little hack to be able to access myself from within ActionListeners + final TextObserverWindow textObserverWindow = this; + + this.setTitle(title); + this.setResizable(true); + this.setUndecorated(false); + this.setDefaultCloseOperation(EXIT_ON_CLOSE); + + Container contentPane = this.getContentPane(); + contentPane.setLayout(new BorderLayout()); + + // Get a TextPane ready + this.textPane = new JTextPane(); + this.textPane.setEnabled(true); + this.textPane.setFocusable(false); + this.textPane.setAutoscrolls(true); + this.textPane.setMargin(new Insets(5, 5, 5, 5)); + contentPane.add(this.textPane, BorderLayout.CENTER); + + // Add a ScrollPane + JScrollPane scrollPane = new JScrollPane(this.textPane); + scrollPane.setPreferredSize(new Dimension(800, 500)); + contentPane.add(scrollPane, BorderLayout.CENTER); + + // Add a checkbox to toggle autoscroll + JCheckBox autoscrollCheckbox = new JCheckBox(); + autoscrollCheckbox.setText("Automatically scroll to bottom"); + autoscrollCheckbox.setEnabled(true); + autoscrollCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JCheckBox checkBox = (JCheckBox) e.getSource(); + textObserverWindow.setAutoScrollToBottom(checkBox.isSelected()); + } + }); + contentPane.add(autoscrollCheckbox, BorderLayout.SOUTH); + + this.setVisible(true); + this.pack(); + } + + /** + * Internal method to append text to the textpane + * @param text to show within the window + * @param addLinebreak whether or not to add a linebreak (usually yes is better) + * @param color to display the text in + */ + private void appendToTextPane(String text, boolean addLinebreak, Color color) { + if (addLinebreak) text += "\n"; + StyleContext styleContext = StyleContext.getDefaultStyleContext(); + AttributeSet attributeSet = styleContext.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, color); + attributeSet = styleContext.addAttribute(attributeSet, StyleConstants.FontFamily, "Lucida Console"); + attributeSet = styleContext.addAttribute(attributeSet, StyleConstants.Alignment, StyleConstants.ALIGN_JUSTIFIED); + int oldCaretPosition = this.textPane.getCaretPosition(); + int length = this.textPane.getDocument().getLength(); + this.textPane.setCaretPosition(length); + this.textPane.setCharacterAttributes(attributeSet, false); + this.textPane.replaceSelection(text); + // TODO: Fix behaviour when autoscroll is disabled! + if(this.autoScrollToBottom) { + Document textPaneDocument = this.textPane.getDocument(); + this.textPane.select(textPaneDocument.getLength(), textPaneDocument.getLength()); + } else { + this.textPane.setCaretPosition(oldCaretPosition); + } + } + + /** + * Generic method of Observer interface to allow for use as an observer. + * In case no CsparqlQueryResultProxy is being observed, output can still be useful. :-) + * @param observable the incoming observable object + * @param argument the argument that came with it + */ + @Override + public void update(Observable observable, Object argument) { + if(observable instanceof CsparqlQueryResultProxy) { + CsparqlQueryResultProxy result = (CsparqlQueryResultProxy) observable; + RDFTable table = (RDFTable) argument; + this.showCsparqlQueryResult(result, table); + } else { + // Debug output in case this is not used to observe + this.showText(observable.getClass().toString(), Color.RED); + this.showText(observable.toString(), Color.BLUE); + this.showText(argument.getClass().toString(), Color.RED); + this.showText(argument.toString(), Color.BLACK); + } + } + + /** + * Method to actually print the results of a Csparql query + * @param CsparqlQueryResultProxy result + * @param RDFTable table + */ + private void showCsparqlQueryResult(CsparqlQueryResultProxy result, RDFTable table) { + String queryTupleNames = table.getNames().toString(); + Collection tuples = table.getTuples(); + this.showText(queryTupleNames, Color.BLACK); + for(RDFTuple tuple : tuples) { + // This one is separated with \t. Due to the implementation of RDFTuple, it is + // implossible to access the list of its fields directly. Sorry for the mess. :-( + String tupleString = tuple.toString(); + // Explode tupleString by \t and show each field with alternating color. + StringTokenizer tokenizer = new StringTokenizer(tupleString); + Color[] palette = this.getColorPaletteWithSize(tokenizer.countTokens()); + int tokenIndex = 0; + while(tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken()+"\t"; + this.showToken(token, palette[tokenIndex]); + tokenIndex++; + } + this.showText("\n", Color.BLACK); + } + } + + /** + * Generates a color palette for the given number of colors + * @param sizeOfPalette number of colors + * @return color palette as an array + */ + private Color[] getColorPaletteWithSize(int sizeOfPalette) { + if(sizeOfPalette == 0) { + return new Color[] {Color.BLACK}; + } + Color[] result = new Color[sizeOfPalette]; + float h = 0.0f; + float s = 0.75f; + float b = 0.75f; + for(int i=0; i