/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.myfaces.shared_tomahawk.util;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;

/**
 * Provides optimized access to java.lang.String internals
 *
 * - Optimized way of creating java.lang.String by reusing a char[] buffer
 * - Optimized way of writing String to java.io.Writer
 *
 * java.lang.String creation reusing a char[] buffer requires Java 1.5+
 *
 * System property "oam.stringchararrayaccessor.enabled" enables this hack.
 * -Doam.stringchararrayaccessor.enabled=true
 *
 * Read JSR-133, "9.1.1 Post-Construction Modification of Final Fields"
 * http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf
 *
 * @author Lari Hotari, Sagire Software Oy
 * @see org.codehaus.groovy.grails.web.util.StreamCharBuffer
 *      file licensed under ASL v2.0 
 *      Copyright 2009 the original author or authors.
 */
public class StringCharArrayAccessor
{

    //static volatile boolean enabled = !Boolean
    //        .getBoolean("oam.stringchararrayaccessor.disabled");
    // In Google Application Engine this hack is not valid. We should
    // set this one as default disabled.
    static volatile boolean enabled = Boolean
            .getBoolean("oam.stringchararrayaccessor.enabled");

    static Field valueField;
    static Field countField;
    static Field offsetField;

    static
    {
        if (enabled)
        {
            try
            {
                valueField = String.class.getDeclaredField("value");
                valueField.setAccessible(true);

                countField = String.class.getDeclaredField("count");
                countField.setAccessible(true);

                offsetField = String.class.getDeclaredField("offset");
                offsetField.setAccessible(true);
            }
            catch (Exception e)
            {
                enabled = false;
                System.err
                        .println("Unable to use direct char[] access of java.lang.String");
                e.printStackTrace();
            }
        }
    }

    /**
     * Writes a portion of a string to a target java.io.Writer with direct access to the char[] of the java.lang.String
     *
     * @param  writer
     *            target java.io.Writer for output
     *
     * @param  str
     *         A String
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    static public void writeStringAsCharArray(Writer writer, String str)
            throws IOException
    {
        writeStringAsCharArray(writer, str, 0, str.length());
    }

    /**
     * Writes a portion of a string to a target java.io.Writer with direct access to the char[] of the java.lang.String
     *
     * @param  writer
     *            target java.io.Writer for output
     *
     * @param  str
     *         A String
     *
     * @param  off
     *         Offset from which to start writing characters
     *
     * @param  len
     *         Number of characters to write
     *
     * @throws  IOException
     *          If an I/O error occurs
     */
    static public void writeStringAsCharArray(Writer writer, String str,
            int off, int len) throws IOException
    {
        if (!enabled)
        {
            writeStringFallback(writer, str, off, len);
            return;
        }

        char[] value;
        int internalOffset;
        try
        {
            value = (char[]) valueField.get(str);
            internalOffset = offsetField.getInt(str);
        }
        catch (Exception e)
        {
            handleError(e);
            writeStringFallback(writer, str, off, len);
            return;
        }
        writer.write(value, internalOffset + off, len);
    }

    private static void writeStringFallback(Writer writer, String str, int off,
            int len) throws IOException
    {
        writer.write(str, off, len);
    }

    static char[] getValue(String str)
    {
        if (!enabled)
        {
            return getValueFallback(str);
        }

        char[] value = null;
        int internalOffset = 0;
        try
        {
            value = (char[]) valueField.get(str);
            internalOffset = offsetField.getInt(str);
        }
        catch (Exception e)
        {
            handleError(e);
        }
        if (value != null && internalOffset == 0)
        {
            return value;
        }

        return getValueFallback(str);
    }

    static char[] getValueFallback(String str)
    {
        return str.toCharArray();
    }

    /**
     * creates a new java.lang.String by setting the char array directly to the String instance with reflection.
     *
     * @param charBuf
     *        char array to be used as java.lang.String content, don't modify it after passing it.
     * @return new java.lang.String
     */
    public static String createString(char[] charBuf)
    {
        if (!enabled)
        {
            return createStringFallback(charBuf);
        }

        String str = new String();
        try
        {
            // try to prevent possible final field setting execution reordering in JIT 
            // (JSR-133/JMM, "9.1.1 Post-Construction Modification of Final Fields")
            // it was a bit unclear for me if this could ever happen in a single thread
            synchronized (str)
            {
                valueField.set(str, charBuf);
                countField.set(str, charBuf.length);
            }
            synchronized (str)
            {
                // safety check, just to be sure that setting the final fields went ok
                if (str.length() != charBuf.length)
                {
                    throw new IllegalStateException(
                            "Fast java.lang.String construction failed.");
                }
            }
        }
        catch (Exception e)
        {
            handleError(e);
            str = createStringFallback(charBuf);
        }
        return str;
    }

    private static String createStringFallback(char[] charBuf)
    {
        return new String(charBuf);
    }

    private static synchronized void handleError(Exception e)
    {
        enabled = false;
        System.err
                .println("Unable to use direct char[] access of java.lang.String. Disabling this method.");
        valueField = null;
        countField = null;
        offsetField = null;
        e.printStackTrace();
    }

    static public boolean isEnabled()
    {
        return enabled;
    }
}
