Index: src/test/java/org/castor/util/TestHex.java =================================================================== --- src/test/java/org/castor/util/TestHex.java (Revision 0) +++ src/test/java/org/castor/util/TestHex.java (Revision 0) @@ -0,0 +1,46 @@ +/* + * Copyright 2007 Werner Guttmann + * + * 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 org.castor.util; + +import java.util.Arrays; +import java.util.Random; + +import junit.framework.TestCase; + +/** + * JUnit test case for HEX en-/coding. + * @author Johan Lindquist + * @version $Revision$ + */ +public class TestHex extends TestCase { + + public void testEncodeDecode() { + byte[] bytes = new byte[256]; + + new Random(1L).nextBytes(bytes); + final String encodedBytes = HexDecoder.encode(bytes); + assertEquals( + "Bad encoded data", + "73d51abbd89cb8196f0efb6892f94d68fccc2c35f0b84609e5f12c55dd85aba8d5d9bef76808f3b572e5900112b81927ba5bb5f67e1bda28b4049bf0e4aed78db15d7bf2fc0c34e9a99de4ef3bc2b17c8137ad659878f9e93df1f658367aca286452474b9ef3765e24e9a88173724dddfb04b01dcceb0c8aead641c58dad569581baeea87c10d40a47902028e61cfdc243d9d16008aabc9fb77cc723a56017e14f1ce8b1698341734a6823ce02043e016b544901214a2ddab82fec85c0b9fe0549c475be5b887bb4b8995b24fb5c6846f88b527b4f9d4c1391f1678b23ba4f9c9cd7bc93eb5776f4f03675344864294661c5949faf17b130fcf6482f971a5500", + encodedBytes); + + final byte[] decodedBytes = HexDecoder.decode(encodedBytes); + + assertTrue("Bad decoded bytes", Arrays.equals(bytes, decodedBytes)); + + } + +} Index: src/main/java/org/exolab/castor/xml/UnmarshalHandler.java =================================================================== --- src/main/java/org/exolab/castor/xml/UnmarshalHandler.java (Revision 6910) +++ src/main/java/org/exolab/castor/xml/UnmarshalHandler.java (Arbeitskopie) @@ -50,6 +50,7 @@ //-- Castor imports import org.castor.mapping.BindingType; import org.castor.util.Base64Decoder; +import org.castor.util.HexDecoder; import org.exolab.castor.util.Configuration; import org.exolab.castor.util.ObjectFactory; import org.exolab.castor.util.DefaultObjectFactory; @@ -801,8 +802,12 @@ if (str == null) state.object = new byte[0]; else { - //-- Base64 decoding - state.object = Base64Decoder.decode(str); + //-- Base64/HexBinary decoding + if (HexDecoder.DATA_TYPE.equals(descriptor.getSchemaType())) { + state.object = HexDecoder.decode(str); + } else { + state.object = Base64Decoder.decode(str); + } } } else if (state.args != null) { @@ -827,10 +832,14 @@ value = toPrimitiveObject(cdesc.getFieldType(), (String)value, state.fieldDesc); else { Class valueType = cdesc.getFieldType(); - //-- handle base64 + //-- handle base64/hexBinary if (valueType.isArray() && (valueType.getComponentType() == Byte.TYPE)) { - value = Base64Decoder.decode((String) value); + if (HexDecoder.DATA_TYPE.equals(descriptor.getSchemaType())) { + value = HexDecoder.decode((String) value); + } else { + value = Base64Decoder.decode((String) value); + } } } @@ -3024,8 +3033,12 @@ if (attValue == null) value = new byte[0]; else { - //-- Base64 decoding - value = Base64Decoder.decode(attValue); + //-- Base64/hexbinary decoding + if (HexDecoder.DATA_TYPE.equals(descriptor.getSchemaType())) { + value = HexDecoder.decode(attValue); + } else { + value = Base64Decoder.decode(attValue); + } } } Index: src/main/java/org/exolab/castor/xml/Marshaller.java =================================================================== --- src/main/java/org/exolab/castor/xml/Marshaller.java (Revision 6910) +++ src/main/java/org/exolab/castor/xml/Marshaller.java (Arbeitskopie) @@ -55,6 +55,7 @@ import org.castor.mapping.MappingUnmarshaller; import org.castor.util.Base64Encoder; import org.castor.util.Messages; +import org.castor.util.HexDecoder; import org.exolab.castor.mapping.CollectionHandler; import org.exolab.castor.mapping.MapItem; import org.exolab.castor.mapping.Mapping; @@ -1636,8 +1637,13 @@ char[] chars = null; Class objType = obj.getClass(); if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) { - //-- handle base64 content - chars = Base64Encoder.encode((byte[]) obj); + //-- handle base64/hexbinary content + final String schemaType = descriptor.getSchemaType(); + if (HexDecoder.DATA_TYPE.equals(schemaType)) { + chars = new String(HexDecoder.encode((byte[]) obj)).toCharArray(); + } else { + chars = Base64Encoder.encode((byte[]) obj); + } } else { //-- all other types String str = obj.toString(); @@ -1670,11 +1676,17 @@ } // special case for byte[] else if (byteArray) { - //-- Base64Encoding - char[] chars = Base64Encoder.encode((byte[]) object); + //-- Base64Encoding / HexBinary + String schemaType = descriptor.getSchemaType(); + char[] chars = new char[0]; + if (HexDecoder.DATA_TYPE.equals(schemaType)) { + chars = new String(HexDecoder.encode((byte[]) object)).toCharArray(); + } else { + chars = Base64Encoder.encode((byte[]) object); + } try { handler.characters(chars, 0, chars.length); - } catch (org.xml.sax.SAXException sx) { + } catch (org.xml.sax.SAXException sx) { throw new MarshalException(sx); } } @@ -2364,10 +2376,15 @@ else value = null; } else if (value != null) { - //-- handle base64 content + //-- handle hex/base64 content Class objType = value.getClass(); if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) { - value = Base64Encoder.encode((byte[]) value); + final String schemaType = attDescriptor.getSchemaType(); + if (HexDecoder.DATA_TYPE.equals(schemaType)) { + value = new String(HexDecoder.encode((byte[]) value)); + } else { + value = new String(Base64Encoder.encode((byte[]) value)); + } } } Index: src/main/java/org/castor/util/HexDecoder.java =================================================================== --- src/main/java/org/castor/util/HexDecoder.java (Revision 0) +++ src/main/java/org/castor/util/HexDecoder.java (Revision 0) @@ -0,0 +1,237 @@ +/* + * Copyright 2007 Werner Guttmann + * + * 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 org.castor.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; + +/** + * Hex encoder/decoder implementation (borrowed from BouncyCastle=. + * + * @author Johan Lindquist + * @since 1.1.1 + * @version $Revision$ + */ +public final class HexDecoder { + + /** + * Identifies the data type supported by this decoder. + */ + public static final String DATA_TYPE = "hexBinary"; + + /** + * Initial size of the decoding table. + */ + private static final int DECODING_TABLE_SIZE = 128; + + /** + * Encoding table. + */ + protected static final byte[] ENCODING_TABLE = {(byte) '0', (byte) '1', + (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', + (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', + (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' }; + + /** + * Decoding table. + */ + protected static final byte[] DECODING_TABLE = new byte[DECODING_TABLE_SIZE]; + + /** + * Initialize the decoding table. + */ + protected static void initialiseDecodingTable() { + for (int i = 0; i < ENCODING_TABLE.length; i++) { + DECODING_TABLE[ENCODING_TABLE[i]] = (byte) i; + } + + DECODING_TABLE['A'] = DECODING_TABLE['a']; + DECODING_TABLE['B'] = DECODING_TABLE['b']; + DECODING_TABLE['C'] = DECODING_TABLE['c']; + DECODING_TABLE['D'] = DECODING_TABLE['d']; + DECODING_TABLE['E'] = DECODING_TABLE['e']; + DECODING_TABLE['F'] = DECODING_TABLE['f']; + } + + static { + initialiseDecodingTable(); + } + + /** + * Creates an instance of this class. + */ + private HexDecoder() { + // Nothing to do ... + } + + /** + * Encodes the input data producing a Hex output stream. + * @param data The input data to be HEX encoded + * @param off Initiak offset + * @param length Initial length of the input data array + * @param out The {@link OutputStream} instance holding the encoded input data. + * @return the number of bytes produced. + * @throws IOException If encoding fails. + */ + public static int encode(final byte[] data, final int off, final int length, + final OutputStream out) throws IOException { + for (int i = off; i < (off + length); i++) { + int v = data[i] & 0xff; + + out.write(ENCODING_TABLE[(v >>> 4)]); + out.write(ENCODING_TABLE[v & 0xf]); + } + + return length * 2; + } + + /** + * Indicates whether a given character should be ignored during en-/decoding. + * @param c The character at question. + * @return True if the given character should be ignored. + */ + private static boolean ignore(final char c) { + return (c == '\n' || c == '\r' || c == '\t' || c == ' '); + } + + /** + * Decodes the Hex encoded byte data writing it to the given output stream, + * whitespace characters will be ignored. + * @param data The data to be encoded + * @param off Initial offset. + * @param length Initial length + * @param out The {@link OutputStream} instance + * @return the number of bytes produced. + * @throws IOException If encoding failed. + */ + public static int decode(final byte[] data, final int off, final int length, + final OutputStream out) throws IOException { + byte b1, b2; + int outLen = 0; + + int end = off + length; + + while (end > off) { + if (!ignore((char) data[end - 1])) { + break; + } + + end--; + } + + int i = off; + while (i < end) { + while (i < end && ignore((char) data[i])) { + i++; + } + + b1 = DECODING_TABLE[data[i++]]; + + while (i < end && ignore((char) data[i])) { + i++; + } + + b2 = DECODING_TABLE[data[i++]]; + + out.write((b1 << 4) | b2); + + outLen++; + } + + return outLen; + } + + /** + * Decodes the Hex encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @param data The data to be encoded + * @param out The {@link OutputStream} instance + * @return the number of bytes produced. + * @throws IOException If encoding failed. + */ + public static int decode(final String data, final OutputStream out) throws IOException { + byte b1, b2; + int length = 0; + + int end = data.length(); + + while (end > 0) { + if (!ignore(data.charAt(end - 1))) { + break; + } + + end--; + } + + int i = 0; + while (i < end) { + while (i < end && ignore(data.charAt(i))) { + i++; + } + + b1 = DECODING_TABLE[data.charAt(i++)]; + + while (i < end && ignore(data.charAt(i))) { + i++; + } + + b2 = DECODING_TABLE[data.charAt(i++)]; + + out.write((b1 << 4) | b2); + + length++; + } + + return length; + } + + /** + * Encodes the input data producing a Hex output stream. + * @param data Input data to encode. + * @return the number of bytes produced. + */ + public static String encode(final byte[] data) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + encode(data, 0, data.length, out); + out.close(); + return new String(out.toByteArray()); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * Decodes the HEX input data producing a output stream. + * @param data Input data to be decoded. + * @return A byte array representing the decoded input data. + */ + public static byte[] decode(final String data) { + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + decode(data, out); + out.close(); + return out.toByteArray(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + } + +} Index: src/doc/release-notes.xml =================================================================== --- src/doc/release-notes.xml (Revision 6910) +++ src/doc/release-notes.xml (Arbeitskopie) @@ -70,6 +70,15 @@ ValidationException will be thrown if the expected order cannot be matched.
+Added support for the XML schema type <xs:hexBinary> during + XML code generation.
+ +The XML (Un-)Marshaller now supports the data type hexBinary.
+The public interface of XMLClassDescriptorResolver has been improved
@@ -79,6 +88,28 @@