001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.core.model.xml;
028
029import org.codehaus.stax2.XMLStreamWriter2;
030import org.codehaus.stax2.util.StreamWriter2Delegate;
031
032import java.util.HashMap;
033import java.util.Map;
034import java.util.Objects;
035
036import javax.xml.stream.XMLStreamException;
037
038public class IndentingXmlStreamWriter2
039    extends StreamWriter2Delegate {
040  private String indentText = DEFAULT_INDENT_TEXT;
041  private String lineEndText = DEFAULT_LINE_END_TEXT;
042  private int depth; // = 0;
043  private final Map<Integer, Boolean> depthWithChildMap = new HashMap<>(); // NOPMD - synchronization not needed
044  private static final String DEFAULT_INDENT_TEXT = "  ";
045  private static final String DEFAULT_LINE_END_TEXT = "\n";
046
047  public IndentingXmlStreamWriter2(XMLStreamWriter2 parent) {
048    super(parent);
049  }
050
051  protected String getIndentText() {
052    return indentText;
053  }
054
055  protected void setIndentText(String indentText) {
056    Objects.requireNonNull(indentText, "indentText");
057    this.indentText = indentText;
058  }
059
060  protected String getLineEndText() {
061    return lineEndText;
062  }
063
064  protected void setLineEndText(String lineEndText) {
065    Objects.requireNonNull(lineEndText, "lineEndText");
066    this.lineEndText = lineEndText;
067  }
068
069  protected void handleStartElement() throws XMLStreamException {
070    // update state of parent node
071    if (depth > 0) {
072      depthWithChildMap.put(depth - 1, true);
073    }
074    // reset state of current node
075    depthWithChildMap.put(depth, false);
076    // indent for current depth
077    getParent().writeCharacters(getLineEndText());
078    getParent().writeCharacters(getIndentText().repeat(depth));
079    depth++;
080  }
081
082  @Override
083  public void writeStartElement(String localName) throws XMLStreamException {
084    handleStartElement();
085    super.writeStartElement(localName);
086  }
087
088  @Override
089  public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
090    handleStartElement();
091    super.writeStartElement(namespaceURI, localName);
092  }
093
094  @Override
095  public void writeStartElement(String prefix,
096      String localName,
097      String namespaceURI) throws XMLStreamException {
098    handleStartElement();
099    super.writeStartElement(prefix, localName, namespaceURI);
100  }
101
102  protected void handleEndElement() throws XMLStreamException {
103    depth--;
104    if (depthWithChildMap.get(depth)) {
105      getParent().writeCharacters(getLineEndText());
106      getParent().writeCharacters(getIndentText().repeat(depth));
107    }
108  }
109
110  @Override
111  public void writeEndElement() throws XMLStreamException {
112    handleEndElement();
113    super.writeEndElement();
114  }
115
116  @Override
117  public void writeFullEndElement() throws XMLStreamException {
118    handleEndElement();
119    super.writeFullEndElement();
120  }
121
122  protected void handleEmptyElement() throws XMLStreamException {
123    // update state of parent node
124    if (depth > 0) {
125      depthWithChildMap.put(depth - 1, true);
126    }
127    // indent for current depth
128    getParent().writeCharacters(getLineEndText());
129    getParent().writeCharacters(getIndentText().repeat(depth));
130  }
131
132  @Override
133  public void writeEmptyElement(String localName) throws XMLStreamException {
134    handleEmptyElement();
135    super.writeEmptyElement(localName);
136  }
137
138  @Override
139  public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
140    handleEmptyElement();
141    super.writeEmptyElement(namespaceURI, localName);
142  }
143
144  @Override
145  public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
146    handleEmptyElement();
147    super.writeEmptyElement(prefix, localName, namespaceURI);
148  }
149}