001 package nl.cwi.sen1.visbase.rstorecontainer;
002
003 import java.io.File;
004 import java.io.FileInputStream;
005 import java.io.FileNotFoundException;
006 import java.io.InputStream;
007 import java.util.LinkedHashMap;
008 import java.util.List;
009 import java.util.Map;
010
011 import nl.cwi.sen1.gui.Studio;
012 import nl.cwi.sen1.gui.plugin.DefaultStudioPlugin;
013 import nl.cwi.sen1.relationstores.Factory;
014 import nl.cwi.sen1.relationstores.types.RElem;
015 import nl.cwi.sen1.relationstores.types.RStore;
016 import nl.cwi.sen1.relationstores.types.RTuple;
017 import nl.cwi.sen1.relationstores.types.relem.Tuple;
018 import nl.cwi.sen1.visbase.rstorecontainer.datatypes.FactInfoList;
019
020 import org.apache.commons.logging.Log;
021 import org.apache.commons.logging.LogFactory;
022
023 import aterm.ATerm;
024 import aterm.ATermList;
025 import aterm.pure.PureFactory;
026
027 /**
028 * Contains the logic needed to communicate RStore data with other
029 * ToolBus-tools.
030 *
031 * @note Hidden methods are
032 * @c protected instead of
033 * @c private so they can be easily unit-tested from tests in the same package.
034 *
035 * @author Ricardo Lindooren
036 * @author Arend van Beelen (reviewer)
037 * @date 12-02-2007
038 */
039 public class RStoreContainer extends DefaultStudioPlugin implements
040 RStoreContainerTif {
041
042 private RStoreContainerBridge m_bridge;
043
044 private Studio m_metaStudio;
045
046 /**
047 * Holds the loaded/parsed RStores
048 */
049 // private Map<Integer, RStore> m_loadedRStoresMap;
050 private Map<Integer, RStoreTracker> m_loadedRStoresMap;
051
052 /**
053 * Used to track which RStore File's where loaded earlier
054 */
055 private Map<File, Integer> m_earlierLoadedRStoreFilesMap;
056
057 private static PureFactory m_pureFactory;
058
059 private static final Log m_log = LogFactory.getLog(RStoreContainer.class);
060
061 /**
062 * The main method is called by the ToolBus to start the RStoreContainer.
063 *
064 * @param args
065 * The arguments passed by the ToolBus.
066 *
067 * @author Ricardo Lindooren
068 * @author Arend van Beelen (reviewer)
069 * @date 16-02-2007
070 */
071 public static void main(String[] args) {
072 if (m_log.isDebugEnabled()) {
073 if (args != null) {
074 StringBuilder debugMessage = new StringBuilder();
075 debugMessage
076 .append("Starting new RStoreContainer tool with args: ");
077
078 int numArgs = args.length;
079 for (int argNum = 0; argNum < numArgs; argNum++) {
080 debugMessage.append(args[argNum]);
081
082 if (argNum < numArgs - 1) {
083 debugMessage.append(", ");
084 }
085 }
086
087 m_log.debug(debugMessage.toString());
088 } else {
089 m_log.debug("Starting new RStoreContainer tool without args.");
090 }
091 }
092
093 new RStoreContainer(args);
094 }
095
096 /**
097 * Default constructor.
098 *
099 * Does not make a connection with the ToolBus.
100 *
101 * @author Ricardo Lindooren
102 * @author Arend van Beelen (reviewer)
103 * @date 12-02-2007
104 */
105 public RStoreContainer() {
106 super();
107
108 m_log.debug("Default " + RStoreContainer.class.getName()
109 + " constructor called");
110
111 m_loadedRStoresMap = new LinkedHashMap<Integer, RStoreTracker>();
112 m_earlierLoadedRStoreFilesMap = new LinkedHashMap<File, Integer>();
113 }
114
115 /**
116 * Constructor used when started by the ToolBus.
117 *
118 * Initializes connection to the ToolBus with the RStoreContainerBridge.
119 *
120 * @param args
121 * The arguments passed by the ToolBus.
122 *
123 * @author Ricardo Lindooren
124 * @author Arend van Beelen (reviewer)
125 * @date 17-02-2007
126 */
127 protected RStoreContainer(String[] args) {
128 super();
129
130 m_loadedRStoresMap = new LinkedHashMap<Integer, RStoreTracker>();
131 m_earlierLoadedRStoreFilesMap = new LinkedHashMap<File, Integer>();
132
133 m_log.debug("Running " + RStoreContainer.class.getSimpleName());
134
135 try {
136 m_bridge = new RStoreContainerBridge(getPureFactory(), this);
137 m_bridge.init(args);
138 m_bridge.connect();
139 m_bridge.run();
140 } catch (Exception exception) {
141 m_log
142 .fatal("Exception during the initialization of the RStoreContainerBridge, see attached cause (can be ignored during JUnit test when there is no ToolBus process present at the moment): "
143 + exception);
144 }
145 }
146
147 /**
148 * Returns the name to indentify this tool.
149 *
150 * @return The string "rStoreContainer".
151 *
152 * @author Ricardo Lindooren
153 * @author Arend van Beelen (reviewer)
154 * @date 21-02-2007
155 */
156 public String getName() {
157 return "rStoreContainer";
158 }
159
160 /**
161 * Initializes and connects this tool when started from the
162 * Meta-Environment.
163 *
164 * @param metaStudio
165 * Reference to the Meta-Studio to connect to.
166 *
167 * @author Ricardo Lindooren
168 * @author Arend van Beelen (reviewer)
169 * @date 21-02-2007
170 */
171 public void initStudioPlugin(Studio metaStudio) {
172 m_metaStudio = metaStudio;
173
174 m_bridge = new RStoreContainerBridge(m_metaStudio.getATermFactory(),
175 this);
176
177 if (m_metaStudio.getATermFactory() instanceof PureFactory) {
178 m_log
179 .debug("metaStudio.getATermFactory() is an instance of PureFactory. Using this one for the static pureFactory variable.");
180
181 m_pureFactory = (PureFactory) m_metaStudio.getATermFactory();
182 } else {
183 m_log
184 .debug("metaStudio.getATermFactory() isn't an instance of PureFactory");
185 }
186
187 m_metaStudio.connect(getName(), m_bridge);
188 }
189
190 /**
191 * Called by the RStoreContainerInterface ToolBus process to load an RStore
192 * directly from an ATerm
193 *
194 * @param filename
195 * Filename of the RStore file, if it would be written to disk.
196 * @return ATerm containing the generated ID for the RStore in the format
197 * @c snd-value(rc-rstore-loaded(<str filename>,<int ID>)). The
198 * ID is set to -1 if loading fails.
199 *
200 * @author Jurgen Vinju
201 */
202 public ATerm rcLoadRstore(String filename, ATerm rstoreData) {
203 File rStoreFile = new File(filename);
204
205 // try to parse the input file to a RStore
206 RStore parsedRStore = null;
207 try {
208 Factory factory = Factory.getInstance(getPureFactory());
209 parsedRStore = factory.RStoreFromTerm(rstoreData);
210 } catch (Exception exception) {
211 m_log
212 .error(
213 "Unexpected exception while trying to parse the RStore file (see cause): ",
214 exception);
215 }
216
217 int rStoreId = -1;
218 if (parsedRStore != null) {
219 // add parsed RStore to loaded RStores so it can be retrieved later
220 // on
221 rStoreId = registerRStore(rStoreFile, parsedRStore);
222
223 m_log.debug("Registered RStore with id: " + rStoreId);
224
225 } else {
226 m_log.warn("Could not register RStore, returning id: " + rStoreId);
227 }
228
229 ATerm result = getPureFactory().make(
230 "snd-value(rc-rstore-loaded(<str>,<int>))", filename,
231 new Integer(rStoreId));
232 return result;
233
234 }
235
236 /**
237 * Called by the RStoreContainerInterface ToolBus process to load an RStore
238 * file.
239 *
240 * @param filename
241 * Filename of the RStore file to load.
242 * @return ATerm containing the generated ID for the RStore in the format
243 * @c snd-value(rc-rstore-loaded(<str filename>,<int ID>)). The
244 * ID is set to -1 if loading fails.
245 *
246 * @author Ricardo Lindooren
247 * @author Arend van Beelen (reviewer)
248 * @date 2007-02-16
249 */
250 public ATerm rcLoadRstore(String filename) {
251 if (m_log.isDebugEnabled()) {
252 m_log.debug("argument: " + filename);
253 }
254
255 File rStoreFile = new File(filename);
256
257 // try to parse the input file to a RStore
258 RStore parsedRStore = null;
259 try {
260 InputStream inputStream = inputStreamFromFile(rStoreFile);
261 parsedRStore = parseRStore(inputStream);
262 } catch (FileNotFoundException exception) {
263 m_log.error("File not found!");
264 } catch (Exception exception) {
265 m_log
266 .error(
267 "Unexpected exception while trying to parse the RStore file (see cause): ",
268 exception);
269 }
270
271 // check parsed RStore result again for safety
272 int rStoreId = -1;
273 if (parsedRStore != null) {
274
275 // add parsed RStore to loaded RStores so it can be retrieved later
276 // on
277 rStoreId = registerRStore(rStoreFile, parsedRStore);
278
279 m_log.debug("Registered RStore with id: " + rStoreId);
280
281 } else {
282 m_log.warn("Could not register RStore, returning id: " + rStoreId);
283 }
284
285 ATerm result = getPureFactory().make(
286 "snd-value(rc-rstore-loaded(<str>,<int>))", filename,
287 new Integer(rStoreId));
288 return result;
289 }
290
291 /**
292 * Called by the RStoreContainerInterface ToolBus process to load the facts
293 * from an earlier loaded RStore file.
294 *
295 * @param id
296 * ID of the loaded RStore file.
297 * @return ATermList containing the ID's of the facts in the loaded RStore
298 * in the format
299 * @c snd-value(rc-rstore-facts(<list ID's>)). The list will be empty
300 * if the RStore was not loaded.
301 *
302 * @author Ricardo Lindooren
303 * @author Arend van Beelen (reviewer)
304 * @date 2007-02-16
305 */
306 public ATerm rcGetRstoreFacts(int id) {
307 m_log.debug("argument: " + id);
308
309 RStoreTracker earlierLoadedRStoreTracker = m_loadedRStoresMap
310 .get(new Integer(id));
311
312 ATermList factIds = null;
313 if (earlierLoadedRStoreTracker != null) {
314
315 FactInfoList factsList = earlierLoadedRStoreTracker
316 .getFactInfoFromRStore();
317
318 factIds = factsList.toATermList();
319 } else {
320 m_log.warn("RStore didn't exist for ID: " + id
321 + " (returning empty facts list)");
322
323 // create empty list
324 factIds = getPureFactory().makeList();
325 }
326
327 ATerm resultList = getPureFactory().make(
328 "snd-value(rc-rstore-facts(<list>))", factIds);
329 return resultList;
330 }
331
332 /**
333 * Called by the RStoreContainerInterface ToolBus process to load the data
334 * belonging to a fact from an earlier loaded RStore file.
335 *
336 * @param rStoreId
337 * ID of the loaded RStore file.
338 * @param factId
339 * ID of the fact to load.
340 * @return ATerm containing the fact data in the format
341 * @c snd-value(rc-fact-data(<term fact-data>)). A fake empty data set
342 * is returned if the fact does not exist.
343 *
344 * @author Ricardo Lindooren
345 * @author Arend van Beelen (reviewer)
346 * @date 2007-02-20
347 */
348 public ATerm rcGetFactData(int rStoreId, int factId) {
349 m_log.debug("arguments: " + rStoreId + ", " + factId);
350
351 RStoreTracker earlierLoadedRStoreTracker = m_loadedRStoresMap
352 .get(new Integer(rStoreId));
353
354 ATerm factData = null;
355 if (earlierLoadedRStoreTracker != null) {
356
357 RTuple factTuple = earlierLoadedRStoreTracker.getRTuple(factId);
358
359 if (factTuple != null) {
360 factData = factTuple.toTerm();
361 } else {
362 m_log.warn("Fact (RTuple) didn't exist for ID: " + factId);
363 }
364 } else {
365 m_log.warn("RStore didn't exist for ID: " + rStoreId
366 + ". Valid ID's are: " + m_loadedRStoresMap.keySet());
367 }
368
369 if (factData == null) {
370 factData = createDummyFactData();
371 }
372
373 ATerm result = getPureFactory().make("snd-value(rc-fact-data(<term>))",
374 factData);
375 return result;
376 }
377
378 /**
379 * Called by the RStoreContainerInterface ToolBus process to unload an
380 * earlier loaded RStore
381 *
382 * @param id
383 * the ID of the earlier loaded RStore
384 *
385 * @return the message <code>snd-value(rc-rstore-unloaded(<int>))</code>
386 *
387 * @author Ricardo Lindooren
388 * @Date 2007-03-14
389 */
390 public ATerm rcUnloadRstore(int id) {
391 m_log.debug("argument: " + id);
392
393 Integer nrIdentifier = new Integer(id);
394
395 if (m_loadedRStoresMap.containsKey(nrIdentifier)) {
396
397 // Delete corresponding entry from earlier loader RStoreTrackers map
398 m_loadedRStoresMap.remove(nrIdentifier);
399
400 /* Delete corresponding entry from earlier loaded files map */
401
402 Integer storedIdentifier = null;
403 File storedFileReference = null;
404
405 // search for entry
406 for (File file : m_earlierLoadedRStoreFilesMap.keySet()) {
407 storedIdentifier = m_earlierLoadedRStoreFilesMap.get(file);
408
409 // check if we got a RStore(Tracker) and this is the
410 // RStore(Tracker) we are looking for
411 if (storedIdentifier != null
412 && storedIdentifier.equals(nrIdentifier)) {
413 storedFileReference = file;
414 break;
415 }
416 }
417
418 // remove the file reference (do this outside the for-loop)
419 if (storedFileReference != null) {
420
421 m_log.debug("Unloading RStore with identifier, id: "
422 + storedIdentifier + ", file: " + storedFileReference);
423
424 m_earlierLoadedRStoreFilesMap.remove(storedFileReference);
425 }
426 } else {
427 nrIdentifier = new Integer(-1);
428
429 m_log.warn("No earlier loaded RStore for identifier: "
430 + nrIdentifier + ". *Returning -1*");
431 }
432
433 ATerm resultList = getPureFactory().make(
434 "snd-value(rc-rstore-unloaded(<int>))", nrIdentifier);
435
436 return resultList;
437 }
438
439 /**
440 * Called by the RStoreContainerInterface ToolBus process when the
441 * fact-update event is received
442 *
443 * @author Ricardo Lindooren
444 * @Date 2007-03-14
445 */
446 public void recAckEvent(ATerm t0) {
447 m_log.debug("argument: " + t0);
448 }
449
450 /**
451 * Creates dummy fact data which is used when no data can be loaded.
452 *
453 * @return An ATerm containing a tuple with two empty strings.
454 *
455 * @author Arend van Beelen
456 * @date 06-03-2007
457 */
458 public static ATerm createDummyFactData() {
459 Factory factory = Factory.getInstance(getPureFactory());
460
461 RElem emptyString = factory.makeRElem_Str("");
462 Tuple tuple = factory.makeRElem_Tuple(factory.makeRElemElements(
463 emptyString, emptyString));
464 RElem rElem = factory.makeRElem_Set(factory.makeRElemElements(tuple));
465 ATerm factData = rElem.toTerm();
466
467 return factData;
468 }
469
470 /**
471 * Handles terminate message.
472 *
473 * @param message
474 * The received message.
475 *
476 * @author Ricardo Lindooren
477 * @author Arend van Beelen (reviewer)
478 * @date 20-02-2007
479 */
480 public void recTerminate(ATerm message) {
481 m_log.debug("Received terminate message: " + message + ".");
482
483 fireStudioPluginClosed();
484
485 m_bridge = null;
486 m_metaStudio = null;
487 }
488
489 /**
490 * Returns the PureFactory object used by this class.
491 *
492 * This class uses a single static instance of the PureFactory.
493 *
494 * Reason for doing this is the Factory stays alive independently from this
495 * class. The Factory doesn't like it when a new instance is created with a
496 * new PureFactory.
497 *
498 * @return The static PureFactory instance for this class.
499 *
500 * @author Ricardo Lindooren
501 * @author Arend van Beelen (reviewer)
502 * @date 14-02-2007
503 */
504 public static synchronized PureFactory getPureFactory() {
505 if (m_pureFactory == null) {
506 m_log.debug("Created the static PureFactory.");
507
508 m_pureFactory = new PureFactory();
509 }
510 return m_pureFactory;
511 }
512
513 /**
514 * Returns the mapping from ID's to the loaded RStoreTrackers.
515 *
516 * @return The map containing all loaded RStores.
517 *
518 * @author Ricardo Lindooren
519 * @author Arend van Beelen (reviewer)
520 * @date 14-02-2007
521 */
522 public Map<Integer, RStoreTracker> getLoadedRStoreTrackersMap() {
523 return m_loadedRStoresMap;
524 }
525
526 /**
527 * Returns the mapping from RStore filenames to ID's (belonging to the
528 * loaded RStoreTrackers).
529 *
530 * @see #getLoadedRStoreTrackersMap()
531 *
532 * @return The map containing all loaded RStores.
533 *
534 * @author Ricardo Lindooren
535 * @date 2007-03-14
536 */
537 public Map<File, Integer> getLoadedRStoreFilesMap() {
538 return m_earlierLoadedRStoreFilesMap;
539 }
540
541 /**
542 * Creates an InputStream object for the given file.
543 *
544 * This is very generic code, not limited to be used by this class only. It
545 * could be promoted to a static method (in a Helper/Util kind of class for
546 * example).
547 *
548 * @param file
549 * File to create an input stream for.
550 * @return A new FileInputStream object for the input File.
551 *
552 * @throws RuntimeException
553 * if the input stream is null.
554 * @throws FileNotFoundException
555 * if the input File cannot be found.
556 *
557 * @author Ricardo Lindooren
558 * @author Arend van Beelen (reviewer)
559 * @date 12-02-2007
560 */
561 protected InputStream inputStreamFromFile(File file)
562 throws FileNotFoundException {
563 if (file == null) {
564 throw new RuntimeException("Input file should not be null.");
565 }
566
567 InputStream inputStream = new FileInputStream(file);
568 return inputStream;
569 }
570
571 /**
572 * Parses RStore data from an input stream.
573 *
574 * @param inputStream
575 * The input stream to parse.
576 * @return The parsed RStore.
577 *
578 * @throws RuntimeException
579 * if the input stream is null.
580 * @throws RStoreParseException
581 * if parsing of RStore input fails.
582 *
583 * @author Ricardo Lindooren
584 * @author Arend van Beelen (reviewer)
585 * @date 12-02-2007
586 */
587 protected RStore parseRStore(InputStream inputStream)
588 throws RStoreParseException {
589 if (inputStream == null) {
590 throw new RuntimeException("Input stream should not be null.");
591 }
592
593 RStore parsedRStore = null;
594
595 try {
596 Factory factory = Factory.getInstance(getPureFactory());
597
598 parsedRStore = factory.RStoreFromFile(inputStream);
599 } catch (Exception exception) {
600 throw new RStoreParseException(
601 "Parsing of RStore data failed (see cause)", exception);
602 }
603
604 return parsedRStore;
605 }
606
607 /**
608 * Registers a loaded RStore in the RStores map.
609 *
610 * @param rStoreFileReference
611 * the reference to the File that was used to create the RStore
612 * @param rStore
613 * The RStore to register.
614 *
615 * @return The ID assigned to the RStore in the map.
616 *
617 * @throws RuntimeException
618 * if File and/or RStore input is null.
619 *
620 * @author Ricardo Lindooren
621 * @author Arend van Beelen (reviewer)
622 * @date 14-02-2007
623 */
624 protected int registerRStore(File rStoreFileReference, RStore rStore) {
625 if (rStoreFileReference == null) {
626 throw new RuntimeException("File input should not be null");
627 }
628 if (rStore == null) {
629 throw new RuntimeException("RStore input should not be null");
630 }
631
632 // Default is a fake value
633 int result_id = -1;
634
635 // Check if the File was loaded earlier
636 if (m_earlierLoadedRStoreFilesMap.containsKey(rStoreFileReference)) {
637 m_log.debug("RStore file has been loaded earlier: "
638 + rStoreFileReference + ". *Updating if needed*");
639
640 // Get the identifier for this earlier loaded RStore
641 Integer idOfEarlierLoadedRStore = m_earlierLoadedRStoreFilesMap
642 .get(rStoreFileReference);
643
644 // Get the RStore Tracker
645 RStoreTracker rStoreTrackerForEarlierLoadedRStore = m_loadedRStoresMap
646 .get(idOfEarlierLoadedRStore);
647
648 // Update the Tracker with the newly parsed RStore data
649 List<Integer> updatedFactIdsList = rStoreTrackerForEarlierLoadedRStore
650 .update(rStore);
651
652 // Send updates for changed facts (if any)
653 sendFactUpdatedEvents(idOfEarlierLoadedRStore, updatedFactIdsList);
654
655 result_id = idOfEarlierLoadedRStore.intValue();
656 } else {
657 // find a unique ID for the RStore in the map
658 int id = m_loadedRStoresMap.size() + 1;
659 while (m_loadedRStoresMap.get(new Integer(id)) != null) {
660 m_log
661 .warn("Suggested ID for RStore already existed in loadedRStoresMap: "
662 + id);
663 id++;
664 }
665
666 // Create a new RStoreTracker
667 RStoreTracker newRStoreTracker = new RStoreTracker(rStore);
668
669 Integer newNrIdentifier = new Integer(id);
670
671 m_earlierLoadedRStoreFilesMap.put(rStoreFileReference,
672 newNrIdentifier);
673 m_loadedRStoresMap.put(newNrIdentifier, newRStoreTracker);
674
675 result_id = id;
676 }
677
678 return result_id;
679 }
680
681 /**
682 * Sends <code>snd-event(rc-fact-updated(<int>,<int>))</code> messages
683 * (RStore,FactId) over the toolbus
684 *
685 * @param rStoreId
686 * the ID of the RStore to which the updated facts belong
687 * @param updatedFactIds
688 * a list of ID's of updated facts
689 *
690 * @throws RuntimeException
691 * if input is null.
692 *
693 * @author Ricardo Lindooren
694 * @date 2007-03-13
695 */
696 protected void sendFactUpdatedEvents(Integer rStoreId,
697 List<Integer> updatedFactIds) {
698 if (rStoreId == null) {
699 throw new RuntimeException("rStoreId input should not be null");
700 }
701 if (updatedFactIds == null) {
702 throw new RuntimeException(
703 "updatedFactIds input should not be null");
704 }
705
706 for (Integer updatedFactId : updatedFactIds) {
707
708 ATerm updatedFactEvent = getPureFactory().make(
709 "rc-fact-updated(<int>,<int>)", rStoreId, updatedFactId);
710
711 if (m_bridge != null) {
712 m_log.debug("Sending this updatedFact-event as term: "
713 + updatedFactEvent);
714
715 m_bridge.postEvent(updatedFactEvent);
716
717 } else {
718 m_log
719 .warn("There is no bridge, cannot send this updatedFact-event: "
720 + updatedFactEvent);
721 }
722 }
723 }
724
725 /**
726 * Returns the RStore for RStoreId
727 *
728 * @param rStoreId
729 * the ID of the RStore to retrieve
730 *
731 * @author Taeke Kooiker
732 * @date 2007-08-14
733 */
734 public ATerm rcGetRstore(int rStoreId) {
735 RStoreTracker tracker = m_loadedRStoresMap.get(rStoreId);
736 ATerm term = m_metaStudio.getATermFactory().make(
737 "snd-value(rc-rstore(<term>))", tracker.getRStore().toTerm());
738 return term;
739 }
740
741 }