File Source: ProgramError.java

     1  /*
     2   * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
     3   *
     4   * Permission is hereby granted, free of charge, to any person obtaining a copy
     5   * of this software and associated documentation files (the "Software"), to deal
     6   * in the Software without restriction, including without limitation the rights
     7   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8   * copies of the Software, and to permit persons to whom the Software is
     9   * furnished to do so, subject to the following conditions:
    10   *
    11   * The above copyright notice and this permission notice shall be included in
    12   * all copies or substantial portions of the Software.
    13   *
    14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    20   * SOFTWARE.
    21   */
    22  
    23  package com.dmdirc.logger;
    24  
    25  import com.dmdirc.Main;
    26  import com.dmdirc.config.IdentityManager;
    27  import com.dmdirc.util.Downloader;
    28  
    29  import java.io.File;
    30  import java.io.FileOutputStream;
    31  import java.io.IOException;
    32  import java.io.OutputStream;
    33  import java.io.PrintWriter;
    34  import java.io.Serializable;
    35  import java.net.MalformedURLException;
    36  import java.util.ArrayList;
    37  import java.util.Arrays;
    38  import java.util.Date;
    39  import java.util.HashMap;
    40  import java.util.List;
    41  import java.util.Map;
    42  import java.util.concurrent.Semaphore;
    43  
    44  /**
    45   * Stores a program error.
    46   */
    47  public final class ProgramError implements Serializable {
    48      
    49      /**
    50       * A version number for this class. It should be changed whenever the class
    51       * structure is changed (or anything else that would prevent serialized
    52       * objects being unserialized with the new class).
    53       */
    54      private static final long serialVersionUID = 3;
    55  
    56      /** Directory used to store errors. */
    57      private static File errorDir;
    58  
    59      /** Semaphore used to serialise write access. */
             /* 
    P/P       *  Method: com.dmdirc.logger.ProgramError__static_init
              * 
              *  Postconditions:
              *    writingSem == &new Semaphore(ProgramError__static_init#1)
              *    new Semaphore(ProgramError__static_init#1) num objects == 1
              */
    60      private static final Semaphore writingSem = new Semaphore(1);
    61      
    62      /** Error ID. */
    63      private final long id;
    64      
    65      /** Error icon. */
    66      private final ErrorLevel level;
    67      
    68      /** Error message. */
    69      private final String message;
    70      
    71      /** Error trace. */
    72      private final String[] trace;
    73      
    74      /** Date/time error occurred. */
    75      private final Date date;
    76      
    77      /** Error report Status. */
    78      private ErrorReportStatus reportStatus;
    79      
    80      /** Error fixed Status. */
    81      private ErrorFixedStatus fixedStatus;
    82      
    83      /**
    84       * Creates a new instance of ProgramError.
    85       *
    86       * @param id error id
    87       * @param level Error level
    88       * @param message Error message
    89       * @param trace Error trace
    90       * @param date Error time and date
    91       */
    92      public ProgramError(final long id, final ErrorLevel level,
                     /* 
    P/P               *  Method: void com.dmdirc.logger.ProgramError(long, ErrorLevel, String, String[], Date)
                      * 
                      *  Preconditions:
                      *    date != null
                      *    id >= 0
                      *    level != null
                      *    message != null
                      *    trace != null
                      *    trace.length <= 232-1
                      * 
                      *  Presumptions:
                      *    java.lang.String:isEmpty(...)@103 == 0
                      * 
                      *  Postconditions:
                      *    this.date != null
                      *    this.fixedStatus == &amp;com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#7)
                      *    this.id == id
                      *    this.id >= 0
                      *    this.level == level
                      *    this.level != null
                      *    this.message == message
                      *    this.message != null
                      *    this.reportStatus == &amp;com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#6)
                      *    init'ed(this.trace)
                      */
    93              final String message, final String[] trace, final Date date) {
    94          
    95          if (id < 0) {
    96              throw new IllegalArgumentException("ID must be a positive integer: " + id);
    97          }
    98          
    99          if (level == null) {
   100              throw new IllegalArgumentException("Level cannot be null");
   101          }
   102          
   103          if (message == null || message.isEmpty()) {
   104              throw new IllegalArgumentException("Message cannot be null or an empty string");
   105          }
   106          
   107          if (trace == null) {
   108              throw new IllegalArgumentException("Trace cannot be null");
   109          }
   110          
   111          if (date == null) {
   112              throw new IllegalArgumentException("date cannot be null");
   113          }
   114          
   115          this.id = id;
   116          this.level = level;
   117          this.message = message;
   118          this.trace = Arrays.copyOf(trace, trace.length);
   119          this.date = (Date) date.clone();
   120          this.reportStatus = ErrorReportStatus.WAITING;
   121          this.fixedStatus = ErrorFixedStatus.UNKNOWN;
   122      }
   123      
   124      /**
   125       * Returns this errors level.
   126       *
   127       * @return Error level
   128       */
   129      public ErrorLevel getLevel() {
                 /* 
    P/P           *  Method: ErrorLevel getLevel()
                  * 
                  *  Postconditions:
                  *    return_value == this.level
                  *    init'ed(return_value)
                  */
   130          return level;
   131      }
   132      
   133      /**
   134       * Returns this errors message.
   135       *
   136       * @return Error message
   137       */
   138      public String getMessage() {
                 /* 
    P/P           *  Method: String getMessage()
                  * 
                  *  Postconditions:
                  *    return_value == this.message
                  *    init'ed(return_value)
                  */
   139          return message;
   140      }
   141      
   142      /**
   143       * Returns this errors trace.
   144       *
   145       * @return Error trace
   146       */
   147      public String[] getTrace() {
                 /* 
    P/P           *  Method: String[] getTrace()
                  * 
                  *  Preconditions:
                  *    this.trace != null
                  *    this.trace.length <= 232-1
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  */
   148          return Arrays.copyOf(trace, trace.length);
   149      }
   150      
   151      /**
   152       * Returns this errors time.
   153       *
   154       * @return Error time
   155       */
   156      public Date getDate() {
                 /* 
    P/P           *  Method: Date getDate()
                  * 
                  *  Preconditions:
                  *    this.date != null
                  * 
                  *  Postconditions:
                  *    return_value != null
                  */
   157          return (Date) date.clone();
   158      }
   159      
   160      /**
   161       * Returns the reportStatus of this error.
   162       * 
   163       * @return Error reportStatus
   164       */
   165      public ErrorReportStatus getReportStatus() {
                 /* 
    P/P           *  Method: ErrorReportStatus getReportStatus()
                  * 
                  *  Preconditions:
                  *    init'ed(this.reportStatus)
                  * 
                  *  Postconditions:
                  *    return_value == this.reportStatus
                  *    init'ed(return_value)
                  */
   166          return reportStatus;
   167      }
   168      
   169      /**
   170       * Returns the fixed status of this error.
   171       * 
   172       * @return Error fixed status
   173       */
   174      public ErrorFixedStatus getFixedStatus() {
                 /* 
    P/P           *  Method: ErrorFixedStatus getFixedStatus()
                  * 
                  *  Preconditions:
                  *    init'ed(this.fixedStatus)
                  * 
                  *  Postconditions:
                  *    return_value == this.fixedStatus
                  *    init'ed(return_value)
                  */
   175          return fixedStatus;
   176      }
   177      
   178      /**
   179       * Sets the report Status of this error.
   180       * 
   181       * @param newStatus new ErrorReportStatus for the error
   182       */
   183      public void setReportStatus(final ErrorReportStatus newStatus) {
                 /* 
    P/P           *  Method: void setReportStatus(ErrorReportStatus)
                  * 
                  *  Preconditions:
                  *    (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
                  *    (soft) this.reportStatus != null
                  * 
                  *  Postconditions:
                  *    this.reportStatus == One-of{old this.reportStatus, newStatus}
                  *    this.reportStatus != null
                  * 
                  *  Test Vectors:
                  *    newStatus: Addr_Set{null}, Inverse{null}
                  *    com.dmdirc.logger.ErrorReportStatus:equals(...)@184: {1}, {0}
                  */
   184          if (newStatus != null && !reportStatus.equals(newStatus)) {
   185              reportStatus = newStatus;
   186              ErrorManager.getErrorManager().fireErrorStatusChanged(this);
   187  
   188              synchronized (this) {
   189                  notifyAll();
   190              }
   191          }
   192      }
   193      
   194      /**
   195       * Sets the fixed status of this error.
   196       * 
   197       * @param newStatus new ErrorFixedStatus for the error
   198       */
   199      public void setFixedStatus(final ErrorFixedStatus newStatus) {
                 /* 
    P/P           *  Method: void setFixedStatus(ErrorFixedStatus)
                  * 
                  *  Preconditions:
                  *    (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
                  *    (soft) this.fixedStatus != null
                  *    (soft) this.reportStatus != null
                  * 
                  *  Postconditions:
                  *    this.fixedStatus == One-of{old this.fixedStatus, newStatus}
                  *    this.fixedStatus != null
                  * 
                  *  Test Vectors:
                  *    newStatus: Addr_Set{null}, Inverse{null}
                  *    com.dmdirc.logger.ErrorFixedStatus:equals(...)@200: {1}, {0}
                  */
   200          if (newStatus != null && !fixedStatus.equals(newStatus)) {
   201              fixedStatus = newStatus;
   202              ErrorManager.getErrorManager().fireErrorStatusChanged(this);
   203  
   204              synchronized (this) {
   205                  notifyAll();
   206              }
   207          }
   208      }
   209      
   210      /**
   211       * Returns the ID of this error.
   212       *
   213       * @return Error ID
   214       */
   215      public long getID() {
                 /* 
    P/P           *  Method: long getID()
                  * 
                  *  Postconditions:
                  *    return_value == this.id
                  *    init'ed(return_value)
                  */
   216          return id;
   217      }
   218  
   219      /**
   220       * Saves this error to disk.
   221       */
   222      public void save() {
                 /* 
    P/P           *  Method: void save()
                  * 
                  *  Preconditions:
                  *    init'ed(errorDir)
                  *    this.date != null
                  *    this.trace != null
                  *    this.trace.length <= 232-1
                  * 
                  *  Presumptions:
                  *    arr$.length <= 232-1
                  *    java.util.Arrays:copyOf(...)@148 != null
                  * 
                  *  Postconditions:
                  *    errorDir == One-of{old errorDir, &amp;new File(getErrorFile#1)}
                  *    errorDir != null
                  *    new File(getErrorFile#1) num objects <= 1
                  */
   223          final PrintWriter out = new PrintWriter(getErrorFile(), true);
   224          out.println("Date:" + getDate());
   225          out.println("Level: " + getLevel());
   226          out.println("Description: " + getMessage());
   227          out.println("Details:");
   228  
   229          for (String traceLine : getTrace()) {
   230              out.println('\t' + traceLine);
   231          }
   232          
   233          out.close();
   234      }
   235  
   236      /**
   237       * Creates a new file for an error and returns the output stream.
   238       *
   239       * @return BufferedOutputStream to write to the error file
   240       */
   241      @SuppressWarnings("PMD.SystemPrintln")
   242      private OutputStream getErrorFile() {
                 /* 
    P/P           *  Method: OutputStream getErrorFile()
                  * 
                  *  Preconditions:
                  *    init'ed(errorDir)
                  *    this.date != null
                  * 
                  *  Presumptions:
                  *    java.lang.System.err != null
                  * 
                  *  Postconditions:
                  *    errorDir == One-of{old errorDir, &amp;new File(getErrorFile#1)}
                  *    errorDir != null
                  *    return_value in Addr_Set{&amp;new FileOutputStream(getErrorFile#8),&amp;new NullOutputStream(getErrorFile#9)}
                  *    new File(getErrorFile#1) num objects <= 1
                  *    new FileOutputStream(getErrorFile#8) num objects <= 1
                  *    new NullOutputStream(getErrorFile#9) num objects <= 1
                  * 
                  *  Test Vectors:
                  *    errorDir: Addr_Set{null}, Inverse{null}
                  *    java.io.File:exists(...)@245: {1}, {0}
                  *    java.io.File:exists(...)@247: {1}, {0}
                  *    java.io.File:exists(...)@256: {0}, {1}
                  */
   243          writingSem.acquireUninterruptibly();
   244          
   245          if (errorDir == null || !errorDir.exists()) {
   246              errorDir = new File(Main.getConfigDir() + "errors");
   247              if (!errorDir.exists()) {
   248                  errorDir.mkdirs();
   249              }
   250          }
   251  
   252          final String logName = getDate().getTime() + "-" + getLevel();
   253  
   254          final File errorFile = new File(errorDir, logName + ".log");
   255  
   256          if (errorFile.exists()) {
   257              boolean rename = false;
   258              int i = 0;
   259              while (!rename) {
   260                  i++;
   261                  rename = errorFile.renameTo(new File(errorDir, logName + "-" + i + ".log"));
   262              }
   263          }
   264  
   265          try {
   266              errorFile.createNewFile();
   267              return new FileOutputStream(errorFile);
   268          } catch (IOException ex) {
   269              System.err.println("Error creating new file: ");
   270              ex.printStackTrace();
   271              return new NullOutputStream();
   272          } finally {
   273              writingSem.release();
   274          }
   275      }
   276  
   277      /**
   278       * Sends this error report to the DMDirc developers.
   279       */
   280      public void send() {
                 /* 
    P/P           *  Method: void send()
                  * 
                  *  Preconditions:
                  *    this.trace != null
                  *    this.trace.length <= 232-1
                  *    (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
                  *    (soft) this.fixedStatus != null
                  *    (soft) this.reportStatus != null
                  * 
                  *  Presumptions:
                  *    com.dmdirc.config.IdentityManager:getGlobalConfig(...)@287 != null
                  *    com.dmdirc.util.Downloader:getPage(...)@300 != null
                  *    java.util.List:get(...)@310 != null
                  *    java.util.List:size(...)@310 >= -231+1
                  *    this.fixedStatus@294 != null
                  *    ...
                  * 
                  *  Postconditions:
                  *    this.fixedStatus != null
                  *    this.reportStatus != null
                  * 
                  *  Test Vectors:
                  *    java.lang.String:equalsIgnoreCase(...)@310: {1}, {0}
                  *    java.util.List:isEmpty(...)@310: {1}, {0}
                  */
   281          final Map<String, String> postData = new HashMap<String, String>();
   282          List<String> response = new ArrayList<String>();
   283          int tries = 0;
   284  
   285          postData.put("message", getMessage());
   286          postData.put("trace", Arrays.toString(getTrace()));
   287          postData.put("version", IdentityManager.getGlobalConfig().getOption("version", "version"));
   288  
   289          setReportStatus(ErrorReportStatus.SENDING);
   290  
   291          do {
   292              if (tries != 0) {
   293                  try {
   294                      Thread.sleep(5000);
   295                  } catch (InterruptedException ex) {
   296                      //Ignore
   297                  }
   298              }
   299              try {
   300                  response = Downloader.getPage("http://www.dmdirc.com/error.php", postData);
   301              } catch (MalformedURLException ex) {
   302                  //Ignore, wont happen
   303              } catch (IOException ex) {
   304                  //Ignore being handled
   305              }
   306  
   307              tries++;
   308          } while ((response.isEmpty() || !response.get(response.size() - 1).
   309                  equalsIgnoreCase("Error report submitted. Thank you."))
   310                  && tries <= 5);
   311  
   312          checkResponses(response);
   313      }
   314  
   315      /**
   316       * Checks the responses and sets status accordingly.
   317       *
   318       * @param error Error to check response
   319       * @param response Response to check
   320       */
   321      private void checkResponses(final List<String> response) {
                 /* 
    P/P           *  Method: void checkResponses(List)
                  * 
                  *  Preconditions:
                  *    response != null
                  *    (soft) init'ed(com.dmdirc.ui.FatalErrorDialog$4__static_init.new int[](FatalErrorDialog$4__static_init#1)[...])
                  *    (soft) this.fixedStatus != null
                  *    (soft) this.reportStatus != null
                  * 
                  *  Presumptions:
                  *    java.util.List:get(...)@322 != null
                  *    java.util.List:get(...)@335 != null
                  *    java.util.List:size(...)@322 >= -231+1
                  * 
                  *  Postconditions:
                  *    this.fixedStatus == One-of{old this.fixedStatus, &amp;com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#1), &amp;com.dmdirc.logger.ErrorFixedStatus__static_init.new ErrorFixedStatus(ErrorFixedStatus__static_init#3), &amp;com.dmdirc.logger....
                  *    this.fixedStatus != null
                  *    this.reportStatus == One-of{old this.reportStatus, &amp;com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#4), &amp;com.dmdirc.logger.ErrorReportStatus__static_init.new ErrorReportStatus(ErrorReportStatus__static_init#2)}
                  *    this.reportStatus != null
                  * 
                  *  Test Vectors:
                  *    java.lang.String:equalsIgnoreCase(...)@322: {0}, {1}
                  *    java.lang.String:matches(...)@336: {0}, {1}
                  *    java.lang.String:matches(...)@338: {0}, {1}
                  *    java.lang.String:matches(...)@340: {0}, {1}
                  *    java.lang.String:matches(...)@342: {0}, {1}
                  *    java.util.List:isEmpty(...)@322: {1}, {0}
                  *    java.util.List:size(...)@330: {-231..0, 2..232-1}, {1}
                  */
   322          if (!response.isEmpty() && response.get(response.size() - 1).
   323                  equalsIgnoreCase("Error report submitted. Thank you.")) {
   324              setReportStatus(ErrorReportStatus.FINISHED);
   325          } else {
   326              setReportStatus(ErrorReportStatus.ERROR);
   327              return;
   328          }
   329  
   330          if (response.size() == 1) {
   331              setFixedStatus(ErrorFixedStatus.NEW);
   332              return;
   333          }
   334  
   335          final String responseToCheck = response.get(0);
   336          if (responseToCheck.matches(".*fixed.*")) {
   337              setFixedStatus(ErrorFixedStatus.FIXED);
   338          } else if (responseToCheck.matches(".*more recent version.*")) {
   339              setFixedStatus(ErrorFixedStatus.TOOOLD);
   340          } else if (responseToCheck.matches(".*invalid.*")) {
   341              setFixedStatus(ErrorFixedStatus.INVALID);
   342          } else if (responseToCheck.matches(".*previously.*")) {
   343              setFixedStatus(ErrorFixedStatus.KNOWN);
   344          } else {
   345              setFixedStatus(ErrorFixedStatus.NEW);
   346          }
   347      }
   348  
   349      /**
   350       * Determines whether or not the stack trace associated with this error
   351       * is from a valid source. A valid source is one that is within a DMDirc
   352       * package (com.dmdirc), and is not the DMDirc event queue.
   353       *
   354       * @return True if the source is valid, false otherwise
   355       */
   356      public boolean isValidSource() {
                 /* 
    P/P           *  Method: bool isValidSource()
                  * 
                  *  Preconditions:
                  *    this.trace != null
                  *    this.trace.length in {1..232-1}
                  *    (soft) this.trace[0] != null
                  *    (soft) this.trace[...] != null
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  */
   357          final String line = getSourceLine();
   358  
   359          return line.startsWith("com.dmdirc")
   360                  && !line.startsWith("com.dmdirc.addons.ui_swing.DMDircEventQueue");
   361      }
   362  
   363      /**
   364       * Returns the "source line" of this error, which is defined as the first
   365       * line starting with a DMDirc package name (com.dmdirc). If no such line
   366       * is found, returns the first line of the message.
   367       *
   368       * @return This error's source line
   369       */
   370      public String getSourceLine() {
                 /* 
    P/P           *  Method: String getSourceLine()
                  * 
                  *  Preconditions:
                  *    this.trace != null
                  *    this.trace.length in {1..232-1}
                  *    (soft) this.trace[0] != null
                  *    (soft) this.trace[...] != null
                  * 
                  *  Postconditions:
                  *    return_value != null
                  * 
                  *  Test Vectors:
                  *    java.lang.String:startsWith(...)@372: {0}, {1}
                  */
   371          for (String line : trace) {
   372              if (line.startsWith("com.dmdirc")) {
   373                  return line;
   374              }
   375          }
   376  
   377          return trace[0];
   378      }
   379      
   380      /** {@inheritDoc} */
   381      @Override
   382      public String toString() {
                 /* 
    P/P           *  Method: String toString()
                  * 
                  *  Preconditions:
                  *    init'ed(this.reportStatus)
                  * 
                  *  Postconditions:
                  *    init'ed(java.lang.StringBuilder:toString(...)._tainted)
                  *    return_value == &amp;java.lang.StringBuilder:toString(...)
                  */
   383          return "ID" + id + " Level: " + getLevel() + " Status: " + getReportStatus()
   384          + " Message: '" + getMessage() + "'";
   385      }
   386  
   387      /** {@inheritDoc} */
   388      @Override
   389      public boolean equals(final Object obj) {
                 /* 
    P/P           *  Method: bool equals(Object)
                  * 
                  *  Preconditions:
                  *    (soft) this.message != null
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  * 
                  *  Test Vectors:
                  *    obj: Inverse{null}, Addr_Set{null}
                  *    this.level == obj.level: {1}, {0}
                  *    java.lang.String:equals(...)@403: {1}, {0}
                  *    java.util.Arrays:equals(...)@407: {1}, {0}
                  */
   390          if (obj == null) {
   391              return false;
   392          }
   393          
   394          if (getClass() != obj.getClass()) {
   395              return false;
   396          }
   397          
   398          final ProgramError other = (ProgramError) obj;
   399          if (this.level != other.level) {
   400              return false;
   401          }
   402          
   403          if (!this.message.equals(other.message)) {
   404              return false;
   405          }
   406          
   407          if (!Arrays.equals(this.trace, other.trace)) {
   408              return false;
   409          }
   410          
   411          return true;
   412      }
   413  
   414      /** {@inheritDoc} */
   415      @Override
   416      public int hashCode() {
                 /* 
    P/P           *  Method: int hashCode()
                  * 
                  *  Preconditions:
                  *    this.level != null
                  *    this.message != null
                  * 
                  *  Presumptions:
                  *    (com.dmdirc.logger.ErrorLevel:hashCode(...)@418*67 + java.lang.String:hashCode(...)@419)*67 + java.util.Arrays:hashCode(...)@420 in {-2_149_588_989..4_292_861_954}
                  *    com.dmdirc.logger.ErrorLevel:hashCode(...)@418 in {-65_539_622..33_486_689}
                  *    com.dmdirc.logger.ErrorLevel:hashCode(...)@418*67 + java.lang.String:hashCode(...)@419 in {-96_187_407..96_124_561}
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  */
   417          int hash = 7;
   418          hash = 67 * hash + this.level.hashCode();
   419          hash = 67 * hash + this.message.hashCode();
   420          hash = 67 * hash + Arrays.hashCode(this.trace);
   421          return hash;
   422      }
   423      
   424  }








SofCheck Inspector Build Version : 2.17854
ProgramError.java 2009-Jun-25 01:54:24
ProgramError.class 2009-Sep-02 17:04:15