home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgLangD.iso / VCAFE.3.0A / Sample.bin / Report.java < prev    next >
Text File  |  1998-11-05  |  23KB  |  558 lines

  1. // Copyright (c) 1997, 1998 Symantec, Inc. All Rights Reserved.
  2.  
  3. import java.io.Serializable;
  4. import java.util.Date;
  5. import java.util.Calendar;
  6. import java.util.Hashtable;
  7. import java.util.Vector;
  8. import java.util.NoSuchElementException;
  9. import java.util.Enumeration;
  10.  
  11. /*
  12. This class encapsulates a single web log "report".
  13. It contains:
  14. 1) The report options (specified/changed by New/Edit Report dialogs),
  15.    including a LogFile object representing the log file specified in the 
  16.    report options.
  17. 2) The report results (available after analyzing the log file).
  18. 3) Working fields (used during log file analysis).
  19. 4) Methods to analyze the log file and generate the results.
  20. Statistics are gathered over a day long interval (i.e. hits per day, etc) and
  21. also gathered over an interval chosen to be appropriate for the timespan of
  22. the report.
  23. It was written manually.
  24. It has no UI code.
  25. */
  26. public class Report implements Serializable {
  27.     //  --  The Report Options
  28.     // possible timeframe choice values
  29.     public static final int TIMEFRAME_ALL = 0;
  30.     public static final int TIMEFRAME_TODAY = 1;
  31.     public static final int TIMEFRAME_YESTERDAY = 2;
  32.     public static final int TIMEFRAME_PAST_2_DAYS = 3;
  33.     public static final int TIMEFRAME_PAST_7_DAYS = 4;
  34.     public static final int TIMEFRAME_PAST_14_DAYS = 5;
  35.     public static final int TIMEFRAME_PAST_30_DAYS = 6;
  36.     public static final int TIMEFRAME_PAST_60_DAYS = 7;
  37.     public static final int TIMEFRAME_PAST_90_DAYS = 8;
  38.     public static final int TIMEFRAME_SPECIFIC = 9;
  39.     String title;
  40.     int timeframe;  // TIMEFRAME_
  41.     Date dateStart; // valid if timeframe == TIMEFRAME_SPECIFIC
  42.     Date dateEnd;   // valid if timeframe == TIMEFRAME_SPECIFIC
  43.     LogFile log = new LogFile();
  44.     String homePageFile;
  45.     boolean optUsageChart;
  46.     boolean optGeneralStatistics;
  47.     boolean optMostActiveDomains;
  48.     //  --  The Report Results
  49.     Date analyzedDate;              // date report last run on
  50.     long startTimestamp;            // actual starting time (millisecs)
  51.     long endTimestamp;              // actual ending time (millisecs)
  52.     int totalHits;                  // total number of hits
  53.     int homePageHits;               // hits on the home page
  54.     int totalUserSessionCount;      // total number of user sessions
  55.     long avgUserSessionLength = 0;  // average session length (millisecs)
  56.     DomainRecord sortedDomains[];   // all domains, sorted by number of hits
  57.     // "per day" statistics
  58.     int hitsVsDay[];                // array to track hits VS day
  59.     int userSessionsVsDay[];        // array to sessions VS day
  60.     double avgHitsPerDay;              // average hits per day
  61.     double avgUserSessionsPerDay;      // average sessions per day
  62.     // "per <time interval>" statistics
  63.     long hitTimeInterval;           // interval statistics are gathered over (millisecs)
  64.     int hitsVsTimeInterval[];       // array to track hits VS hitTimeInterval
  65.     int userSessionsVsTimeInterval[];//array to sessions VS hitTimeInterval
  66.     double avgHitsPerHitTimeInterval;  // average number of hits per hitTimeInterval
  67.     double avgUserSessionsPerHitTimeInterval;  // average sessions per hitTimeInterval
  68.     //SessionRecord sortedSessions[];
  69.     //  --  Working fields. Used during log file analysis
  70.     String normalizedHomePageFile;  // home page file with "/" added at start as needed and "\" changed to "/" as needed
  71.     long newUserSessionThreshold;   // user idle time to create a new session (millisecs)
  72.     long timeTillFirstInterval;     // millisecs till first full interval of time being analyzed
  73.     long timeTillFirstDayInterval;  // millisecs till first full day of time being analyzed
  74.     long totalUserSessionLength;    // total length in time of all user sessions
  75.     Hashtable domains;              // of DomainRecord
  76.     Hashtable sessions;             // of SessionRecord
  77.     SessionRecord sessionListHead = null;   // list of all SessionRecords
  78.     
  79.     /**
  80.      * Constructs a Report.
  81.      */
  82.     public Report() {
  83.             //{{INIT_CONTROLS
  84.         //}}
  85. }
  86.  
  87.     /**
  88.      * Frees up memory used to analyze (but not report) a web log.
  89.      */
  90.     void freeUpMem() {
  91.         // Free up mem used to analyze, but not report, log info
  92.         domains = null;
  93.         sessions = null;
  94.         sessionListHead = null;
  95.         log.freeUpMem();
  96.     }
  97.  
  98.     // -- ACCESS
  99.     
  100.     // Returns true if this report has been run (analyzed) before.
  101.     public boolean hasBeenRun() {
  102.         return analyzedDate != null;
  103.     }
  104.     
  105.     // -- Handy shortcuts to get/set LogFile attributes
  106.     public String getLogFileURL() {
  107.         return log.getLogFileURL();
  108.     }
  109.     public void setLogFileURL(String logFileURL) {
  110.         log.setLogFileURL(logFileURL);
  111.     }
  112.     public int getLogFileFormat() {
  113.         return log.getLogFileFormat();
  114.     }
  115.     public void setLogFileFormat(int logFileFormat) {
  116.         log.setLogFileFormat(logFileFormat);
  117.     }
  118.     
  119.     // -- ANALYSIS
  120.  
  121.     /*
  122.     Runs the report, analyzing the log file and gathering statistics.
  123.     The TrackProgress interface is used to report analysis status to UI code.
  124.     */
  125.     public void run(TrackProgress trackProgress) {
  126.         try {
  127.             
  128.             //  --  Setup analysis
  129.             
  130.             // Reading and parse the log file, getting the results
  131.             Analyzer.throwExceptionIfCurrentThreadCancelled();
  132.             LogRecord records[] = log.readAndParseLogFile(trackProgress);
  133.             if(records == null || records.length == 0) {
  134.                 trackProgress.okAlert("No valid HTTP records to process (wrong log file format?).");
  135.                 trackProgress.step("ABORTED", 0);
  136.                 // Note that we're done in the UI
  137.                 trackProgress.done();
  138.                 return;
  139.             }
  140.             // determine starting and ending time for this report
  141.             Analyzer.throwExceptionIfCurrentThreadCancelled();
  142.             startTimestamp = log.getEarliestTimestamp();
  143.             endTimestamp = log.getLatestTimestamp();
  144.             if(timeframe != TIMEFRAME_ALL) {
  145.                 startTimestamp = dateStart.getTime();
  146.                 endTimestamp = dateEnd.getTime();
  147.             }
  148.             // 
  149.             totalHits = 0;
  150.             homePageHits = 0;
  151.             totalUserSessionCount = 0;
  152.             avgUserSessionLength = 0;
  153.             sortedDomains = null;
  154.             // init per day stats
  155.             timeTillFirstDayInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_DAY, CAL_HOUR);
  156.             // table: hitsVsDay
  157.             int numTimeIntervals = 1 + timestampToDayIntervalIndex(endTimestamp);
  158.             hitsVsDay = new int[numTimeIntervals];
  159.             // table: userSessionsVsDay
  160.             userSessionsVsDay = new int[numTimeIntervals];
  161.             avgHitsPerDay = 0;
  162.             avgUserSessionsPerDay = 0;
  163.             // init per <time interval> stats
  164.             // determine the <time interval> units: min, hour, day, week, or year
  165.             long millisecs = endTimestamp - startTimestamp;
  166.             if(millisecs <= WLAUtil.MILLISECS_PER_HOUR) {
  167.                 hitTimeInterval = WLAUtil.MILLISECS_PER_MINUTE;
  168.                 timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_MINUTE, CAL_SECOND);
  169.             } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_DAY) {
  170.                 hitTimeInterval = WLAUtil.MILLISECS_PER_HOUR;
  171.                 timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_HOUR, CAL_MINUTE);
  172.             } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_WEEK) {
  173.                 hitTimeInterval = WLAUtil.MILLISECS_PER_DAY;
  174.                 timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_DAY, CAL_HOUR);
  175.             } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_YEAR) {
  176.                 hitTimeInterval = WLAUtil.MILLISECS_PER_WEEK;
  177.                 timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_WEEK, CAL_DAY_OF_WEEK);
  178.             } else {
  179.                 hitTimeInterval = WLAUtil.MILLISECS_PER_YEAR;
  180.                 timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_YEAR, CAL_WEEK_OF_YEAR);
  181.             }
  182.             // table: hitsVsTimeInterval
  183.             numTimeIntervals = 1 + timestampToIntervalIndex(endTimestamp);
  184.             hitsVsTimeInterval = new int[numTimeIntervals];
  185.             // table: userSessionsVsTimeInterval
  186.             userSessionsVsTimeInterval = new int[numTimeIntervals];
  187.             avgHitsPerHitTimeInterval = 0;
  188.             avgUserSessionsPerHitTimeInterval = 0;
  189.             // "normalize" home page file name
  190.             int lim = homePageFile.length();
  191.             if(lim == 0) {
  192.                 normalizedHomePageFile = homePageFile;
  193.             } else {
  194.                 StringBuffer buf = new StringBuffer(lim + 1);
  195.                 int idx = 0;
  196.                 char ch = homePageFile.charAt(0);
  197.                 // if no leading slash, add one
  198.                 buf.append('/');
  199.                 if(ch == '/' || ch == java.io.File.separatorChar) {
  200.                     idx = 1;
  201.                 }
  202.                 while(idx < lim) {
  203.                     ch = homePageFile.charAt(idx++);
  204.                     if(ch == java.io.File.separatorChar) {
  205.                         buf.append('/');
  206.                     } else {
  207.                         buf.append(ch);
  208.                     }
  209.                 }
  210.                 normalizedHomePageFile = buf.toString();
  211.             }
  212.             // init remaining working fields
  213.             newUserSessionThreshold = Data.getDataInstance().userSessionIdleLimit * WLAUtil.MILLISECS_PER_MINUTE;
  214.             totalUserSessionLength = 0;
  215.             domains = new Hashtable();      // of DomainRecord
  216.             sessions = new Hashtable();      // of SessionRecord
  217.             sessionListHead = null;
  218.             // init local info
  219.             int hitCount = 0;
  220.             int totalLogHits = log.getTotalHits();
  221.             // Done with analysis setup
  222.             
  223.             if(totalLogHits > 0) {
  224.             
  225.                 //  --  SCAN LOG LINES AND ACCUMULATE DATA
  226.                 
  227.                 for(int r = 0; r < totalLogHits; r++) {
  228.                     // percent for this phase ranges from 96 -> 100
  229.                     int percent = 96 + ((4 * hitCount)/totalLogHits);
  230.                     trackProgress.step("Processing log file...", percent);
  231.                     // Note the line's stats
  232.                     noteLogRecord(records[r]);
  233.                 }
  234.                 
  235.                 //  --  CALC STATISTICS
  236.                 
  237.                 // Calc averages
  238.                 double timespan = endTimestamp - startTimestamp;
  239.                 timespan = timespan / WLAUtil.MILLISECS_PER_DAY;
  240.                 avgHitsPerDay = (double)totalHits / timespan;
  241.                 avgUserSessionsPerDay = (double)totalUserSessionCount / timespan;
  242.                 timespan = endTimestamp - startTimestamp;
  243.                 timespan = timespan / hitTimeInterval;
  244.                 avgHitsPerHitTimeInterval = (double)totalHits / timespan;
  245.                 avgUserSessionsPerHitTimeInterval = (double)totalUserSessionCount / timespan;
  246.  
  247.                 // Calc total length of user sessions
  248.                 Analyzer.throwExceptionIfCurrentThreadCancelled();
  249.                 SessionRecord sr = sessionListHead;
  250.                 while(sr != null) {
  251.                     long delta = sr.lastTimestamp - sr.firstTimestamp;
  252.                     totalUserSessionLength += delta;
  253.                     sr = sr.next;
  254.                 }
  255.  
  256.                 // Calc avg user session length
  257.                 if(totalUserSessionCount > 0) {
  258.                     avgUserSessionLength = totalUserSessionLength / totalUserSessionCount;
  259.                 }
  260.                 
  261.                 // Sort domains in order of most hits to least hits
  262.                 sortedDomains = new DomainRecord[domains.size()];
  263.                 // copy into array...
  264.                 int idx = 0;
  265.                 for(Enumeration e = domains.elements(); e.hasMoreElements();) {
  266.                     DomainRecord dr = (DomainRecord)e.nextElement();
  267.                     sortedDomains[idx++] = dr;
  268.                 }
  269.                 // ...and sort
  270.                 Analyzer.throwExceptionIfCurrentThreadCancelled();
  271.                 WLAUtil.quickSort(sortedDomains);
  272.             }
  273.             
  274.             //  --  DONE OK, note date/time report run
  275.             
  276.             analyzedDate = new Date();  // date report last run on
  277.             trackProgress.step("Done", 100);
  278.             
  279.         } catch(NoSuchElementException x) {
  280.             trackProgress.okAlert(x.toString());
  281.             trackProgress.step("ABORTED", 0);
  282.         } catch(ParseLogException x) {
  283.             // These have already been reported.
  284.         } catch(OutOfMemoryError x) {
  285.             // free the main memory occupiers
  286.             Data.freeUpMem();
  287.             trackProgress.okAlert(x.toString());
  288.             trackProgress.step("ABORTED", 0);
  289.         }
  290.         
  291.         // Note that we're done in the UI
  292.         trackProgress.done();
  293.     }
  294.  
  295.     /*
  296.     Called by the run() method for each log record to analyze.
  297.     Accumulates report statistics.
  298.     */
  299.     void noteLogRecord(LogRecord logRec) throws ParseLogException {
  300.         Analyzer.throwExceptionIfCurrentThreadCancelled();
  301.  
  302.         //  --  Filter out undesired log records
  303.         
  304.         // Does this record occur within the timeframe we're analyzing??
  305.         if(startTimestamp > logRec.requestTimestamp || logRec.requestTimestamp > endTimestamp) {
  306.             // No. Ignore it.
  307.             return;
  308.         }
  309.         // HTTP error on this request?
  310.         if(logRec.httpStatusCode >= 500) {
  311.             // Yes. Ignore it.
  312.             return;
  313.         }
  314.         // Determine type of HTTP request
  315.         int idx = logRec.clientRequest.indexOf(' ');
  316.         if(idx < 0) {
  317.             // Unexpected clientRequest. Ignore
  318.             return;
  319.         }
  320.         String str = logRec.clientRequest.substring(0, idx);
  321.         if(!str.equals("GET")) {
  322.             // Ignore non-GET HTTP requests
  323.             return;
  324.         }
  325.  
  326.         //  --  Use this log record
  327.         
  328.         // Note info for General Statistics
  329.         totalHits++;
  330.         
  331.         // Note info for Hits VS Time chart
  332.         int hitInterval = timestampToIntervalIndex(logRec.requestTimestamp);
  333.         hitsVsTimeInterval[hitInterval]++;
  334.  
  335.         // Note info for Hits VS Day for day-related averages
  336.         hitInterval = timestampToDayIntervalIndex(logRec.requestTimestamp);
  337.         hitsVsDay[hitInterval]++;
  338.  
  339.         // Request for home page?
  340.         int idx2 = logRec.clientRequest.indexOf(' ', ++idx);
  341.         if(idx2 > 0) {
  342.             str = logRec.clientRequest.substring(idx, idx2);
  343.             if(str.equals("/") || str.equalsIgnoreCase(normalizedHomePageFile)) {
  344.                 homePageHits++;
  345.             }
  346.         }
  347.         
  348.         // Track # of hits for each domain
  349.         DomainRecord dr = (DomainRecord)domains.get(logRec.remoteHost);
  350.         if(dr == null) {
  351.             dr = new DomainRecord(logRec.remoteHost);
  352.             domains.put(logRec.remoteHost, dr);
  353.         }
  354.         // update the domain record
  355.         dr.hits++;
  356.         
  357.         //  -- Track sessions
  358.         
  359.         String sessionStr = logRec.remoteHost + " " + logRec.remoteUserLogname + " " + logRec.authenticatedUserName;
  360.         SessionRecord sr = (SessionRecord)sessions.get(sessionStr);
  361.         
  362.         if(sr == null 
  363.             || (logRec.requestTimestamp - sr.lastTimestamp >= newUserSessionThreshold)) {
  364.  
  365.             // New user session
  366.             sr = new SessionRecord(sessionStr, logRec);
  367.             // Add to linked list of all Session Records
  368.             sr.next = sessionListHead;
  369.             sessionListHead = sr;
  370.             // Update the hashtable which tracks current "live" sessions
  371.             sessions.put(sessionStr, sr);
  372.             totalUserSessionCount++;      // total number of user sessions
  373.  
  374.             // Track userSessions Vs TimeInterval and Vs Day
  375.  
  376.             // Note info for Hits VS Time chart
  377.             hitInterval = timestampToIntervalIndex(logRec.requestTimestamp);
  378.             userSessionsVsTimeInterval[hitInterval]++;
  379.             
  380.             // Note info for Hits VS Day for day-related averages
  381.             hitInterval = timestampToDayIntervalIndex(logRec.requestTimestamp);
  382.             userSessionsVsDay[hitInterval]++;
  383.         }
  384.         // update the session record
  385.         sr.hits++;
  386.         sr.lastTimestamp = logRec.requestTimestamp;
  387.     }
  388.  
  389.     /* determineTimeTillFirstFullInterval()
  390.     Time intervals are aligned on natural boundries (i.e. midnight for a
  391.     day long interval). This method determines the length of the first
  392.     interval such that it ends on a natural boundry. Typically it is smaller 
  393.     than a full interval.
  394.     */
  395.     private static final int CAL_SECOND         = 0;
  396.     private static final int CAL_MINUTE         = 1;
  397.     private static final int CAL_HOUR           = 2;
  398.     private static final int CAL_DAY_OF_WEEK    = 3;
  399.     private static final int CAL_WEEK_OF_YEAR   = 4;
  400.     private static final int CALENDAR_UNIT_VALUES[] = {
  401.         Calendar.SECOND,
  402.         Calendar.MINUTE,
  403.         Calendar.HOUR_OF_DAY,
  404.         Calendar.DAY_OF_WEEK,
  405.         Calendar.WEEK_OF_YEAR
  406.     };
  407.     private long determineTimeTillFirstFullInterval(long MILLISECS_PER_, int CAL_) {
  408.         // Get a calendar and set its time to the report starting time + interval length
  409.         Calendar cal = Calendar.getInstance();
  410.         cal.setTime(new Date(startTimestamp+MILLISECS_PER_));
  411.         // zero the appropriate fields, truncating time to interval natural boundry
  412.         do {
  413.             cal.set(CALENDAR_UNIT_VALUES[CAL_], 0);
  414.         } while(--CAL_ >= 0);
  415.         // Get resultant time, and calculate delta to that time from report starting time
  416.         Date date = cal.getTime();
  417.         long deltaTime = date.getTime() - startTimestamp;
  418.         // done
  419.         return deltaTime;
  420.     }
  421.  
  422.     /*
  423.     Calculates the day interval index that the given time falls into.
  424.     */
  425.     private int timestampToDayIntervalIndex(long timestamp) {
  426.         int intervalIndex = 0;
  427.         long ms = timestamp - startTimestamp;
  428.         if(ms >= timeTillFirstDayInterval) {
  429.             ms -= timeTillFirstDayInterval;
  430.             intervalIndex = (int)(ms / WLAUtil.MILLISECS_PER_DAY) + 1;
  431.         }
  432.         return intervalIndex;
  433.     }
  434.  
  435.     /*
  436.     Calculates the <time interval> index that the given time falls into.
  437.     */
  438.     private int timestampToIntervalIndex(long timestamp) {
  439.         int intervalIndex = 0;
  440.         long ms = timestamp - startTimestamp;
  441.         if(ms >= timeTillFirstInterval) {
  442.             ms -= timeTillFirstInterval;
  443.             intervalIndex = (int)(ms / hitTimeInterval) + 1;
  444.         }
  445.         return intervalIndex;
  446.     }
  447.  
  448.  
  449.     /*
  450.     This class tracks info on a single user session.
  451.     All SessionRecords are kept on a linked-list (sessionListHead refers
  452.     to the most recently created SessionRecord).
  453.     This class implements the Sortable interface to allow the quicksort
  454.     utility routine to sort it.
  455.     */
  456.     class SessionRecord implements Serializable, Sortable {
  457.         // The next SessionRecord in the linked-list.
  458.         SessionRecord next = null;
  459.         // This session's identifying string (a concatenation of the domain 
  460.         // name, remote user log name, and authenticated user name)
  461.         String sessionStr;
  462.         // The number of hits by this session
  463.         int hits;
  464.         // The session's starting time
  465.         long firstTimestamp;
  466.         // The time of this session's last request
  467.         long lastTimestamp;
  468.         
  469.         /*
  470.         Constructs a new session.
  471.         */
  472.         SessionRecord(String sessionStr, LogRecord logRec) {
  473.             this.sessionStr = sessionStr;
  474.             firstTimestamp = logRec.requestTimestamp;
  475.             lastTimestamp = logRec.requestTimestamp;
  476.             hits = 0;
  477.         }
  478.  
  479.         // -- Implement the Sortable interface
  480.         
  481.         // A Sortable interface method.
  482.         // Returns true if this "is before" the given obj
  483.         public boolean isLessThan(Object obj) {
  484.             if(obj instanceof SessionRecord) {
  485.                 SessionRecord r = (SessionRecord)obj;
  486.                 if(hits != r.hits) {
  487.                     return hits < r.hits;
  488.                 }
  489.                 return sessionStr.compareTo(r.sessionStr) > 0;
  490.             }
  491.             return false;
  492.         }
  493.         
  494.         // A Sortable interface method.
  495.         // Returns true if this "is before or equal to" the given obj
  496.         public boolean isLessThanOrEqual(Object obj) {
  497.             if(obj instanceof SessionRecord) {
  498.                 SessionRecord r = (SessionRecord)obj;
  499.                 if(hits != r.hits) {
  500.                     return hits < r.hits;
  501.                 }
  502.                 return sessionStr.compareTo(r.sessionStr) >= 0;
  503.             }
  504.             return false;
  505.         }
  506.     }
  507.  
  508.  
  509.     /*
  510.     This class tracks info on a single user session.
  511.     This class implements the Sortable interface to allow the quicksort
  512.     utility routine to sort it.
  513.     */
  514.     class DomainRecord implements Serializable, Sortable {
  515.         // The domain name
  516.         String domain;
  517.         // The number of hits this domain has generated
  518.         int hits;
  519.         
  520.         // Creates a new DomainRecord with the given name.
  521.         DomainRecord(String domain) {
  522.             this.domain = domain;
  523.         }
  524.  
  525.         // -- Implement the Sortable interface
  526.         
  527.         // A Sortable interface method.
  528.         // Returns true if this "is before" the given obj
  529.         public boolean isLessThan(Object obj) {
  530.             if(obj instanceof DomainRecord) {
  531.                 DomainRecord r = (DomainRecord)obj;
  532.                 if(hits != r.hits) {
  533.                     return hits < r.hits;
  534.                 }
  535.                 return domain.compareTo(r.domain) < 0;
  536.             }
  537.             return false;
  538.         }
  539.         
  540.         // A Sortable interface method.
  541.         // Returns true if this "is before or equal to" the given obj
  542.         public boolean isLessThanOrEqual(Object obj) {
  543.             if(obj instanceof DomainRecord) {
  544.                 DomainRecord r = (DomainRecord)obj;
  545.                 if(hits != r.hits) {
  546.                     return hits < r.hits;
  547.                 }
  548.                 return domain.compareTo(r.domain) <= 0;
  549.             }
  550.             return false;
  551.         }
  552.     }
  553.  
  554.     //{{DECLARE_CONTROLS
  555.     //}}
  556. }
  557.  
  558.