diff --git a/src/main/java/lu/jpt/csparqlproject/SimulationContext.java b/src/main/java/lu/jpt/csparqlproject/SimulationContext.java index 1f337f2..2f8c310 100644 --- a/src/main/java/lu/jpt/csparqlproject/SimulationContext.java +++ b/src/main/java/lu/jpt/csparqlproject/SimulationContext.java @@ -2,6 +2,7 @@ package lu.jpt.csparqlproject; import java.util.ArrayList; import java.util.Collection; +import java.util.Observer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,7 +11,8 @@ import eu.larkc.csparql.cep.api.RdfStream; import eu.larkc.csparql.core.engine.CsparqlEngine; import eu.larkc.csparql.core.engine.CsparqlEngineImpl; import eu.larkc.csparql.core.engine.CsparqlQueryResultProxy; -import lu.jpt.csparqlproject.gui.TextObserverWindow; +import lu.jpt.csparqlproject.gui.FancyTextObserverWindow; +import lu.jpt.csparqlproject.gui.RawTextObserverWindow; import lu.jpt.csparqlproject.rentacar.RentACarSimulation; import lu.jpt.csparqlproject.util.CsparqlQueryHelper; @@ -167,12 +169,29 @@ public class SimulationContext { SimulationContext.logger.error(query); } this.queryResultProxies.add(resultProxy); - resultProxy.addObserver(new TextObserverWindow("[ResultProxy] " + queryName)); + Observer resultObserver = this.createResultObserverWindow(queryName); + resultProxy.addObserver(resultObserver); } // Setup complete, ready to run. SimulationContext.logger.info("Simulation set up and ready to go!"); this.currentState = SimulationState.INITIALIZED; } + + /** + * Builds a query result observer depending on the given queryName. + * This is useful if a querys result set is larger than usual. + * @param queryName + * @return Observer for query result + */ + private Observer createResultObserverWindow(String queryName) { + Observer observer = null; + if(false && queryName.equals("getEvents")) { + observer = new RawTextObserverWindow("[ResultProxy] " + queryName); + } else { + observer = new FancyTextObserverWindow("[ResultProxy] " + queryName); + } + return observer; + } } diff --git a/src/main/java/lu/jpt/csparqlproject/gui/TextObserverWindow.java b/src/main/java/lu/jpt/csparqlproject/gui/FancyTextObserverWindow.java similarity index 83% rename from src/main/java/lu/jpt/csparqlproject/gui/TextObserverWindow.java rename to src/main/java/lu/jpt/csparqlproject/gui/FancyTextObserverWindow.java index bea6f53..14b1167 100644 --- a/src/main/java/lu/jpt/csparqlproject/gui/TextObserverWindow.java +++ b/src/main/java/lu/jpt/csparqlproject/gui/FancyTextObserverWindow.java @@ -22,11 +22,15 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import eu.larkc.csparql.cep.api.RdfQuadruple; import eu.larkc.csparql.common.RDFTable; import eu.larkc.csparql.common.RDFTuple; @@ -37,8 +41,10 @@ import lu.jpt.csparqlproject.Main; * 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 { +public class FancyTextObserverWindow extends JFrame implements Observer { + public static Logger logger = LoggerFactory.getLogger(FancyTextObserverWindow.class); + private static final long serialVersionUID = 1L; private static final int TEXTPANE_WIDTH = 800; private static final int TEXTPANE_HEIGHT = 240; @@ -51,7 +57,7 @@ public class TextObserverWindow extends JFrame implements Observer { * Constructor * @param title of the window */ - public TextObserverWindow(String title) { + public FancyTextObserverWindow(String title) { this.initWindow(title); } @@ -131,7 +137,7 @@ public class TextObserverWindow extends JFrame implements Observer { */ private void initWindow(String title) { // Little hack to be able to access myself from within ActionListeners - final TextObserverWindow textObserverWindow = this; + final FancyTextObserverWindow textObserverWindow = this; this.setTitle(title); this.setResizable(true); @@ -171,7 +177,7 @@ public class TextObserverWindow extends JFrame implements Observer { // Add a ScrollPane JScrollPane scrollPane = new JScrollPane(this.textPane); - scrollPane.setPreferredSize(new Dimension(TextObserverWindow.TEXTPANE_WIDTH, TextObserverWindow.TEXTPANE_HEIGHT)); + scrollPane.setPreferredSize(new Dimension(FancyTextObserverWindow.TEXTPANE_WIDTH, FancyTextObserverWindow.TEXTPANE_HEIGHT)); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); contentPane.add(scrollPane, BorderLayout.CENTER); @@ -252,20 +258,52 @@ public class TextObserverWindow extends JFrame implements Observer { 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); + 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(this.autoScrollToBottom) { Document textPaneDocument = this.textPane.getDocument(); this.textPane.select(textPaneDocument.getLength(), textPaneDocument.getLength()); - } else { - this.textPane.setCaretPosition(oldCaretPosition); + } + } + + /** + * Helper method to enforce a line limit for the window. + */ + private void enforceLineLimit() { + int MAX_LINES = 50; + String[] lines = this.textPane.getText().split("\n"); + int totalLines = lines.length; + int characters = 0; + if(totalLines > MAX_LINES) { + int linesToRemove = (totalLines - MAX_LINES) - 1; + if(linesToRemove > 5) { + for(int currentLine = 0; currentLine < linesToRemove; currentLine++) { + characters += lines[currentLine].length() + 1; + } + try { + this.textPane.getDocument().remove(0, characters); + } catch (BadLocationException e) { + FancyTextObserverWindow.logger.error(e.toString()); + FancyTextObserverWindow.logger.error(e.getMessage()); + } + } } } + /** * Generic method of Observer interface to allow for use as an observer. * In case no CsparqlQueryResultProxy is being observed, output can still be useful. :-) @@ -274,16 +312,22 @@ public class TextObserverWindow extends JFrame implements Observer { */ @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); + try { + 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); + } + } catch(Exception e) { + // This is to prevent the whole stack from crashing. + FancyTextObserverWindow.logger.error(e.toString()); + FancyTextObserverWindow.logger.error(e.getMessage()); } } diff --git a/src/main/java/lu/jpt/csparqlproject/gui/RawTextObserverWindow.java b/src/main/java/lu/jpt/csparqlproject/gui/RawTextObserverWindow.java new file mode 100644 index 0000000..189a08d --- /dev/null +++ b/src/main/java/lu/jpt/csparqlproject/gui/RawTextObserverWindow.java @@ -0,0 +1,325 @@ +package lu.jpt.csparqlproject.gui; + +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +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.Observable; +import java.util.Observer; +import java.util.StringTokenizer; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.text.Document; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; + +/** + * Multi-purpose text window to allow for easy viewing of RdfStream quadruples, + * various logging or observing query results. + */ +public class RawTextObserverWindow extends JFrame implements Observer { + + public static Logger logger = LoggerFactory.getLogger(RawTextObserverWindow.class); + + private static final long serialVersionUID = 1L; + private static final int TEXTAREA_WIDTH = 800; + private static final int TEXTAREA_HEIGHT = 240; + + private JTextArea textArea; + private volatile boolean autoScrollToBottom = true; + private volatile boolean usePrefixManager = true; + + /** + * Constructor + * @param title of the window + */ + public RawTextObserverWindow(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; + } + + /** + * Set whether or not the PrefixManager shall be used to shorten + * uris in strings to prefixes. + * @param usePrefixManager + */ + public void setUsePrefixManager(boolean usePrefixManager) { + this.usePrefixManager = usePrefixManager; + } + + /** + * Show given text in the window + * @param text + */ + public void showText(String text) { + this.appendToTextPane(text, true); + } + + /** + * Show given token in the window without appending linebreaks + * @param text + */ + public void showToken(String token) { + this.appendToTextPane(token, false); + } + + /** + * Show given RdfQuadruple in window + * @param quad + */ + public void showQuadruple(RdfQuadruple quad) { + String quadruple = this.shortenUriToPrefix(quad.toString()); + this.appendToTextPane(quadruple, true); + } + + /** + * 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 RawTextObserverWindow textObserverWindow = this; + + this.setTitle(title); + this.setResizable(true); + this.setUndecorated(false); + this.setDefaultCloseOperation(EXIT_ON_CLOSE); + + Container contentPane = this.getContentPane(); + contentPane.setLayout(new BorderLayout()); + + // Add a title label for convenience + JLabel titleLabel = new JLabel(title); + Font labelFont = titleLabel.getFont(); + titleLabel.setFont(new Font(labelFont.getName(), Font.PLAIN, 18)); + contentPane.add(titleLabel, BorderLayout.NORTH); + + // Get a TextPane ready - a little hack to make it always scroll horizontally + this.textArea = new JTextArea() { + 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.textArea.setEnabled(true); + this.textArea.setFocusable(false); + this.textArea.setEditable(true); + this.textArea.setAutoscrolls(true); + this.textArea.setMargin(new Insets(5, 5, 5, 5)); + contentPane.add(this.textArea, BorderLayout.CENTER); + + // Add a ScrollPane + JScrollPane scrollPane = new JScrollPane(this.textArea); + scrollPane.setPreferredSize(new Dimension(RawTextObserverWindow.TEXTAREA_WIDTH, RawTextObserverWindow.TEXTAREA_HEIGHT)); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + contentPane.add(scrollPane, BorderLayout.CENTER); + + // Bottom Panel contains control elements + JPanel bottomPanel = new JPanel(); + + // Add a checkbox to toggle autoscroll + JCheckBox autoscrollCheckbox = new JCheckBox(); + autoscrollCheckbox.setText("Automatically scroll to bottom"); + autoscrollCheckbox.setEnabled(true); + autoscrollCheckbox.setSelected(true); + autoscrollCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JCheckBox checkBox = (JCheckBox) e.getSource(); + textObserverWindow.setAutoScrollToBottom(checkBox.isSelected()); + } + }); + bottomPanel.add(autoscrollCheckbox); + + // Add a checkbox to toggle uri shortening using PrefixManager + JCheckBox usePrefixManagerCheckbox = new JCheckBox(); + usePrefixManagerCheckbox.setText("Shorten URIs to prefixes"); + usePrefixManagerCheckbox.setEnabled(true); + usePrefixManagerCheckbox.setSelected(true); + usePrefixManagerCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JCheckBox checkBox = (JCheckBox) e.getSource(); + textObserverWindow.setUsePrefixManager(checkBox.isSelected()); + } + }); + bottomPanel.add(usePrefixManagerCheckbox); + + // Add buttons to increase/decrease font size + JButton increaseFontSizeButton = new JButton("+"); + increaseFontSizeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Font currentFont = textObserverWindow.textArea.getFont(); + textObserverWindow.textArea.setFont( + new Font(currentFont.getName(), currentFont.getStyle(), currentFont.getSize()+1) + ); + } + }); + + bottomPanel.add(increaseFontSizeButton); + JButton decreaseFontSizeButton = new JButton("-"); + decreaseFontSizeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Font currentFont = textObserverWindow.textArea.getFont(); + textObserverWindow.textArea.setFont( + new Font(currentFont.getName(), currentFont.getStyle(), currentFont.getSize()-1) + ); + } + }); + bottomPanel.add(decreaseFontSizeButton); + + // Add bottomPanel to contentPane + contentPane.add(bottomPanel, BorderLayout.SOUTH); + + // Finally show the window + 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) + */ + private void appendToTextPane(String text, boolean addLinebreak) { + if (addLinebreak) text += "\n"; + int length = this.textArea.getDocument().getLength(); + boolean success = false; + do { + try { + this.textArea.setCaretPosition(length); + this.textArea.replaceSelection(text); + success = true; + } catch(Exception e) { + // Yes, i know. :-( + } + } while(!success); + // TODO: Fix behaviour when autoscroll is disabled! + if(this.autoScrollToBottom) { + Document textPaneDocument = this.textArea.getDocument(); + this.textArea.select(textPaneDocument.getLength(), textPaneDocument.getLength()); + } + } + + /** + * 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) { + try { + 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()); + this.showText(observable.toString()); + this.showText(argument.getClass().toString()); + this.showText(argument.toString()); + } + } catch(Exception e) { + // This is to prevent the whole stack from crashing. + RawTextObserverWindow.logger.error(e.toString()); + RawTextObserverWindow.logger.error(e.getMessage()); + } + } + + /** + * Method to actually pretty-print the results of a Csparql query + * @param CsparqlQueryResultProxy result + * @param RDFTable table + */ + private void showCsparqlQueryResult(CsparqlQueryResultProxy result, RDFTable table) { + long currentTime = System.currentTimeMillis(); + Collection tupleElementNames = table.getNames(); + Collection tuples = table.getTuples(); + this.showText("SystemTime=["+currentTime+"], Results=["+tuples.size()+"]"); + // Show names in according color coding + this.showToken("Columns in order: ["); + Iterator elementNamesIterator = tupleElementNames.iterator(); + while(elementNamesIterator.hasNext()) { + String elementName = elementNamesIterator.next(); + if(elementName == "") elementName = "[NO VALUE]"; + if(elementNamesIterator.hasNext()) { + elementName += ", "; + } + this.showToken(elementName); + } + this.showText("]"); + 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); + 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); + } + } + this.showText("End of results ----------------------------------------------------------------- \n"); + } + + /** + * Helper method to shorten strings using the PrefixManager if + * this window uses it + * @param str input string + * @return output string, shortened or not depending on usePrefixManager variable + */ + private String shortenUriToPrefix(String str) { + if(this.usePrefixManager) { + return Main.prefixManager.applyPrefix(str); + } else { + return str; + } + } + +} diff --git a/src/main/java/lu/jpt/csparqlproject/rentacar/RentACarSimulation.java b/src/main/java/lu/jpt/csparqlproject/rentacar/RentACarSimulation.java index a0470bf..11dbb86 100644 --- a/src/main/java/lu/jpt/csparqlproject/rentacar/RentACarSimulation.java +++ b/src/main/java/lu/jpt/csparqlproject/rentacar/RentACarSimulation.java @@ -42,8 +42,8 @@ public class RentACarSimulation implements Runnable { public RentACarSimulation() { this.registerOwnPrefixes(); - int numberOfCars = 5; - int numberOfCustomers = 5; + int numberOfCars = 1; + int numberOfCustomers = 1; // Create a car pool and drivers this.carPool = new CarPool(numberOfCars); this.drivers = new ArrayList(); @@ -163,10 +163,10 @@ public class RentACarSimulation implements Runnable { + " ?e car:speed ?speed . " + " ?e car:motorRPM ?rpm . " + " ?e car:handbrakeEngaged ?handbrake . " - + " ?e car:tirePressure1 ?pressure1 . " - + " ?e car:tirePressure2 ?pressure2 . " - + " ?e car:tirePressure3 ?pressure3 . " - + " ?e car:tirePressure4 ?pressure4 . " + + " ?e car:tirePressure1 ?tirePressure1 . " + + " ?e car:tirePressure2 ?tirePressure2 . " + + " ?e car:tirePressure3 ?tirePressure3 . " + + " ?e car:tirePressure4 ?tirePressure4 . " + "}"; } diff --git a/src/main/java/lu/jpt/csparqlproject/util/WindowLoggingRdfStream.java b/src/main/java/lu/jpt/csparqlproject/util/WindowLoggingRdfStream.java index 0b2621a..2e2ea66 100644 --- a/src/main/java/lu/jpt/csparqlproject/util/WindowLoggingRdfStream.java +++ b/src/main/java/lu/jpt/csparqlproject/util/WindowLoggingRdfStream.java @@ -1,7 +1,7 @@ package lu.jpt.csparqlproject.util; import eu.larkc.csparql.cep.api.RdfQuadruple; -import lu.jpt.csparqlproject.gui.TextObserverWindow; +import lu.jpt.csparqlproject.gui.RawTextObserverWindow; /** * Using this class, RdfStreams can automatically be made visible @@ -9,7 +9,7 @@ import lu.jpt.csparqlproject.gui.TextObserverWindow; */ public class WindowLoggingRdfStream extends LoggableRdfStream { - private TextObserverWindow observerWindow; + private RawTextObserverWindow observerWindow; public WindowLoggingRdfStream(String iri) { super(iri); @@ -17,7 +17,7 @@ public class WindowLoggingRdfStream extends LoggableRdfStream { final WindowLoggingRdfStream me = this; java.awt.EventQueue.invokeLater(new Runnable() { public void run() { - me.observerWindow = new TextObserverWindow("[RdfStream] "+iri); + me.observerWindow = new RawTextObserverWindow("[RdfStream] "+iri); } }); }