/*
* @(#)$Id: Base64BinaryType.java,v 1.20 2002/03/09 15:27:06 kk122374 Exp $
*
* Copyright 2001 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the proprietary information of Sun Microsystems, Inc.
* Use is subject to license terms.
*
*/
package com.sun.msv.datatype.xsd;
import org.relaxng.datatype.ValidationContext;
import com.sun.msv.datatype.SerializationContext;
/**
* "base64Binary" type.
*
* type of the value object is {@link BinaryValueType}.
* See http://www.w3.org/TR/xmlschema-2/#base64Binary for the spec
*
* @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a>
*/
public class Base64BinaryType extends BinaryBaseType {
public static final Base64BinaryType theInstance = new Base64BinaryType();
private Base64BinaryType() { super("base64Binary"); }
// base64 decoder
//====================================
private static final byte[] decodeMap = initDecodeMap();
private static final byte PADDING = 127;
private static byte[] initDecodeMap() {
byte[] map = new byte[256];
int i;
for( i=0; i<256; i++ ) map[i] = -1;
for( i='A'; i<='Z'; i++ ) map[i] = (byte)(i-'A');
for( i='a'; i<='z'; i++ ) map[i] = (byte)(i-'a'+26);
for( i='0'; i<='9'; i++ ) map[i] = (byte)(i-'0'+52);
map['+'] = 62;
map['/'] = 63;
map['='] = PADDING;
return map;
}
public Object _createValue( String lexicalValue, ValidationContext context ) {
// I know, these methods should throw an exception ...
byte[] buf = load(lexicalValue);
if(buf==null) return null;
else return new BinaryValueType(buf);
}
public static byte[] load( String lexicalValue ) {
final byte[] buf = lexicalValue.getBytes();
final int outlen = calcLength(buf);
if( outlen==-1 ) return null;
final byte[] out = new byte[outlen];
int o=0;
final int len = buf.length;
int i;
final byte[] quadruplet = new byte[4];
int q=0;
// convert each quadruplet to three bytes.
for( i=0; i<len; i++ ) {
byte v = decodeMap[buf[i]];
if( v!=-1 )
quadruplet[q++] = v;
if(q==4) {
// quadruplet is now filled.
out[o++] = (byte)((quadruplet[0]<<2)|(quadruplet[1]>>4));
if( quadruplet[2]!=PADDING )
out[o++] = (byte)((quadruplet[1]<<4)|(quadruplet[2]>>2));
if( quadruplet[3]!=PADDING )
out[o++] = (byte)((quadruplet[2]<<6)|(quadruplet[3]));
q=0;
}
}
// assertion failed.
if(q!=0) throw new IllegalStateException();
return out;
}
protected boolean checkFormat( String lexicalValue, ValidationContext context ) {
return calcLength( lexicalValue.getBytes() ) != -1;
}
/**
* computes the length of binary data.
*
* This function also performs format check.
* @return -1 if format is illegal.
*
*/
private static int calcLength( final byte[] buf ) {
final int len = buf.length;
int base64count=0, paddingCount=0;
int i;
for( i=0; i<len; i++ ) {
if( buf[i]=='=' ) // decodeMap['=']!=-1, so we have to check this first.
break;
if( decodeMap[buf[i]]!=-1 )
base64count++;
}
// once we saw '=', nothing but '=' can be appeared.
for( ; i<len; i++ ) {
if( buf[i]=='=' ) {
paddingCount++;
continue;
}
if( decodeMap[buf[i]]!=-1 )
return -1;
}
// no more than two paddings are allowed.
if( paddingCount > 2 ) return -1;
// characters must be a multiple of 4.
if( (base64count+paddingCount)%4 != 0 ) return -1;
return ((base64count+paddingCount)/4)*3-paddingCount;
}
private static final char[] encodeMap = initEncodeMap();
private static char[] initEncodeMap() {
char[] map = new char[64];
int i;
for( i= 0; i<26; i++ ) map[i] = (char)('A'+i);
for( i=26; i<52; i++ ) map[i] = (char)('a'+(i-26));
for( i=52; i<62; i++ ) map[i] = (char)('0'+(i-52));
map[62] = '+';
map[63] = '/';
return map;
}
protected static char encode( int i ) {
return encodeMap[i&0x3F];
}
public String serializeJavaObject( Object value, SerializationContext context ) {
if(!(value instanceof byte[]))
throw new IllegalArgumentException();
return save((byte[])value);
}
public static String save( byte[] input ) {
StringBuffer r = new StringBuffer(input.length*4/3); /* rough estimate*/
for( int i=0; i<input.length; i+=3 ) {
switch( input.length-i ) {
case 1:
r.append( encode(input[i]>>2) );
r.append( encode(((input[i])&0x3)<<4) );
r.append("==");
break;
case 2:
r.append( encode(input[i]>>2) );
r.append( encode(
((input[i]&0x3)<<4) |
((input[i+1]>>4)&0xF)) );
r.append( encode((input[i+1]&0xF)<<2) );
r.append("=");
break;
default:
r.append( encode(input[i]>>2) );
r.append( encode(
((input[i]&0x3)<<4) |
((input[i+1]>>4)&0xF)) );
r.append( encode(
((input[i+1]&0xF)<<2)|
((input[i+2]>>6)&0x3)) );
r.append( encode(input[i+2]&0x3F) );
break;
}
}
return r.toString();
}
public String convertToLexicalValue( Object value, SerializationContext context ) {
if(!(value instanceof BinaryValueType))
throw new IllegalArgumentException();
return serializeJavaObject( ((BinaryValueType)value).rawData, context );
}
}