001    package jigcell.compare.impl;
002    
003    import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
004    import EDU.oswego.cs.dl.util.concurrent.Sync;
005    import bsh.Capabilities;
006    import java.beans.Encoder;
007    import java.beans.PersistenceDelegate;
008    import java.beans.PropertyChangeEvent;
009    import java.beans.PropertyChangeListener;
010    import java.beans.PropertyChangeSupport;
011    import java.io.IOException;
012    import java.io.InputStream;
013    import java.security.Permission;
014    import java.text.MessageFormat;
015    import java.util.ArrayList;
016    import java.util.Collections;
017    import java.util.HashMap;
018    import java.util.Iterator;
019    import java.util.List;
020    import java.util.Map;
021    import java.util.ResourceBundle;
022    import java.util.Set;
023    import java.util.logging.ConsoleHandler;
024    import java.util.logging.FileHandler;
025    import java.util.logging.Level;
026    import java.util.logging.Logger;
027    import javax.swing.JFrame;
028    import jigcell.compare.IDataSource;
029    import jigcell.compare.ITab;
030    
031    /**
032     * The Comparator backend and some support for a shell servicing the Comparator frontend.
033     *
034     * <p>
035     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
036     * </p>
037     *
038     * @author Nicholas Allen
039     */
040    
041    public class Compare {
042    
043       /**
044        * Property key for class names
045        */
046    
047       public final static String CONFIG_CLASSNAME = "Compare.class";
048    
049       /**
050        * Property key for data source prototypes
051        */
052    
053       public final static String CONFIG_DATASOURCE = "Compare.dataSources";
054    
055       /**
056        * Parameters if none specified
057        */
058    
059       public final static String DEFAULT_CONFIG = "compare.config";
060    
061       /**
062        * Dialog title for error dialogs
063        */
064    
065       public final static String MESSAGE_ERROR;
066    
067       /**
068        * Dialog title for warning dialogs
069        */
070    
071       public final static String MESSAGE_WARNING;
072    
073       /**
074        * Property name used when the central configuration repository has changed
075        */
076    
077       public final static String PROPERTY_CONFIG_EDIT = "compare_config_edited";
078    
079       /**
080        * Resource key for the central configuration repository
081        */
082    
083       public final static String RESOURCE_CONFIG = "compare_config";
084    
085       /**
086        * Resource key for the central data source maintainer
087        */
088    
089       public final static String RESOURCE_DATAMANAGER = "compare_datamanager";
090    
091       /**
092        * Resource key for tabs added to the Comparator
093        */
094    
095       public final static String RESOURCE_TABS = "compare_tabs";
096    
097       /**
098        * Command line parameter to enable exception debugging
099        */
100    
101       protected final static String PARAMETER_DEBUG_EXCEPTIONS = "debug_exceptions";
102    
103       /**
104        * Configuration property to enable exception debugging
105        */
106    
107       protected final static String PROPERTY_DEBUG_EXCEPTIONS = "Compare.debugExceptions";
108    
109       /**
110        * Whether to provide debugging information for exceptions
111        */
112    
113       private static volatile boolean DEBUG_EXCEPTIONS;
114    
115       /**
116        * Method signature of data sources
117        */
118    
119       private final static Class SIGNATURE_DATASOURCE [] = new Class [] {};
120    
121       /**
122        * Maximum number of files to log
123        */
124    
125       private final static int LOG_COUNT = 2;
126    
127       /**
128        * Maximum number of bytes to log
129        */
130    
131       private final static int LOG_LIMIT = 1048576;
132    
133       /**
134        * Logger to output to
135        */
136    
137       private final static Logger logger = Logger.getAnonymousLogger ();
138    
139       /**
140        * Lock for controlling resource bundle manipulation
141        */
142    
143       private final static ReentrantWriterPreferenceReadWriteLock BUNDLE_LOCK = new ReentrantWriterPreferenceReadWriteLock ();
144    
145       /**
146        * Resource bundle for string translation
147        */
148    
149       private static ResourceBundle resourceBundle;
150    
151       /**
152        * Logging filename pattern
153        */
154    
155       private final static String LOG_PATTERN = "compare.log";
156    
157       /**
158        * Formatter for controlling how XML persistence behaves
159        */
160    
161       private final static XMLFormatter formatter = new XMLFormatter ();
162    
163       /**
164        * Security manager for Comparator
165        */
166    
167       protected CompareSecurityManager securityManager;
168    
169       /**
170        * Data source for the configuration file
171        */
172    
173       protected FileDataSource configSource;
174    
175       /**
176        * Standard configuration markers for this component
177        */
178    
179       protected List configMarkers;
180    
181       /**
182        * Lock for controlling tab manipulation
183        */
184    
185       protected final Object TAB_LOCK = new Object ();
186    
187       /**
188        * Collection of resources maintained for system
189        */
190    
191       private final Map resources = new HashMap ();
192    
193       /**
194        * Support for handling property change events
195        */
196    
197       private PropertyChangeSupport propertySupport;
198    
199       /**
200        * Lock for controlling resource manipulation
201        */
202    
203       private final ReentrantWriterPreferenceReadWriteLock RESOURCE_LOCK = new ReentrantWriterPreferenceReadWriteLock ();
204    
205       /**
206        * Information about a Comparator tab.
207        */
208    
209       public static class TabInfo {
210    
211          /**
212           * Tab
213           */
214    
215          private ITab tab;
216    
217          /**
218           * Creates a new tab description.
219           *
220           * @param tab Tab
221           */
222    
223          public TabInfo (ITab tab) {
224             this.tab = tab;
225          }
226    
227          /**
228           * Not allowed.
229           */
230    
231          private TabInfo () {}
232    
233          /**
234           * The tab display name.
235           */
236    
237          public String getName () {
238             return tab.getName ();
239          }
240    
241          /**
242           * The tab.
243           */
244    
245          public ITab getTab () {
246             return tab;
247          }
248       }
249    
250       /**
251        * Controls termination of the Comparator to prevent badly behaving tabs from causing problems.
252        */
253    
254       protected class CompareSecurityManager extends SecurityManager {
255    
256          /**
257           * Whether a shutdown should be allowed to proceed.
258           */
259    
260          protected boolean allowShutdown;
261    
262          /**
263           * Creates a new security manager.
264           */
265    
266          protected CompareSecurityManager () {
267             super ();
268          }
269    
270          public void checkExit (int status) {
271             if (allowShutdown)
272                return;
273             exit (status);
274             throw new SecurityException ();
275          }
276    
277          public void checkPermission (Permission permission) {}
278    
279          public void checkPermission (Permission permission, Object context) {}
280    
281          /**
282           * Shuts down the virtual machine.
283           */
284    
285          public void terminate (int status) {
286             allowShutdown = true;
287             System.exit (status);
288          }
289       }
290    
291       static {
292          DEBUG_EXCEPTIONS = Boolean.getBoolean (PARAMETER_DEBUG_EXCEPTIONS);
293          try {
294             logger.addHandler (new FileHandler (LOG_PATTERN, LOG_LIMIT, LOG_COUNT, true));
295          } catch (Exception e) {
296             logger.addHandler (new ConsoleHandler ());
297             assertion ("Unable to open log file.", e);
298          }
299          logger.setUseParentHandlers (false);
300          try {
301             addResourceBundle ("/jigcell/compare/impl/resource/core.properties");
302          } catch (Exception e) {
303             assertion ("Unable to load core resource bundle.", e);
304          }
305          MESSAGE_ERROR = getString ("Compare.titleError");
306          MESSAGE_WARNING = getString ("Compare.titleWarning");
307          Capabilities.setAccessibility (true);
308       }
309    
310       /**
311        * Acquires the lock of a Sync object.
312        *
313        * @param sync Sync
314        */
315    
316       public final static void acquireSync (Sync sync) {
317          while (true)
318             try {
319                sync.acquire ();
320                break;
321             } catch (InterruptedException e) {
322                assertion (getString ("Compare.syncAcquire"), e);
323             }
324       }
325    
326       /**
327        * @see jigcell.compare.impl.XMLFormatter#addDelegate(java.lang.Class,java.beans.PersistenceDelegate)
328        */
329    
330       public final static void addDelegate (Class clazz, PersistenceDelegate delegate) {
331          formatter.addDelegate (clazz, delegate);
332       }
333    
334       /**
335        * Loads a table of resource strings for the current locale.
336        *
337        * @param resourceName Name of resource to bundle to load
338        */
339    
340       public final static void addResourceBundle (String resourceName) throws IOException {
341          if (resourceName == null)
342             throw new IllegalArgumentException ();
343          InputStream resourceStream = Compare.class.getResourceAsStream (resourceName);
344          if (resourceStream == null)
345             throw new IOException (resourceBundle == null ? "Unable to load resource bundle." : getString ("Compare.missingResourceError"));
346          Sync sync = BUNDLE_LOCK.writeLock ();
347          acquireSync (sync);
348          try {
349             resourceBundle = new LinkedPropertyResourceBundle (resourceStream, resourceBundle);
350          } finally {
351             sync.release ();
352          }
353       }
354    
355       /**
356        * @see jigcell.compare.impl.XMLFormatter#addTransient(java.lang.Class,java.lang.String)
357        */
358    
359       public final static void addTransient (Class clazz, String field) {
360          formatter.addTransient (clazz, field);
361       }
362    
363       /**
364        * Logs an exceptional condition and asserts if debugging is enabled.
365        *
366        * @param message Exception message
367        */
368    
369       public final static void assertion (String message) {
370          assertion (message, null);
371       }
372    
373       /**
374        * Logs an exceptional condition and asserts if debugging is enabled.
375        *
376        * @param message Exception message
377        * @param t Throwable
378        */
379    
380       public final static void assertion (String message, Throwable t) {
381          for (Throwable cause = t; cause != null; cause = t.getCause ())
382             t = cause;
383          warning (message, t);
384          assert !getDebugExceptions () : t;
385       }
386    
387       /**
388        * Acquires the lock of a Sync object or returns false if this operation would block.
389        *
390        * @param sync Sync
391        */
392    
393       public final static boolean attemptSync (Sync sync) {
394          try {
395             return sync.attempt (0);
396          } catch (InterruptedException e) {
397             assertion (getString ("Compare.syncAttempt"), e);
398          }
399          return false;
400       }
401    
402       /**
403        * Whether to display exception debugging information
404        */
405    
406       public final static boolean getDebugExceptions () {
407          return DEBUG_EXCEPTIONS;
408       }
409    
410       /**
411        * Looks up a string in the table of resources and applies a message format to it.
412        *
413        * @param key Resource key
414        * @param object Format object
415        */
416    
417       public final static String formatString (String key, Object object) {
418          return formatString (key, new Object [] {object});
419       }
420    
421       /**
422        * Looks up a string in the table of resources and applies a message format to it.
423        *
424        * @param key Resource key
425        * @param objects Format objects
426        */
427    
428       public final static String formatString (String key, Object objects []) {
429          return MessageFormat.format (getString (key), objects);
430       }
431    
432       /**
433        * Looks up a string in the table of resources.
434        *
435        * @param key Resource key
436        */
437    
438       public final static String getString (String key) {
439          Sync sync = BUNDLE_LOCK.readLock ();
440          acquireSync (sync);
441          try {
442             if (resourceBundle == null)
443                throw new IllegalStateException ("No resource bundle has been loaded.");
444             return resourceBundle.getString (key);
445          } finally {
446             sync.release ();
447          }
448       }
449    
450       /**
451        * Starts a new headless Comparator.
452        *
453        * @param args Program arguments
454        */
455    
456       public static void main (String args []) {
457          new Compare (args == null || args.length == 0 ? DEFAULT_CONFIG : args [0]);
458       }
459    
460       /**
461        * Logs a message.
462        *
463        * @param message Message
464        */
465    
466       public final static void message (String message) {
467          logger.log (Level.INFO, message);
468       }
469    
470       /**
471        * Sets whether to display exception debugging information
472        *
473        * @param debug Whether to display exception debugging information
474        */
475    
476       public final static void setDebugExceptions (boolean debug) {
477          DEBUG_EXCEPTIONS = debug;
478       }
479    
480       /**
481        * Converts a throwable from a potentially checked exception type to an unchecked exception type and throws it.
482        *
483        * @param t Throwable
484        */
485    
486       public final static void throwUncheckedException (Throwable t) {
487          if (t instanceof Error)
488             throw (Error) t;
489          if (t instanceof RuntimeException)
490             throw (RuntimeException) t;
491          throw new RuntimeException (t.getMessage (), t);
492       }
493    
494       /**
495        * @see jigcell.compare.impl.XMLFormatter#updateEncoder(java.beans.Encoder)
496        */
497    
498       public final static void updateEncoder (Encoder encoder) {
499          formatter.updateEncoder (encoder);
500       }
501    
502       /**
503        * Logs an exceptional condition.
504        *
505        * @param message Exception message
506        */
507    
508       public final static void warning (String message) {
509          warning (message, null);
510       }
511    
512       /**
513        * Logs an exceptional condition.
514        *
515        * @param message Exception message
516        * @param t Throwable
517        */
518    
519       public final static void warning (String message, Throwable t) {
520          for (Throwable cause = t; cause != null; cause = t.getCause ())
521             t = cause;
522          logger.log (Level.WARNING, message, t);
523       }
524    
525       /**
526        * Creates a new headless Comparator.
527        *
528        * @param configFile Name of configuration file
529        */
530    
531       public Compare (String configFile) {
532          try {
533             Thread.currentThread ().setContextClassLoader (Compare.class.getClassLoader ());
534          } catch (Exception e) {
535             shellHandleException (MESSAGE_WARNING, getString ("Compare.startupLoaderError"));
536          }
537          try {
538             if (System.getSecurityManager () == null) {
539                securityManager = new CompareSecurityManager ();
540                System.setSecurityManager (securityManager);
541             }
542          } catch (SecurityException e) {
543             shellHandleException (MESSAGE_WARNING, getString ("Compare.startupSecurityError"));
544          }
545          if (configFile == null) {
546             String errorMessage = getString ("Compare.configNoFileError");
547             shellHandleException (MESSAGE_ERROR, errorMessage);
548             throw new IllegalArgumentException (errorMessage);
549          }
550          shellStartHook ();
551          propertySupport = new PropertyChangeSupport (this);
552          propertySupport.addPropertyChangeListener (ProxyBuilder.proxyPropertyChangeListener (this, "propertyChange"));
553          configSource = new XMLFileDataSource (configFile);
554          configSource.setExceptionListenerOption (new ExceptionRecorder (this, getString ("Compare.configFormatError"), false));
555          Config config;
556          try {
557             config = (Config) configSource.read ();
558          } catch (Exception e) {
559             config = new Config ();
560             shellHandleException (MESSAGE_ERROR, getString ("Compare.configLoadError"), e);
561          } finally {
562             configSource.finish ();
563          }
564          configMarkers = Config.createStandardMarkers (this);
565          setConfig (config);
566          shellStableHook ();
567          try {
568             DEBUG_EXCEPTIONS |= Config.convertToBoolean (config.findValue (configMarkers, PROPERTY_DEBUG_EXCEPTIONS, false, false, false));
569          } catch (Exception e) {
570             shellHandleException (MESSAGE_WARNING, getString ("Compare.debugReadError"));
571          }
572          DataManager dataManager = createDataManager ();
573          setDataManager (dataManager == null ? new DataManager () : dataManager);
574       }
575    
576       /**
577        * Adds a PropertyChangeListener to the listener list.
578        *
579        * @param listener Listener
580        */
581    
582       public void addPropertyChangeListener (PropertyChangeListener listener) {
583          propertySupport.addPropertyChangeListener (listener);
584       }
585    
586       /**
587        * Adds a PropertyChangeListener for a specific property.
588        *
589        * @param property Property
590        * @param listener Listener
591        */
592    
593       public void addPropertyChangeListener (String property, PropertyChangeListener listener) {
594          propertySupport.addPropertyChangeListener (property, listener);
595       }
596    
597       /**
598        * Adds a mapping between the give name and resource.
599        *
600        * @return Whether the resource could be added
601        */
602    
603       public boolean addResource (String name, Object resource) {
604          if (name == null || resource == null)
605             return false;
606          Sync sync = RESOURCE_LOCK.writeLock ();
607          acquireSync (sync);
608          try {
609             if (resources.containsKey (name))
610                return false;
611             resources.put (name, resource);
612             return true;
613          } finally {
614             sync.release ();
615          }
616       }
617    
618       /**
619        * Adds a tab to the main Comparator display.
620        *
621        * @param component Contents of the tab
622        */
623    
624       public TabInfo addTab (ITab component) {
625          TabInfo info = new TabInfo (component);
626          synchronized (TAB_LOCK) {
627             updateResourceMap (RESOURCE_TABS, component, info);
628          }
629          return info;
630       }
631    
632       /**
633        * A tab registered with the Comparator of the specified type or null if no such tab exists.
634        *
635        * @param clazz Tab class
636        */
637    
638       public ITab findTabByClass (Class clazz) {
639          if (clazz == null)
640             return null;
641          for (Iterator iterator = getTabMap ().keySet ().iterator (); iterator.hasNext (); ) {
642             ITab tab = (ITab) iterator.next ();
643             if (clazz.isAssignableFrom (tab.getClass ()))
644                return tab;
645          }
646          return null;
647       }
648    
649       /**
650        * A tab registered with the Comparator of the specified type or null if no such tab exists.
651        *
652        * @param clazz Tab class
653        */
654    
655       public ITab findTabByClassExact (Class clazz) {
656          if (clazz == null)
657             return null;
658          for (Iterator iterator = getTabMap ().keySet ().iterator (); iterator.hasNext (); ) {
659             ITab tab = (ITab) iterator.next ();
660             if (clazz == tab.getClass ())
661                return tab;
662          }
663          return null;
664       }
665    
666       /**
667        * A tab registered with the Comparator of the specified name or null if no such tab exists.
668        *
669        * @param name Tab name
670        */
671    
672       public ITab findTabByName (String name) {
673          if (name == null)
674             return null;
675          for (Iterator iterator = getTabMap ().entrySet ().iterator (); iterator.hasNext (); ) {
676             Map.Entry entry = (Map.Entry) iterator.next ();
677             if (name.equals (((TabInfo) entry.getValue ()).getName ()))
678                return (ITab) entry.getKey ();
679          }
680          return null;
681       }
682    
683       /**
684        * Fires a property change message about a named resource.
685        *
686        * @param name Resource name
687        */
688    
689       public void firePropertyChange (String name) {
690          if (name == null)
691             throw new IllegalArgumentException ();
692          firePropertyChange (name, null, getResource (name));
693       }
694    
695       /**
696        * Fires a property change message about a named resource.
697        *
698        * @param name Resource name
699        * @param oldValue Old resource value
700        * @param newValue New resource value
701        */
702    
703       public void firePropertyChange (String name, Object oldValue, Object newValue) {
704          propertySupport.firePropertyChange (name, oldValue, newValue);
705       }
706    
707       /**
708        * The configuration resource.
709        */
710    
711       public Config getConfig () {
712          return (Config) getResource (RESOURCE_CONFIG);
713       }
714    
715       /**
716        * The data manager resource.
717        */
718    
719       public DataManager getDataManager () {
720          return (DataManager) getResource (RESOURCE_DATAMANAGER);
721       }
722    
723       /**
724        * The frontend shell display frame.  Returns null if this is a headless Comparator.
725        */
726    
727       public JFrame getDisplayFrame () {
728          return null;
729       }
730    
731       /**
732        * An array of all the PropertyChangeListeners added to this Compare with addPropertyChangeListener.
733        */
734    
735       public PropertyChangeListener [] getPropertyChangeListeners () {
736          return propertySupport.getPropertyChangeListeners ();
737       }
738    
739       /**
740        * An array of all the listeners which have been associated with the named property.
741        *
742        * @param property Property
743        */
744    
745       public PropertyChangeListener [] getPropertyChangeListeners (String property) {
746          return propertySupport.getPropertyChangeListeners (property);
747       }
748    
749       /**
750        * A named resource.
751        *
752        * @param name Resource name
753        */
754    
755       public Object getResource (String name) {
756          if (name == null)
757             return null;
758          Sync sync = RESOURCE_LOCK.readLock ();
759          acquireSync (sync);
760          try {
761             return resources.get (name);
762          } finally {
763             sync.release ();
764          }
765       }
766    
767       /**
768        * A named resource known to be a map.  Creates a map if none exists so that future modifications of the named resource map are properly
769        * updated in existing instances.
770        *
771        * @param name Resource name
772        */
773    
774       public Map getResourceMap (String name) {
775          if (name == null)
776             throw new IllegalArgumentException ();
777          Sync sync = RESOURCE_LOCK.writeLock ();
778          acquireSync (sync);
779          try {
780             Map map = (Map) getResource (name);
781             if (map == null) {
782                map = Collections.synchronizedMap (new HashMap ());
783                addResource (name, map);
784             }
785             return map;
786          } finally {
787             sync.release ();
788          }
789       }
790    
791       /**
792        * The currently used resource names.
793        */
794    
795       public Set getResourceNames () {
796          Sync sync = RESOURCE_LOCK.readLock ();
797          acquireSync (sync);
798          try {
799             return resources.keySet ();
800          } finally {
801             sync.release ();
802          }
803       }
804    
805       /**
806        * The tab resource map.
807        */
808    
809       public Map getTabMap () {
810          return getResourceMap (RESOURCE_TABS);
811       }
812    
813       /**
814        * All of the tabs currently registered with the Comparator.
815        */
816    
817       public ITab [] getTabs () {
818          return (ITab []) getTabMap ().keySet ().toArray (new ITab [0]);
819       }
820    
821       /**
822        * Removes a PropertyChangeListener from the listener list.
823        *
824        * @param listener Listener
825        */
826    
827       public void removePropertyChangeListener (PropertyChangeListener listener) {
828          propertySupport.removePropertyChangeListener (listener);
829       }
830    
831       /**
832        * Removes a PropertyChangeListener for a specific property.
833        *
834        * @param property Property
835        * @param listener Listener
836        */
837    
838       public void removePropertyChangeListener (String property, PropertyChangeListener listener) {
839          propertySupport.removePropertyChangeListener (property, listener);
840       }
841    
842       /**
843        * Deletes a named resource.
844        *
845        * @param name Resource name
846        */
847    
848       public boolean removeResource (String name) {
849          if (name == null)
850             return false;
851          Sync sync = RESOURCE_LOCK.writeLock ();
852          acquireSync (sync);
853          try {
854             return resources.remove (name) != null;
855          } finally {
856             sync.release ();
857          }
858       }
859    
860       /**
861        * Adds a mapping between the give name and resource replacing any existing mapping.  This method should be used with caution.  Editing a
862        * shared instance is preferrable to replacing instances.
863        *
864        * @param name Resource name
865        * @param resource Resource
866        */
867    
868       public boolean replaceResource (String name, Object resource) {
869          if (name == null || resource == null)
870             return false;
871          Sync sync = RESOURCE_LOCK.writeLock ();
872          acquireSync (sync);
873          try {
874             return resources.put (name, resource) == null;
875          } finally {
876             sync.release ();
877          }
878       }
879    
880       /**
881        * Compiles the contents of a resource catalog.  A resource catalog is a resource map whose values are all lists.
882        *
883        * @param name Resource name
884        */
885    
886       public List scanResourceCatalog (String name) {
887          if (name == null)
888             throw new IllegalArgumentException ();
889          List resource = new ArrayList ();
890          Sync sync = RESOURCE_LOCK.readLock ();
891          acquireSync (sync);
892          try {
893             Map map = getResourceMap (name);
894             synchronized (map) {
895                for (Iterator iterator = map.values ().iterator (); iterator.hasNext (); ) {
896                   Object entry = iterator.next ();
897                   if (entry instanceof List)
898                      resource.addAll ((List) entry);
899                   else
900                      assertion (getString ("Compare.resourceScanError"));
901                }
902             }
903          } finally {
904             sync.release ();
905          }
906          return resource;
907       }
908    
909       /**
910        * Compiles the contents of a resource map.
911        *
912        * @param name Resource name
913        */
914    
915       public List scanResourceMap (String name) {
916          if (name == null)
917             throw new IllegalArgumentException ();
918          List resource = new ArrayList ();
919          Map map = getResourceMap (name);
920          resource.addAll (map.values ());
921          return resource;
922       }
923    
924       /**
925        * Compiles the contents of all resources.
926        */
927    
928       public List scanResources () {
929          List resource = new ArrayList ();
930          Sync sync = RESOURCE_LOCK.readLock ();
931          acquireSync (sync);
932          try {
933             resource.addAll (resources.values ());
934          } finally {
935             sync.release ();
936          }
937          return resource;
938       }
939    
940       /**
941        * Handle an exception in a Comparator component.
942        *
943        * @param type Exception type
944        * @param message Display message
945        */
946    
947       public void shellHandleException (String type, Object message) {
948          shellHandleException (type, message, null);
949       }
950    
951       /**
952        * Handle an exception in a Comparator component.
953        *
954        * @param type Exception type
955        * @param message Display message
956        * @param t Throwable
957        */
958    
959       public void shellHandleException (String type, Object message, Throwable t) {
960          String text = String.valueOf (message);
961          System.err.println ("*** " + type + "\n" + text);
962          assertion (text, t);
963       }
964    
965       /**
966        * Handle an informational message in a Comparator component.
967        *
968        * @param type Message type
969        * @param message Display message
970        */
971    
972       public void shellHandleMessage (String type, Object message) {
973          String text = String.valueOf (message);
974          System.out.println (text);
975          message (text);
976       }
977    
978       /**
979        * Changes a mapping in a named resource known to be a map.
980        *
981        * @param name Resource name
982        * @param key Mapping key
983        * @param value Mapping value
984        */
985    
986       public void updateResourceMap (String name, Object key, Object value) {
987          if (name == null || key == null)
988             throw new IllegalArgumentException ();
989          Sync sync = RESOURCE_LOCK.writeLock ();
990          acquireSync (sync);
991          try {
992             Map map = getResourceMap (name);
993             map.put (key, value);
994          } finally {
995             sync.release ();
996          }
997       }
998    
999       /**
1000        * Builds the repository of external data sources.
1001        */
1002    
1003       protected DataManager createDataManager () {
1004          DataManager dataManager = new DataManager ();
1005          Config config = getConfig ();
1006          try {
1007             Object param [] = new Object [0];
1008             for (Iterator iterator = Config.convertToList (config.findValue (configMarkers, CONFIG_DATASOURCE, false, false, false)).iterator ();
1009                iterator.hasNext (); )
1010                try {
1011                   dataManager.addPrototypeSource (
1012                      (IDataSource) Class.forName ((String) iterator.next ()).getDeclaredConstructor (SIGNATURE_DATASOURCE).newInstance (param));
1013                } catch (Exception e) {
1014                   shellHandleException (MESSAGE_WARNING, getString ("Compare.dataLoadError"), e);
1015                }
1016          } catch (Exception e) {
1017             shellHandleException (MESSAGE_WARNING, getString ("Compare.dataListError"));
1018          }
1019          return dataManager;
1020       }
1021    
1022       /**
1023        * Exits from the Comparator.
1024        *
1025        * @param status Status code
1026        */
1027    
1028       protected void exit (int status) {
1029          if (securityManager == null)
1030             System.exit (status);
1031          securityManager.terminate (status);
1032       }
1033    
1034       protected void propertyChange (PropertyChangeEvent e) {
1035          if (!PROPERTY_CONFIG_EDIT.equals (e.getPropertyName ()))
1036             return;
1037          Config config = getConfig ();
1038          Sync sync = config.getLock ().readLock ();
1039          acquireSync (sync);
1040          try {
1041             try {
1042                configSource.write (getConfig ());
1043             } catch (Exception _e) {
1044                assertion (getString ("Compare.configSaveError"), _e);
1045             } finally {
1046                configSource.finish ();
1047             }
1048          } finally {
1049             sync.release ();
1050          }
1051       }
1052    
1053       /**
1054        * Sets the configuration resource.
1055        *
1056        * @param config Configuration
1057        */
1058    
1059       protected void setConfig (Config config) {
1060          addResource (RESOURCE_CONFIG, config);
1061       }
1062    
1063       /**
1064        * Sets the data manager resource.
1065        *
1066        * @param manager Data manager
1067        */
1068    
1069       protected void setDataManager (DataManager manager) {
1070          addResource (RESOURCE_DATAMANAGER, manager);
1071       }
1072    
1073       /**
1074        * Stable hook for frontend shell.  The system configuration is guaranteed to have been loaded but no processing has been done.
1075        */
1076    
1077       protected void shellStableHook () {
1078          message (getString ("Compare.shellStable"));
1079       }
1080    
1081       /**
1082        * Start hook for frontend shell.  No initialization is guaranteed to have taken place at this point.
1083        */
1084    
1085       protected void shellStartHook () {
1086          message (getString ("Compare.shellStart"));
1087       }
1088    }