/* diag.c: MEMORY POOL MANAGER DIAGNOSTICS
 *
 * $Id: //info.ravenbrook.com/project/mps/version/1.111/code/diag.c#1 $
 * Copyright (c) 2007 Ravenbrook Limited.  See end of file for license.
 *
 * To Do: [RHSK 2007-08-13]
 *  @@ sigs and AVERTs for Rule, and macro for Rules initializer
 *  @@ deprecate un-tagged diags, remove old macros
 *  @@ every diag should end with \n: warn if this is missing.
 */

#include <stdarg.h>

#include "mpm.h"
#include "mpslib.h" /* for mps_lib_stdout */

#if defined(DIAG_WITH_STREAM_AND_WRITEF)

typedef struct RuleStruct {
  const char *action;
  const char *tag;
  const char *para;
  const char *line;
  int tpMatch;  /* .tpmatch */
  /* @@ needs sig; (at end, to make initializer expression easy?) */
} *Rule;


/* RulesGlobal -- throw away some diags (see INSTRUCTIONS below) */

struct RuleStruct RulesGlobal[] = {
  { "-", "*", "*", "*" },
  { "+", "DiagFilter_Rules", "*", "*" },
  { "+", "VMCompact", "*", "*" },
  /* ----v---- always on please (RHSK) ----v---- */
  { "+", "MPSVersion", "*", "*" },
  { "+", "traceSetSignalEmergency", "*", "*" },
  { NULL, "", "", "" }
};

struct RuleStruct RulesGlobal_RHSK[] = {
  { "+", "*", "*", "*" },
  { "+", "DiagFilter_Rules", "*", "*" },
  { "-", "DIAGTEST_", "*", "*" },
  { "+", "AMCTraceEnd_pageret", "*", "*" },
  { "-", "ChainCondemnAuto", "*", "*" },
  { "+", "VM_ix_", "*", "*" },
  { "-", "vmArenaExtend_", "*", "*" },
  { "-", "traceFindGrey", "*", "*" },
  { "-", "TraceStart", "*", "*" },
  { "+", "TraceStart", "*", "controlPool" },
  { "+", "TraceStart", "*", "reserved" },
  { "+", "TraceStart", "*", "committed" },
  { "+", "TraceStart", "*", "genZoneSet" },
  { "-", "TraceStart", "because code 1", "*" },
  { "+", "VMCompact", "*", "*" },
  { "-", "VMCompact_hex", "*", "*" },
  { "+", "VM_ix_Create", "*", "*" },
  /* ----v---- always on please (RHSK) ----v---- */
  { "+", "traceSetSignalEmergency", "*", "*" },
  { NULL, "", "", "" }
};

struct RuleStruct RulesGlobalExample[] = {
  { "+", "*", "*", "*" },
  { "+", "DiagFilter_Rules", "*", "*" },
  { "-", "DIAGTEST_", "*", "*" },
  { "+", "ChainCondemnAuto", "gens [0..0]", "*" },
  { "+", "TraceStart", "*", "*" },
  { "+", "TraceStart", "because code 1:", "*" },
  { "-", "TraceStart", "*", "controlPool" },
  { "-", "TraceStart", "*", "ommit" },
  { "-", "TraceStart", "*", "zoneShift" },
  { "-", "TraceStart", "*", "alignment" },
  { "-", "amcScanNailed-loop", "*", "*" },
  { NULL, "", "", "" }
};

/* RulesGlobal -- INSTRUCTIONS
 *
 * In your local copy of diag.c, you can modify RulesGlobal as you 
 * wish, to control what diags you see.
 *
 * Each rule consists of: action, tag, para, and line.  A rule that 
 * matches on TAG, PARA and LINE determines what ACTION is taken 
 * for that line of that diag.  Later rules override earlier rules, 
 * ie. the lowest matching rule wins.  (And at least one rule must 
 * match, so the first rule should be a catch-all).
 *
 * ACTION = "+" (output this line of diag), or "-" (skip this line).
 *
 * TAG: does pattern (text or *) appear in diag's tag?
 *
 * PARA: does pattern (text or *) appear anywhere in diag's text output
 * (does not match the tag)?
 *
 * LINE: does pattern (text or *) appear on this line of the diag
 * text?
 *
 * Note: a diag that deliberately has no output, eg.
 *     DIAG_SINGLEF(( "MyTag", NULL )),
 * is treated as having a single empty 'line'.  See .empty-diag.
 *
 * Note: for help debugging your ruleset, see .rules.debug below.
 *
 * Note: the entire filtering mechanism can be turned off, so that 
 * diagnostics go immediately to mps_lib_stdout: see .filter-disable.
 */


/* Forward declarations */

static mps_lib_FILE *filterStream(void);
static int filterStream_fputc(int c, mps_lib_FILE *stream);
static int filterStream_fputs(const char *s, mps_lib_FILE *stream);
static void diag_test(void);


/* Stream -- output to filterStream or to a real mps_lib_FILE stream
 *
 * There are only two functions and two destinations; a full class 
 * hierarchy would be overkill!  RHSK 2007-08-08.
 */

int Stream_fputc(int c, mps_lib_FILE *stream)
{
  if(stream == filterStream())
    return filterStream_fputc(c, stream);
  else
    return mps_lib_fputc(c, stream);
}

int Stream_fputs(const char *s, mps_lib_FILE *stream)
{
  if(stream == filterStream())
    return filterStream_fputs(s, stream);
  else
    return mps_lib_fputs(s, stream);
}


/* Diag -- a buffer to store a diagnostic
 *
 */

#define DiagSig       ((Sig)0x519D1A99)  /* SIGnature DIAG */

typedef struct DiagStruct {
  Sig sig;
  const char  *tag;
  Bool overflow;  /* diag > buf? set flag, truncate, force output */
  Count n;
  char buf[DIAG_BUFFER_SIZE];
} *Diag;

static Bool DiagCheck(Diag diag)
{
  CHECKS(Diag, diag);
  CHECKL(diag->n <= sizeof(diag->buf));
  return TRUE;
}



/* filterStream -- capable of filtering diagnostics
 *
 * This is not really an mps_lib_FILE*; it is a single global instance 
 * of a DiagStruct.
 *
 * Output is stored in a DiagStruct, to be filtered and output
 * (or not) when complete.
 */

static struct DiagStruct filterDiagGlobal = { DiagSig, NULL, FALSE, 0 };

static mps_lib_FILE *filterStream(void)
{
  return (mps_lib_FILE*)&filterDiagGlobal;
}

/* filterStream_under: the underlying stream used to output diags */
/* that pass the filter. */
static mps_lib_FILE *filterStream_under(void)
{
  return mps_lib_stdout;
}

/* .tpmatch: does this rule match current diag's tag and para? */
enum {
  TPMatch_Unknown = 0,  /* initial value = 0 */
  TPMatch_Yes,
  TPMatch_No
};

static void version_diag(void)
{
  DIAG_SINGLEF(( "MPSVersion",
    "$S", (WriteFS)MPSVersion(), NULL ));
}

static void rules_diag(Rule rules)
{
  Index ir;
  
  AVER(rules);
  DIAG_FIRSTF(( "DiagFilter_Rules", 
    "Only showing diags permitted by these tag/paragraph/line"
    " rules:\n", NULL ));
  for(ir = 0; rules[ir].action != NULL; ir++) {
    DIAG_DECL( Rule rule = &rules[ir]; )
    DIAG_MOREF(( "$S$S/$S/$S\n", (WriteFS)rule->action, (WriteFS)rule->tag, 
                 (WriteFS)rule->para, (WriteFS)rule->line,
                 NULL ));
  }
  DIAG_END("DiagFilter_Rules");
}


/* patternOccurs -- does patt occur in buf[i..j)?
 *
 * Returns true iff patt[0..pattLen) literally occurs in buf[i..j).
 */

static Bool patternOccurs(const char *patt, Count pattLen, 
                          const char *buf, Index i, Index j)
{
  Index im; /* start of tentative match */
  Index ip; /* index into patt */

  AVER(patt);
  AVER(buf);
  AVER(i <= j);

  /* Search (naively) for patt anywhere inside buf[i..j) */
  for(im = i; im + pattLen <= j; im++) {
    /* Consider upto pattLen chars starting at patt[0] and buf[im] */
    for(ip = 0; ip < pattLen; ip++) {
      if(patt[ip] != buf[im + ip])
        break;
    }
    if(ip == pattLen) {
      return TRUE;
    }
  }

  return FALSE;
}

static Bool matchLine(Rule rule, Diag diag, Index i, Index j)
{
  AVER(rule);
  AVER(diag);
  AVER(i <= j);
  AVER(j <= diag->n);

  if(rule->line[0] == '*')
    return TRUE;

  return patternOccurs(rule->line, StringLength(rule->line),
                       diag->buf, i, j);
}

static Bool matchPara(Rule rule, Diag diag)
{
  AVER(rule);
  AVER(diag);

  if(rule->para[0] == '*')
    return TRUE;
  
  return patternOccurs(rule->para, StringLength(rule->para),
                       diag->buf, 0, diag->n);
}

static Bool matchTag(Rule rule, const char *tag)
{
  AVER(rule);
  AVER(rule->tag);
  AVER(tag);

  if(rule->tag[0] == '*')
    return TRUE;

  return patternOccurs(rule->tag, StringLength(rule->tag),
                       tag, 0, StringLength(tag));
}

static void filterStream_LineOut(Diag diag, Index i, Index j)
{
  int r;

  AVER(diag);
  AVER(i <= j);
  AVER(j <= diag->n);
  
  r = Stream_fputs(DIAG_PREFIX_LINE, filterStream_under());
  AVER(r != mps_lib_EOF);
  
  for(; i < j; i++) {
    char c;
    c = diag->buf[i];
    r = Stream_fputc(c, filterStream_under());
    AVER(r != mps_lib_EOF);
  }
}


/* filterStream_Output -- output this diag, if the rules select it
 *
 */

static void filterStream_Output(Diag diag, Rule rules)
{
  static Bool inside = FALSE;
  Res res;
  Count nr;
  Index ir;
  Index i, j;
  Bool nolinesyet = TRUE;
  Bool emptyonce;
  
  AVER(!inside);
  inside = TRUE;
  AVER(diag);
  AVER(rules);
  
  if(diag->tag == NULL)
    diag->tag = "(no tag)";

  /* Count the rules */
  for(ir = 0; rules[ir].action != NULL; ir++) {
    rules[ir].tpMatch = TPMatch_Unknown;
  }
  nr = ir;
  
  /* Filter */
  /* .empty-diag: Treat a diag that deliberately has no output, */
  /* eg: DIAG_SINGLEF(( "Tag", NULL )), as having a single empty */
  /* 'line'.  This is the only time a line may be empty. */
  emptyonce = (diag->n == 0);
  for(i = 0; emptyonce || i < diag->n; i = j) {
    
    /* Get the next line [i..j) */
    for(j = i; j < diag->n; j++) {
      if(diag->buf[j] == '\n') {
        j++;
        break;
      }
    }
    AVER(emptyonce || i < j);  /* .empty-diag */
    emptyonce = FALSE;

    /* Find the lowest rule that matches it. */
    ir = nr - 1;
    for(;;) {
      Rule rule = &rules[ir];
      if(rule->tpMatch == TPMatch_Unknown) {
        /* memoize .tpMatch */
        if(matchTag(rule, diag->tag) && matchPara(rule, diag)) {
          rule->tpMatch = TPMatch_Yes;
        } else {
          rule->tpMatch = TPMatch_No;
        }
      }
      if(rule->tpMatch == TPMatch_Yes && matchLine(rule, diag, i, j))
        break;
      AVER(ir != 0); /* there must ALWAYS be a matching rule */
      ir--;
    }
    
    /* Do the rule's action. */
    if(0) {
      /* .rules.debug: Turn this on to show which rule applied. */
      Rule rule = &rules[ir];
      (void) WriteF(filterStream_under(), "[$U/$U:", ir, nr, 
                    " $S$S/$S/$S] ", rule->action, rule->tag, 
                    rule->para, rule->line, NULL);
    }
    if(rules[ir].action[0] == '+' || diag->overflow) {
      if(nolinesyet) {
        res = WriteF(filterStream_under(),
                     DIAG_PREFIX_TAGSTART "$S {", (WriteFS)diag->tag, NULL);
        AVER(res == ResOK);
        nolinesyet = FALSE;
      }
      filterStream_LineOut(diag, i, j);
    }
  }

  if(diag->overflow) {
    res = WriteF(filterStream_under(),
      "\n--- diagnostic too large: "
      "forced to output, but truncated here ---\n"
      "--- (for a bigger buffer, change DIAG_BUFFER_SIZE) ---\n", NULL);
    AVER(res == ResOK);
  }
  if(!nolinesyet) {
    res = WriteF(filterStream_under(), DIAG_PREFIX_TAGEND "}\n", NULL);
    AVER(res == ResOK);
  }
  inside = FALSE;
}

static void filterStream_TagBegin(mps_lib_FILE *stream, const char *tag)
{
  static Bool first = TRUE;
  Diag diag;

  AVER(stream);
  AVER(tag);

  diag = (Diag)stream;
  AVERT(Diag, diag);

  if(first) {
    first = FALSE;
    version_diag();
    rules_diag(&RulesGlobal[0]);
    diag_test();
  }

  if(diag->tag != NULL) {
    /* Be helpful to the poor programmer! */
    (void) WriteF(filterStream_under(),
                  "\nWARNING: diag tag \"$S\" is still current"
                  " (missing DIAG_END()).", 
                  diag->tag, NULL);  
  }
  AVER(diag->tag == NULL);

  /* @@ when all diags are tagged, the buffer must be empty */
  /* @@ but for now, as a courtesy... */
  if(diag->n > 0) {
    filterStream_Output(diag, &RulesGlobal[0]);
    diag->n = 0;
  }

  diag->tag = tag;
  diag->overflow = FALSE;
  AVER(diag->n == 0);
}

static void filterStream_TagEnd(mps_lib_FILE *stream, const char *tag)
{
  Diag diag;
  diag = (Diag)stream;
  AVERT(Diag, diag);

  AVER(diag->tag != NULL);

  if(!StringEqual(diag->tag, tag)) {
    /* Be helpful to the poor programmer! */
    (void) WriteF(filterStream_under(),
                  "\nWARNING: diag tag \"$S\" is current, "
                  "but got DIAG_END(\"$S\").  (They must match).", 
                  (WriteFS)diag->tag, (WriteFS)tag, NULL);
  }
  AVER(StringEqual(diag->tag, tag));

  /* Output the diag */
  filterStream_Output(diag, &RulesGlobal[0]);

  diag->tag = NULL;
  diag->n = 0;
}

static int filterStream_fputc(int c, mps_lib_FILE *stream)
{
  Diag diag;
  
  AVER(c != mps_lib_EOF);
  AVER(stream == filterStream());

  diag = (Diag)stream;
  AVERT(Diag, diag);
  /* @@ when all diags are tagged: AVER(diag->tag != NULL); */

  /* AVER(diag->n + 1 <= sizeof(diag->buf)); */
  if(!(diag->n + 1 <= sizeof(diag->buf))) {
    diag->overflow = TRUE;
    /* ignore failure; do not return mps_lib_EOF */
    return c;
  }
  
  /* add c to buffer */
  diag->buf[diag->n++] = (char)c;
  return c;
}

static int filterStream_fputs(const char *s, mps_lib_FILE *stream)
{
  Diag diag;
  Count l;
  Index i;
  
  AVER(s);
  AVER(stream == filterStream());

  diag = (Diag)stream;
  AVERT(Diag, diag);
  /* @@ when all diags are tagged: AVER(diag->tag != NULL); */

  l = StringLength(s);
  
  /* AVER(diag->n + l <= sizeof(diag->buf)); */
  if(!(diag->n + l <= sizeof(diag->buf))) {
    diag->overflow = TRUE;
    /* ignore failure; do not return mps_lib_EOF */
    return 1;
  }

  /* add s to buffer */
  for (i = 0; i < l; i++) {
    diag->buf[diag->n++] = s[i];
  }
  return 1;
}


/* DIAG_WITH_STREAM_AND_WRITEF -- Diagnostic output channel
 *
 * Only used for DIAG_WITH_STREAM_AND_WRITEF; see config.h.
 */

Bool DiagEnabledGlobal = TRUE;

Bool DiagIsOn(void)
{
  return DiagEnabledGlobal;
}

mps_lib_FILE *DiagStream(void)
{
  /* .filter-disable: the entire filtering mechanism can be turned */
  /* off, so that diagnostics go immediately to mps_lib_stdout, */
  /* with no buffering or filtering. */
  Bool filter = TRUE;

  if(filter) {
    return filterStream();
  } else {
    return mps_lib_stdout;
  }
}

static void diagTagBegin(mps_lib_FILE *stream, const char *tag)
{
  AVER(stream);
  AVER(tag);

  if(stream == filterStream()) {
    filterStream_TagBegin(stream, tag);
  } else {
    Res res;
    res = WriteF(stream, DIAG_PREFIX_TAGSTART "$S {\n", (WriteFS)tag, NULL);
    AVER(res == ResOK);
  }
}

static void diagTagEnd(mps_lib_FILE *stream, const char *tag)
{
  AVER(stream);
  AVER(tag);

  if(stream == filterStream()) {
    filterStream_TagEnd(stream, tag);
  } else {
    Res res;
    res = WriteF(stream, DIAG_PREFIX_TAGEND "}\n", tag, NULL);
    AVER(res == ResOK);
  }
}


/* Diag*F functions -- interface for general MPS code (via macros)
 *
 * These function manage TagBegin/End, and WriteF the text to 
 * DiagStream().
 *
 * Direct writing to DiagStream() is also permitted (eg. from a
 * Describe method).
 */

void DiagSingleF(const char *tag, ...)
{
  va_list args;
  Res res;

  diagTagBegin(DiagStream(), tag);

  va_start(args, tag);
  res = WriteF_v(DiagStream(), args);
  AVER(res == ResOK);
  va_end(args);

  diagTagEnd(DiagStream(), tag);
}

void DiagFirstF(const char *tag, ...)
{
  va_list args;
  Res res;

  diagTagBegin(DiagStream(), tag);

  va_start(args, tag);
  res = WriteF_v(DiagStream(), args);
  AVER(res == ResOK);
  va_end(args);
}

void DiagMoreF(const char *firstformat, ...)
{
  va_list args;
  Res res;

  /* ISO C says there must be at least one named parameter: hence */
  /* the named firstformat.  It only looks different: there is no */
  /* change from the expected WriteF protocol.  (In particular, */
  /* firstformat may legally be NULL, with the variable part empty). */

  va_start(args, firstformat);
  res = WriteF_firstformat_v(DiagStream(), firstformat, args);
  AVER(res == ResOK);
  va_end(args);
}

void DiagEnd(const char *tag)
{
  diagTagEnd(DiagStream(), tag);
}


/* Test Code -- unit tests for this source file
 *
 * These are for developers to run if they modify this source file.
 * There's no point running them otherwise.  RHSK.
 */

static void patternOccurs_test(Bool expect, const char *patt,
                               const char *text)
{
  Count pattLen = StringLength(patt);
  Count textLen = StringLength(text);
  enum {bufLen = 100};
  char buf[bufLen];
  Index start, i;
  Count padLen;
  Bool occurs;

  /* Call patternOccurs with this patt and text 3 times: each time */
  /* putting the text in the buffer at a different offset, to */
  /* verify that patternOccurs is not accepting matches outside the */
  /* [i..j) portion of the buffer. */
  
  for(start = 0; start < 21; start += 7) {
    AVER(bufLen > (start + textLen));
    /* put text into buf at start */
    for(i = 0; i < start; i++) {
      buf[i] = 'X';
    }
    for(i = 0; i < textLen; i++) {
      (buf+start)[i] = text[i];
    }
    padLen = bufLen - (start + textLen);
    for(i = 0; i < padLen; i++) {
      (buf+start+textLen)[i] = 'X';
    }
    occurs = patternOccurs(patt, pattLen, buf, start, start+textLen);
    AVER(occurs == expect);
  }
}

static void diag_test(void)
{
  DIAG_SINGLEF(( "DIAGTEST_Tag1", "text $U.\n", (WriteFU)42, NULL ));

  DIAG_SINGLEF(( "DIAGTEST_EmptyDiag", NULL ));

  DIAG_FIRSTF((
    "DIAGTEST_StringEqual",
    "Fred = Fred: $U.\n",
    StringEqual("Fred", "Fred"),
    NULL
  ));
  DIAG_MOREF(("Fred = Tom: $U.\n", (WriteFU)StringEqual("Fred", "Tom"), NULL));
  DIAG_MOREF(("Tom = Fred: $U.\n", (WriteFU)StringEqual("Tom", "Fred"), NULL));
  DIAG_MOREF(("0 = Fred: $U.\n", (WriteFU)StringEqual("", "Fred"), NULL));
  DIAG_MOREF(("Fred = 0: $U.\n", (WriteFU)StringEqual("Fred", ""), NULL));
  DIAG_MOREF(("0 = 0: $U.\n", (WriteFU)StringEqual("", ""), NULL));
  DIAG_MOREF(("0 = 000: $U.\n", (WriteFU)StringEqual("", "\0\0"), NULL));
  DIAG_END("DIAGTEST_StringEqual");

  DIAG_FIRSTF(( "DIAGTEST_patternOccurs", NULL ));
  patternOccurs_test(TRUE, "Fred", "Fred");
  patternOccurs_test(TRUE, "Fred", "XFredX");
  patternOccurs_test(TRUE, "Fred", "FFred");
  patternOccurs_test(TRUE, "Fred", "FrFred");
  patternOccurs_test(TRUE, "Fred", "FreFred");
  patternOccurs_test(TRUE, "Fred", "FreFreFFred");
  patternOccurs_test(TRUE, "Fred", "FredFred");
  patternOccurs_test(TRUE, "Fred", "FFredFre");
  patternOccurs_test(TRUE, "Fred", "FrFredFr");
  patternOccurs_test(TRUE, "Fred", "FreFredF");
  patternOccurs_test(TRUE, "Fred", "FreFreFFredFre");
  patternOccurs_test(TRUE, "Fred", "FredFredF");
  patternOccurs_test(TRUE, "X", "X");
  patternOccurs_test(TRUE, "", "X");
  patternOccurs_test(TRUE, "", "Whatever");
  patternOccurs_test(FALSE, "Fred", "Tom");
  patternOccurs_test(FALSE, "X", "Tom");
  patternOccurs_test(FALSE, "X", "x");
  patternOccurs_test(FALSE, "X", "");
  patternOccurs_test(FALSE, "Whatever", "");
  patternOccurs_test(FALSE, "Fred", "Fre");
  patternOccurs_test(FALSE, "Fred", "red");
  patternOccurs_test(FALSE, "Fred", "Fxred");
  patternOccurs_test(FALSE, "Fred", "Frexd");
  DIAG_END("DIAGTEST_patternOccurs");

#if 0
  DIAG_FIRSTF(( "TestTag2", "text $U.\n", (WriteFU)42, NULL ));
  DIAG_MOREF(( NULL ));
  DIAG_MOREF(( "string $S.\n", (WriteFS)"fooey!", NULL ));
  DIAG_MOREF(( NULL ));
  DIAG_MOREF(( "Another string $S.\n", (WriteFS)"baloney!", NULL ));
  DIAG_END( "TestTag2" );
#endif
}

#endif /* DIAG_WITH_STREAM_AND_WRITEF */


/* C. COPYRIGHT AND LICENSE
 *
 * Copyright (C) 2007 Ravenbrook Limited <http://www.ravenbrook.com/>.
 * All rights reserved.  This is an open source license.  Contact
 * Ravenbrook for commercial licensing options.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * 3. Redistributions in any form must be accompanied by information on how
 * to obtain complete source code for this software and any accompanying
 * software that uses this software.  The source code must either be
 * included in the distribution or be available for no more than the cost
 * of distribution plus a nominal fee, and must be freely redistributable
 * under reasonable conditions.  For an executable file, complete source
 * code means the source code for all modules it contains. It does not
 * include source code for modules or files that typically accompany the
 * major components of the operating system on which the executable file
 * runs.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
