// Copyright 2008-2012 severally by the contributors
//
// 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 net.sf.practicalxml.internal;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;

import net.sf.practicalxml.XmlException;


/**
 *  This class hides implementation-specific details of output transformers. It
 *  exists to support {@link net.sf.practicalxml.OutputUtil}, but may be useful
 *  on its own.
 *  <p>
 *  To use, create an instance, call one or more of the configuration methods
 *  (which follow the builder pattern, so can be chained), and then call
 *  {@link #newTransformer}. Be aware that the underlying transformer factories
 *  are not guaranteed to be thread-safe, so you must manually synchronize this
 *  method across instances of this class (and across any other transformer
 *  creation).
 *  <p>
 *  There are a bunch of private static methods to support different transformer
 *  implementation classes. These contain ugly code, typically "if" statements
 *  based on implementation classname. Default behavior of each method supports
 *  the Sun JDK 1.5 implementation (a port of Xerces).
 */
public class TransformerFactoryHelper
{
    // will be lazily created by newTransformer(), cleared by any config method
    private TransformerFactory _factory;

    // we preserve all settings until the first transform
    private boolean _usePrologue;
    private String _encoding;
    private boolean _indent;
    private int _indentLevel;


    /**
     *  Enables output of a prologue, without specifying an encoding (this
     *  means that the output must be encoded as UTF-8).
     */
    public TransformerFactoryHelper setPrologue()
    {
        _factory = null;
        _usePrologue = true;
        return this;
    }


    /**
     *  Enables output of a prologue with specified encoding. Note that an
     *  encoding specification must be carried through to output; this
     *  should not be used for String-based output.
     */
    public TransformerFactoryHelper setPrologue(String encoding)
    {
        _factory = null;
        _usePrologue = true;
        _encoding = encoding;
        return this;
    }


    /**
     *  Enables indentation of the output, with the specified number of
     *  spaces at each level. Note that this is a <em>best effort</em>
     *  setting, as the <code>javax.xml.transform</code> API does not
     *  provide a method to set indent level, and not all transformers
     *  may support such a setting.
     */
    public TransformerFactoryHelper setIndent(int indentLevel)
    {
        _factory = null;
        _indent = true;
        _indentLevel = indentLevel;
        return this;
    }


    /**
     *  Creates a new transformer instance that supports the requested output
     *  configuration.
     */
    public Transformer newTransformer()
    {
        try
        {
            if (_factory == null)
            {
                _factory = TransformerFactory.newInstance();
                configIndent(_factory);
            }

            Transformer xform = _factory.newTransformer();
            configIndent(xform);
            configPrologue(xform);
            return xform;
        }
        catch (Exception e)
        {
            throw new XmlException("unable to configure transformer", e);
        }
    }


//----------------------------------------------------------------------------
//  Internals -- bury implementation-specific cruft in here
//----------------------------------------------------------------------------

    /**
     *  Enables/disables prologue generation (default is disabled), with
     *  optional specification of encoding.
     */
    private void configPrologue(Transformer xform)
    {
        xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
                                _usePrologue ? "no" : "yes");
        if (_encoding != null)
        {
            xform.setOutputProperty(OutputKeys.ENCODING, _encoding);
        }
    }


    /**
     *  Configures indented output.
     *
     *  For JDK 1.5, indenting is controlled by the transformer, while the
     *  level is controlled by the factory (see bug #6296446). This still
     *  works in 1.6, so the same code is used in both places.
     */
    private void configIndent(TransformerFactory fact)
    {
        if (!_indent)
            return;

        if (! fact.getClass().getName().startsWith("org.apache.xalan"))
        {
            fact.setAttribute("indent-number", Integer.valueOf(_indentLevel));
        }
    }


    /**
     *  Configures indented output.
     *
     *  For JDK 1.5, indenting is controlled by the transformer, while the
     *  level is controlled by the factory (see bug #6296446). This still
     *  works in 1.6, so the same code is used in both places.
     */
    private void configIndent(Transformer xform)
    {
        xform.setOutputProperty(OutputKeys.INDENT,
                                _indent ? "yes" : "no");

        if (xform.getClass().getName().startsWith("org.apache.xalan"))
        {
            xform.setOutputProperty("{http://xml.apache.org/xalan}indent-amount",
                                    String.valueOf(_indentLevel));
        }
    }
}
