class COSString extends COSBase
{
    private static final Logger LOG = LogManager.getLogger(COSString.class);

    private byte[] bytes;
    private boolean forceHexForm;

    // legacy behaviour for old PDFParser
    public static final boolean FORCE_PARSING =
            Boolean.getBoolean("org.apache.pdfbox.forceParsing");

    /**
     * Creates a new PDF string from a byte array. This method can be used to read a string from
     * an existing PDF file, or to create a new byte string.
     *
     * @param bytes The raw bytes of the PDF text string or byte string.
     */
    public COSString(byte[] bytes)
    {
        setValue(bytes);
    }

    /**
     * Creates a new <i>text string</i> from a Java String.
     *
     * @param text The string value of the object.
     */
    public COSString(String text)
    {
        // check whether the string uses only characters available in PDFDocEncoding
        boolean isOnlyPDFDocEncoding = true;
        for (char c : text.toCharArray())
        {
            if (!PDFDocEncoding.containsChar(c))
            {
                isOnlyPDFDocEncoding = false;
                break;
            }
        }

        if (isOnlyPDFDocEncoding)
        {
            // PDFDocEncoded string
            bytes = PDFDocEncoding.getBytes(text);
        }
        else
        {
            // UTF-16BE encoded string with a leading byte order marker
            byte[] data = text.getBytes(StandardCharsets.UTF_16BE);
            bytes = new byte[data.length + 2];
            bytes[0] = (byte) 0xFE;
            bytes[1] = (byte) 0xFF;
            System.arraycopy(data, 0, bytes, 2, data.length);
        }
    }

    /**
     * This will create a COS string from a string of hex characters.
     *
     * @param hex A hex string.
     * @return A cos string with the hex characters converted to their actual bytes.
     * @throws IOException If there is an error with the hex string.
     */
    public static COSString parseHex(String hex) throws IOException
    {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        StringBuilder hexBuffer = new StringBuilder(hex.trim());

        // if odd number then the last hex digit is assumed to be 0
        if (hexBuffer.length() % 2 != 0)
        {
            hexBuffer.append('0');
        }

        int length = hexBuffer.length();
        for (int i = 0; i < length; i += 2)
        {
            try
            {
                bytes.write(Integer.parseInt(hexBuffer.substring(i, i + 2), 16));
            }
            catch (NumberFormatException e)
            {
                if (FORCE_PARSING)
                {
                    LOG.warn("Encountered a malformed hex string");
                    bytes.write('?'); // todo: what does Acrobat do? Any example PDFs?
                }
                else
                {
                    throw new IOException("Invalid hex string: " + hex, e);
                }
            }
        }

        return new COSString(bytes.toByteArray());
    }

    /**
     * Sets the raw value of this string.
     *
     * @param value The raw bytes of the PDF text string or byte string.
     * 
     * @deprecated to be removed in a future release.
     */
    @Deprecated
    public void setValue(byte[] value)
    {
        bytes = value.clone();
    }

    /**
     * Sets whether to force the string is to be written in hex form.
     * This is needed when signing PDF files.
     *
     * @param value True to force hex.
     */
    public void setForceHexForm(boolean value)
    {
        this.forceHexForm = value;
    }

    /**
     * Returns true if the string is to be written in hex form.
     * 
     * @return true if the COSString is written in hex form
     */
    public boolean getForceHexForm()
    {
        return forceHexForm;
    }

    /**
     * Returns the content of this string as a PDF <i>text string</i>.
     * 
     * @return the PDF string representation of the COSString
     */
    public String getString()
    {<FILL_FUNCTION_BODY>}

    /**
     * Returns the content of this string as a PDF <i>ASCII string</i>.
     * 
     * @return the ASCII string representation of the COSString
     */
    public String getASCII()
    {
        // ASCII string
        return new String(bytes, StandardCharsets.US_ASCII);
    }

    /**
     * Returns the raw bytes of the string using a new byte array. Best used with a PDF <i>byte string</i>.
     * 
     * @return a clone of the underlying byte[] representation of the COSString
     */
    public byte[] getBytes()
    {
        return bytes.clone();
    }

    /**
     * This will take this string and create a hex representation of the bytes that make the string.
     *
     * @return A hex string representing the bytes in this string.
     */
    public String toHexString()
    {
        return Hex.getString(bytes);
    }

    /**
     * Visitor pattern double dispatch method.
     * 
     * @param visitor The object to notify when visiting this object.
     * @throws IOException If an error occurs while visiting this object.
     */
    @Override
    public void accept(ICOSVisitor visitor) throws IOException
    {
        visitor.visitFromString(this);
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof COSString)
        {
            COSString strObj = (COSString) obj;
            return getString().equals(strObj.getString()) &&
                   forceHexForm == strObj.forceHexForm;
        }
        return false;
    }

    @Override
    public int hashCode()
    {
        int result = Arrays.hashCode(bytes);
        return result + (forceHexForm ? 17 : 0);
    }

    @Override
    public String toString()
    {
        return "COSString{" + getString() + "}";
    }
}

        // text string - BOM indicates Unicode
        if (bytes.length >= 2)
        {
            if ((bytes[0] & 0xff) == 0xFE && (bytes[1] & 0xff) == 0xFF)
            {
                // UTF-16BE
                return new String(bytes, 2, bytes.length - 2, StandardCharsets.UTF_16BE);
            }
            else if ((bytes[0] & 0xff) == 0xFF && (bytes[1] & 0xff) == 0xFE)
            {
                // UTF-16LE - not in the PDF spec!
                return new String(bytes, 2, bytes.length - 2, StandardCharsets.UTF_16LE);
            }
        }

        // otherwise use PDFDocEncoding
        return PDFDocEncoding.toString(bytes);
    
<methods>public void <init>() ,public abstract void accept(org.apache.pdfbox.cos.ICOSVisitor) throws java.io.IOException,public org.apache.pdfbox.cos.COSBase getCOSObject() ,public org.apache.pdfbox.cos.COSObjectKey getKey() ,public boolean isDirect() ,public void setDirect(boolean) ,public void setKey(org.apache.pdfbox.cos.COSObjectKey) <variables>private boolean direct,private org.apache.pdfbox.cos.COSObjectKey key