diff --git a/src/main/java/lu/jpt/csparqlproject/SimulationContext.java b/src/main/java/lu/jpt/csparqlproject/SimulationContext.java index 9b1b290..a7e82bd 100644 --- a/src/main/java/lu/jpt/csparqlproject/SimulationContext.java +++ b/src/main/java/lu/jpt/csparqlproject/SimulationContext.java @@ -15,6 +15,7 @@ import eu.larkc.csparql.core.engine.CsparqlEngineImpl; import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy; import eu.larkc.csparql.core.engine.RDFStreamFormatter; import lu.jpt.csparqlproject.gui.FancyTextObserverWindow; +import lu.jpt.csparqlproject.misc.ReasoningTester; import lu.jpt.csparqlproject.rentacar.RentACarSimulation; import lu.jpt.csparqlproject.util.CsparqlQueryHelper; import lu.jpt.csparqlproject.util.CsparqlQueryHelper.CsparqlQueryInfo; @@ -145,6 +146,8 @@ public class SimulationContext { + "{ GRAPH { :room :isConnectedTo :room2 } }"; this.engine.execUpdateQueryOverDatasource(updateQuery); */ + // DEBUGGING ONLY + ReasoningTester rt = new ReasoningTester(this.engine); // Spawn the whole simulation - this takes care of simulation-specific things simulation = new RentACarSimulation(); // Register all the event streams diff --git a/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTester.java b/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTester.java new file mode 100644 index 0000000..a50dcd9 --- /dev/null +++ b/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTester.java @@ -0,0 +1,81 @@ +package lu.jpt.csparqlproject.misc; + +import java.util.Observable; +import java.util.Observer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.larkc.csparql.cep.api.RdfStream; +import eu.larkc.csparql.common.RDFTable; +import eu.larkc.csparql.common.utils.CsparqlUtils; +import eu.larkc.csparql.common.utils.ReasonerChainingType; +import eu.larkc.csparql.core.engine.CsparqlEngine; +import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy; + +/** + * This ugly component acts as a console to throw data in and see how it got enriched. + * Small helper to be registered as an RdfStream and an Observer for its own + * CsparqlQueryResultProxy in order to observe what triples are being generated + */ +public class ReasoningTester extends RdfStream implements Observer { + + public static Logger logger = LoggerFactory.getLogger(ReasoningTester.class); + + public static String iri = "http://example.org/reasoningTestStr"; + + private CsparqlEngine engine; + private ReasoningTesterWindow reasoningTesterWindow; + + public ReasoningTester(CsparqlEngine engine) { + super(ReasoningTester.iri); + this.engine = engine; + this.reasoningTesterWindow = new ReasoningTesterWindow(this); + this.initialize(); + } + + private void initialize() { + try { + this.engine.registerStream(this); + CsparqlQueryResultProxy resultProxy = this.engine.registerQuery(this.getQuery(), true); + engine.updateReasoner( + resultProxy.getSparqlQueryId(), + CsparqlUtils.fileToString("data/rdfs.rules"), + ReasonerChainingType.FORWARD, + CsparqlUtils.serializeRDFFile("data/carSimulationTBox.rdf") + ); + resultProxy.addObserver(this); + } catch (Exception e) { + ReasoningTester.logger.error(e.toString()); + ReasoningTester.logger.error(e.getStackTrace().toString()); + } + } + + private String getQuery() { + return "REGISTER QUERY ReasoningObserveTest AS " + + "PREFIX rdf: " + + "PREFIX f: " + + "PREFIX xsd: " + + "SELECT ?s ?p ?o " + + "FROM STREAM <"+ReasoningTester.iri+"> [RANGE 2s STEP 1s] " + + "WHERE { " + + " ?s ?p ?o . " + + "}"; + } + + @Override + public void update(Observable o, Object arg) { + CsparqlQueryResultProxy resultProxy = null; + RDFTable rdfTable = null; + if(o instanceof CsparqlQueryResultProxy) { + resultProxy = (CsparqlQueryResultProxy) o; + } + if(arg instanceof RDFTable) { + rdfTable = (RDFTable) arg; + } + if(this.reasoningTesterWindow != null) { + this.reasoningTesterWindow.showCsparqlQueryResult(resultProxy, rdfTable); + } + } + +} diff --git a/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTesterWindow.java b/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTesterWindow.java new file mode 100644 index 0000000..78790f2 --- /dev/null +++ b/src/main/java/lu/jpt/csparqlproject/misc/ReasoningTesterWindow.java @@ -0,0 +1,314 @@ +package lu.jpt.csparqlproject.misc; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Collection; +import java.util.Iterator; +import java.util.StringTokenizer; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +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; +import lu.jpt.csparqlproject.Main; + +public class ReasoningTesterWindow extends JFrame { + + private static final long serialVersionUID = 1L; + + private ReasoningTester reasoningTester; + + private JTextPane textPane; + private JTextField subjectField; + private JTextField predicateField; + private JTextField objectField; + + public ReasoningTesterWindow(ReasoningTester reasoningTester) { + this.reasoningTester = reasoningTester; + this.initialize(); + } + + private void initialize() { + final ReasoningTesterWindow me = this; + Container contentPane = this.getContentPane(); + + this.setTitle("ReasoningTest"); + this.setUndecorated(false); + this.setDefaultCloseOperation(EXIT_ON_CLOSE); + + // Get a TextPane ready - a little hack to make it always scroll horizontally + this.textPane = new JTextPane() { + private static final long serialVersionUID = 1L; + + public boolean getScrollableTracksViewportWidth() { + return false; + } + + public void setSize(Dimension dimension) { + if (dimension.width < getParent().getSize().width) { + dimension.width = getParent().getSize().width; + } + super.setSize(dimension); + } + }; + this.textPane.setEnabled(true); + this.textPane.setFocusable(false); + this.textPane.setEditable(true); + 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, 600)); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + contentPane.add(scrollPane, BorderLayout.CENTER); + + JPanel panel = new JPanel(); + // Create three input fields plus a button, add them to panel + this.subjectField = new JTextField(30); + panel.add(this.subjectField); + this.predicateField = new JTextField(30); + panel.add(this.predicateField); + this.objectField = new JTextField(30); + panel.add(this.objectField); + JButton submitButton = new JButton("submit"); + panel.add(submitButton); + submitButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + RdfQuadruple quad = new RdfQuadruple( + me.subjectField.getText(), + me.predicateField.getText(), + me.objectField.getText(), + System.currentTimeMillis() + ); + me.showQuadruple(quad, Color.BLUE); + me.reasoningTester.put(quad); + me.subjectField.setText(""); + me.predicateField.setText(""); + me.objectField.setText(""); + } + }); + contentPane.add(panel, BorderLayout.SOUTH); + + this.setVisible(true); + this.pack(); + } + + /** + * 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) { + String quadruple = this.shortenUriToPrefix(quad.toString()); + this.appendToTextPane(quadruple, true, color); + } + + /** + * Internal man-in-the-middle method to pass the task to the AWT EventQueue + * @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) { + final ReasoningTesterWindow me = this; + EventQueue.invokeLater(() -> { + me.actuallyAppendTextToTextPane(text, addLinebreak, color); + }); + } + + /** + * 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 actuallyAppendTextToTextPane(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 length = this.textPane.getDocument().getLength(); + boolean success = false; + do { + try { + this.textPane.setCaretPosition(length); + this.textPane.setCharacterAttributes(attributeSet, false); + this.textPane.replaceSelection(text); + success = true; + } catch(Exception e) { + // Yes, i know. :-( + } + } while(!success); + // This is the place to enforce a line limit + this.enforceLineLimit(); + // TODO: Fix behaviour when autoscroll is disabled! + if(true) { + Document textPaneDocument = this.textPane.getDocument(); + this.textPane.select(textPaneDocument.getLength(), textPaneDocument.getLength()); + } + } + + /** + * Helper method to enforce a line limit for the window. + */ + private void enforceLineLimit() { + int MAX_LINES = 800; + String[] lines = this.textPane.getText().split("\n"); + int totalLines = lines.length; + if(totalLines > MAX_LINES) { + if(true) { + try { + this.textPane.getDocument().remove(0, this.textPane.getDocument().getLength()); + } catch (BadLocationException e) { + // This has to work. :-/ + } + } + } + } + + /** + * Method to actually pretty-print the results of a Csparql query + * @param CsparqlQueryResultProxy result + * @param RDFTable table + */ + public void showCsparqlQueryResult(CsparqlQueryResultProxy result, RDFTable table) { + long currentTime = System.currentTimeMillis(); + Collection tupleElementNames = table.getNames(); + Collection tuples = table.getTuples(); + Color[] palette = this.getColorPaletteWithSize(table.getNames().size()); + this.showText("SystemTime=["+currentTime+"], Results=["+tuples.size()+"]", Color.BLACK); + // Show names in according color coding + this.showToken("Columns in order: [", Color.BLACK); + Iterator elementNamesIterator = tupleElementNames.iterator(); + int nameIndex = 0; + while(elementNamesIterator.hasNext()) { + String elementName = elementNamesIterator.next(); + if(elementName == "") elementName = "[NO VALUE]"; + if(elementNamesIterator.hasNext()) { + elementName += ", "; + } + this.showToken(elementName, palette[nameIndex]); + nameIndex++; + } + this.showText("]", Color.BLACK); + for(RDFTuple tuple : tuples) { + // This one is separated with \t. Due to the implementation of RDFTuple, it is + // impossible 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); + int tokenIndex = 0; + while(tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + token = this.shortenUriToPrefix(token); + if(token == "") token = "[NO VALUE]"; + if(tokenizer.hasMoreTokens()) { + token += "\t"; + } else { + token += "\n"; + } + this.showToken(token, palette[tokenIndex]); + tokenIndex++; + } + } + this.showText("End of results ----------------------------------------------------------------- \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.8f; + float b = 0.65f; + for(int i=0; i