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 }