/** Performs tasks to resolve the lazy instantiation. */
  private synchronized void init() {
    if (!initialized) {
      // Mimic the behavior of XStream's JVM class
      String vendor = System.getProperty("java.vm.vendor");
      float version = 1.3f;
      try {
        version = Float.parseFloat(System.getProperty("java.version").substring(0, 3));
      } catch (NumberFormatException nfe) {
        // Keep the default
      }
      Class unsafe = null;
      try {
        unsafe = Class.forName("sun.misc.Unsafe", false, getClass().getClassLoader());
      } catch (ClassNotFoundException cnfe) {
        // Keep the default
      }
      ReflectionProvider reflectionProvider = null;
      if ((vendor.contains("Sun")
              || vendor.contains("Oracle")
              || vendor.contains("Apple")
              || vendor.contains("Hewlett-Packard")
              || vendor.contains("IBM")
              || vendor.contains("Blackdown"))
          && version >= 1.4f
          && unsafe != null) {
        try {
          reflectionProvider =
              (ReflectionProvider)
                  Class.forName(
                          "com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider",
                          false,
                          getClass().getClassLoader())
                      .newInstance();
        } catch (InstantiationException ie) {
          reflectionProvider = new PureJavaReflectionProvider();
        } catch (IllegalAccessException iae) {
          reflectionProvider = new PureJavaReflectionProvider();
        } catch (ClassNotFoundException cnfe) {
          reflectionProvider = new PureJavaReflectionProvider();
        }
      } else {
        reflectionProvider = new PureJavaReflectionProvider();
      }
      HierarchicalStreamDriver driver = new DomDriver();

      xs = new XStream(reflectionProvider, driver);
      xs.setMarshallingStrategy(new LockssReferenceByXPathMarshallingStrategy(lockssContext));
      xs.registerConverter(new LockssDateConverter());
      initialized = true;
    }
  }
/**
 * This class wrapps WikiTableWriter
 *
 * @author shahzad bhatti
 *     <p>modification history date who what 1/9/06 SB created.
 */
public class WikiTableWriter implements ExtendedHierarchicalStreamWriter {
  private final PointableWriter writer;
  private final FastStack elementStack = new FastStack(16);
  private final char[] lineIndenter;

  private boolean useLineSeparateBetweenTags;
  private boolean tagInProgress;
  private int startPointer;
  private int depth;
  private boolean readyForNewLine;
  private boolean tagIsEmpty;
  private boolean quickNodeValue;
  private boolean lastTagHadAttributes;

  private static final char[] AMP = "&amp;".toCharArray();
  private static final char[] LT = "&lt;".toCharArray();
  private static final char[] GT = "&gt;".toCharArray();
  private static final char[] SLASH_R = "&#x0D;".toCharArray();
  private static final char[] QUOT = "&quot;".toCharArray();
  private static final char[] APOS = "&apos;".toCharArray();
  private static final char[] CLOSE = "</".toCharArray();
  private static final String LF = System.getProperty("line.separator");

  /**
   * WikiTableWriter constructor
   *
   * @param writer - output writer object
   * @param lineIdenter - indentation
   */
  public WikiTableWriter(Writer writer, char[] lineIndenter) {
    this.writer = new PointableWriter(writer);
    this.lineIndenter = lineIndenter;
    this.useLineSeparateBetweenTags =
        true; // context.getConfig().getBoolean("testplayer.fitnesse.use.line.separator.between.tags", true);
  }

  /**
   * WikiTableWriter constructor
   *
   * @param writer - output writer object
   * @param lineIdenter - indentation
   */
  public WikiTableWriter(Writer writer, String lineIndenter) {
    this(writer, lineIndenter.toCharArray());
  }

  /**
   * WikiTableWriter constructor
   *
   * @param writer - print writer object
   */
  public WikiTableWriter(PrintWriter writer) {
    this(writer, new char[] {'|'});
  }

  /**
   * WikiTableWriter constructor
   *
   * @param writer - writer object
   */
  public WikiTableWriter(Writer writer) {
    this(new PrintWriter(writer));
  }

  /**
   * Marks start of XML node
   *
   * @param name - name of node
   */
  public void startNode(String name, Class clazz) {
    startNode(name);
  }

  public void startNode(String name) {
    startPointer = writer.getPointer();
    quickNodeValue = false;
    lastTagHadAttributes = false;
    tagIsEmpty = false;
    finishTag();
    if (depth != 0) writer.write(LF);
    writer.write(
        WikiTableDriver.DELIM
            + WikiTableDriver.QUOTES
            + WikiTableDriver.START_NODE
            + WikiTableDriver.QUOTES
            + WikiTableDriver.DELIM);

    if (this.useLineSeparateBetweenTags) {
      writer.write(LF + WikiTableDriver.DELIM);
    }

    writer.write(name + WikiTableDriver.DELIM);
    elementStack.push(name);
    tagInProgress = true;
    depth++;
    readyForNewLine = true;
    tagIsEmpty = true;
  }

  /**
   * Stores value of previously defined XML node
   *
   * @param text - value of node
   */
  public void setValue(String text) {
    readyForNewLine = false;
    tagIsEmpty = false;
    finishTag();

    if (lastTagHadAttributes) {
      writer.write(
          LF
              + WikiTableDriver.DELIM
              + WikiTableDriver.QUOTES
              + WikiTableDriver.SET_VALUE
              + WikiTableDriver.QUOTES
              + WikiTableDriver.DELIM);
      quickNodeValue = false;
    } else {
      String startTag =
          StringHelper.replace(
              writer.substring(startPointer),
              WikiTableDriver.START_NODE,
              WikiTableDriver.SET_NODE_VALUE);

      if (this.useLineSeparateBetweenTags) {
        writer.write(LF + WikiTableDriver.DELIM);
      }

      writer.setPointer(startPointer);
      writer.write(startTag);
      quickNodeValue = true;
    }

    writeText(writer, text);
    writer.write(WikiTableDriver.DELIM);
  }

  /**
   * Adds attribute for XML node
   *
   * @param key - name of attribute
   * @param value - value of attribute
   */
  public void addAttribute(String key, String value) {
    writer.write(
        LF
            + WikiTableDriver.DELIM
            + WikiTableDriver.QUOTES
            + WikiTableDriver.ADD_ATTRIBUTE
            + WikiTableDriver.QUOTES
            + WikiTableDriver.DELIM);

    if (this.useLineSeparateBetweenTags) {
      writer.write(LF + WikiTableDriver.DELIM);
    }

    writer.write(key + WikiTableDriver.DELIM);

    writeAttributeValue(writer, value);
    writer.write(WikiTableDriver.DELIM);
    lastTagHadAttributes = true;
  }

  /** Marks end of node */
  public void endNode() {
    depth--;
    if (tagIsEmpty) {
      String prevTag = (String) elementStack.pop(); // Silently();
      if (!quickNodeValue) {
        writer.write(
            LF
                + WikiTableDriver.DELIM
                + WikiTableDriver.QUOTES
                + WikiTableDriver.END_NODE
                + WikiTableDriver.QUOTES
                + WikiTableDriver.DELIM);
        if (this.useLineSeparateBetweenTags) {
          writer.write(LF + WikiTableDriver.DELIM);
        }
        writer.write(prevTag + WikiTableDriver.DELIM);
      }
      readyForNewLine = false;
    } else {
      String prevTag = (String) elementStack.pop();
      if (!quickNodeValue) {
        writer.write(
            LF
                + WikiTableDriver.DELIM
                + WikiTableDriver.QUOTES
                + WikiTableDriver.END_NODE
                + WikiTableDriver.QUOTES
                + WikiTableDriver.DELIM);
        if (this.useLineSeparateBetweenTags) {
          writer.write(LF + WikiTableDriver.DELIM);
        }
        writer.write(prevTag + WikiTableDriver.DELIM);
      }
    }
    finishTag();
    readyForNewLine = true;
    if (depth == 0) {
      writer.flush();
    }
    quickNodeValue = false;
  }

  /** Flushes output */
  public void flush() {
    writer.flush();
  }

  /** Closes output */
  public void close() {
    writer.close();
  }

  /** Returns underlying writer */
  public HierarchicalStreamWriter underlyingWriter() {
    return this;
  }

  /////////////////////////////////////////////////////////////////////////
  //
  private void finishTag() {
    tagInProgress = false;
    readyForNewLine = false;
    tagIsEmpty = false;
  }

  private void endOfLine() {
    writer.write(LF);
    for (int i = 0; i < depth; i++) {
      writer.write(lineIndenter);
    }
  }

  private void writeAttributeValue(PointableWriter writer, String text) {
    int length = text.length();
    for (int i = 0; i < length; i++) {
      char c = text.charAt(i);
      switch (c) {
        case '&':
          this.writer.write(AMP);
          break;
        case '<':
          this.writer.write(LT);
          break;
        case '>':
          this.writer.write(GT);
          break;
        case '"':
          this.writer.write(QUOT);
          break;
        case '\'':
          this.writer.write(APOS);
          break;
        case '\r':
          this.writer.write(SLASH_R);
          break;
        default:
          this.writer.write(c);
      }
    }
  }

  private void writeText(PointableWriter writer, String text) {
    int length = text.length();
    for (int i = 0; i < length; i++) {
      char c = text.charAt(i);
      switch (c) {
        case '&':
          this.writer.write(AMP);
          break;
        case '<':
          this.writer.write(LT);
          break;
        case '>':
          this.writer.write(GT);
          break;
        case '"':
          this.writer.write(QUOT);
          break;
        case '\'':
          this.writer.write(APOS);
          break;
        case '\r':
          this.writer.write(SLASH_R);
          break;
        default:
          this.writer.write(c);
      }
    }
  }
}