// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package google.registry.xjc;

import google.registry.xml.XmlException;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import javax.xml.namespace.QName;

/** The superclass for XML classes generated by JAXB that provides marshalling and validation. */
@XmlTransient
public abstract class XjcObject {

  /**
   * Validates and streams {@code this} as formatted XML bytes with XML declaration.
   *
   * <p>This object must be annotated with {@link jakarta.xml.bind.annotation.XmlRootElement},
   * otherwise you should call {@link #toString()}. This method will verify that your object
   * strictly conforms to the schema defined in {@link XjcXmlTransformer}. Because the output is
   * streamed, {@link XmlException} will most likely be thrown <i>after</i> output has been written.
   *
   * @param out byte-oriented output for writing XML. This method won't close it.
   * @param encoding should almost always be set to {@code "UTF-8"}.
   */
  public void marshal(OutputStream out, Charset encoding) throws XmlException {
    XjcXmlTransformer.marshalStrict(this, out, encoding);
  }

  /**
   * Turns object into a formatted XML string <i>by any means necessary</i>.
   *
   * <p>No validation is performed and the XML declaration is omitted. If the object can't be
   * marshalled, a string describing the error is returned.
   *
   * @see #marshal
   */
  @Override
  public String toString() {
    try {
      StringWriter out = new StringWriter();
      XjcXmlTransformer.marshalLenient(
          getClass().isAnnotationPresent(XmlRootElement.class)
              ? this
              : new JAXBElement<>(new QName(getClass().getSimpleName()), Object.class, this),
          out);
      return out.toString();
    } catch (XmlException e) {
      return String.format("<!-- Invalid XML: %s -->", e.toString());
    }
  }
}
