/*
 * 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.axis2.datasource.jaxb;

import org.apache.axiom.om.impl.MTOMXMLStreamWriter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This is an MTOMXMLStreamWriter filter that removes illegal characters.
 * 
 * Valid and invalid character ranges are defined by:
 * http://www.w3.org/TR/2008/REC-xml-20081126/#NT-Char
 *
 *
 */
public class XMLStreamWriterRemoveIllegalChars extends
        XMLStreamWriterFilterBase {

    private static final Log log = LogFactory.getLog(XMLStreamWriterRemoveIllegalChars.class);
    
    public XMLStreamWriterRemoveIllegalChars(MTOMXMLStreamWriter delegate) {
        super(delegate);
        if (log.isDebugEnabled()) {
            log.debug("Creating XMLStreamWriterRemoveIllegalChars object " + this);
        }
    }
    // Characters less than 0x20 may be control characters and should be removed
    // Note the non-initialized bytes in this array are zero
    private static byte[] REMOVE = new byte[32];
    static {
        REMOVE[0x00] = 1;
        REMOVE[0x01] = 1;
        REMOVE[0x02] = 1;
        REMOVE[0x03] = 1;
        REMOVE[0x04] = 1;
        REMOVE[0x05] = 1;
        REMOVE[0x06] = 1;
        REMOVE[0x07] = 1;
        REMOVE[0x08] = 1;
        // 0x09 is TAB...which is allowed
        // 0x0A is LINEFEED...which is allowed
        REMOVE[0x0B] = 1;
        REMOVE[0x0C] = 1;
        // 0x0D is CARRIAGE RETURN, which is allowed
        REMOVE[0x0E] = 1;
        REMOVE[0x0F] = 1;
        REMOVE[0x10] = 1;
        REMOVE[0x11] = 1;
        REMOVE[0x12] = 1;
        REMOVE[0x13] = 1;
        REMOVE[0x14] = 1;
        REMOVE[0x15] = 1;
        REMOVE[0x16] = 1;
        REMOVE[0x17] = 1;
        REMOVE[0x18] = 1;
        REMOVE[0x19] = 1;
        REMOVE[0x1A] = 1;
        REMOVE[0x1B] = 1;
        REMOVE[0x1C] = 1;
        REMOVE[0x1D] = 1;
        REMOVE[0x1E] = 1;
        REMOVE[0x1F] = 1;
    }
    
    // These two characters are not allowed
    private final int FFFE = 0xFFFE;
    private final char FFFF = 0xFFFF;
    
    // Characters in the surrogate range are not allowed
    // (unless the result is a valid supplemental character)
    private final char SURROGATE_START = 0xD800;
    private final char SURROGATE_END =   0xDFFF;
    
    
    /* (non-Javadoc)
     * @see org.apache.axiom.om.util.XMLStreamWriterFilterBase#xmlData(java.lang.String)
     */
    protected String xmlData(String value) {
        
        char[] buffer = null;
        int len = value.length();
        int srcI = 0;
        int tgtI = 0;
        int copyLength = 0;
        int i = 0;
        
        // Traverse all of the characters in the input String (value)
        while (i < len) {
            
            // Get the codepoint of the character at the index
            // Note that the code point may be two characters long (a supplemental character)
            int cp = value.codePointAt(i);
            
            if (cp > FFFF) {
                // Supplemental Character...Increase index by 2
                // Increase the length of good characters to copy by 2
                i = i+2;
                copyLength = copyLength+2;
            } else {
                // See if the character is invalid
                if ((cp < 0x20 && (REMOVE[cp] > 0)) || // Control Character
                        (cp >= SURROGATE_START && cp <= SURROGATE_END ) ||  // Bad surrogate
                        (cp == FFFF || cp == FFFE)) {  // or illegal character
                    // Flow to here indicates that the character is not allowed.  
                    // The good characters (up to this point) are copied into the buffer.
                    
                    // Note that the buffer is initialized with the original characters.
                    // Thus the buffer copy is always done on the same buffer (saving
                    // both time and space).
                    
                    
                    // Make the buffer on demand
                    if (buffer == null) {
                        if (log.isDebugEnabled()) {
                            log.debug("One or more illegal characterss found.  Codepoint=" + cp);
                        }
                        buffer = value.toCharArray();
                    }
                    
                    // Copy the good characters into the buffer
                    System.arraycopy(buffer, srcI, buffer, tgtI, copyLength);
                    tgtI = tgtI + copyLength;  // Update the target location in the array
                    srcI = i + 1;  // Skip over the current character
                    copyLength = 0; // reset new copy length
                } else {
                    // Valid character, increase copy length
                    copyLength = copyLength+1;
                }
                // Single bit16 character, increase index by 1
                i = i+1;
            }
        }
        
        if (buffer == null) {
            // Normal case, no illegal characters removed..No buffer
            return value;
        } else {
            // Move the final valid characters to the buffer
            // and return a string representing the value
            System.arraycopy(buffer, srcI, buffer, tgtI, copyLength);
            String newValue = new String(buffer, 0, tgtI + copyLength);
            return newValue;
        }
        
    }
}
