001    package com.thaiopensource.datatype.xsd;
002    
003    import org.relaxng.datatype.ValidationContext;
004    
005    class Base64BinaryDatatype extends BinaryDatatype {
006      static private final byte[] weightTable = makeWeightTable();
007      static private final byte INVALID = (byte)-1;
008      static private final byte WHITESPACE = (byte)-2;
009      static private final byte PADDING = (byte)-3;
010    
011      // for efficiency, don't assume whitespace normalized
012      boolean lexicallyAllows(String str) {
013        return byteCount(str) >= 0;
014      }
015    
016      private static int byteCount(String str) {
017        int nChars = 0;
018        int nPadding = 0;
019        int lastCharWeight = -1;
020        for (int i = 0, len = str.length(); i < len; i++) {
021          char c = str.charAt(i);
022          if (c >= 128)
023            return -1;
024          int w = weightTable[c];
025          switch (w) {
026          case WHITESPACE:
027            break;
028          case PADDING:
029            if (++nPadding > 2)
030              return -1;
031            break;
032          case INVALID:
033            return -1;
034          default:
035            if (nPadding > 0)
036              return -1;
037            lastCharWeight = w;
038            nChars++;
039            break;
040          }
041        }
042        if (((nChars + nPadding) & 0x3) != 0)
043          return -1;
044        switch (nPadding) {
045        case 1:
046          // 1 padding char; last quartet specifies 2 bytes = 16 bits = 6 + 6 + 4 bits
047          // lastChar must have 6 - 4 = 2 unused bits
048          if ((lastCharWeight & 0x3) != 0)
049            return -1;
050          break;
051        case 2:
052          // 2 padding chars; last quartet specifies 1 byte = 8 bits = 6 + 2 bits
053          // lastChar must have 6 - 2 = 4 unused bits
054          if ((lastCharWeight & 0xF) != 0)
055            return -1;
056          break;
057        }
058        return ((nChars + nPadding) >> 2)*3 - nPadding;
059      }
060    
061      Object getValue(String str, ValidationContext vc) {
062        int nBytes = byteCount(str);
063        byte[] value = new byte[nBytes];
064        int valueIndex = 0;
065        int nBytesAccum = 0;
066        int accum = 0;
067        for (int i = 0, len = str.length(); i < len; i++) {
068          int w = weightTable[str.charAt(i)];
069          if (w != WHITESPACE) {
070            accum <<= 6;
071            if (w != PADDING)
072              accum |= w;
073            if (++nBytesAccum == 4) {
074              for (int shift = 16; shift >= 0; shift -= 8) {
075                if (valueIndex < nBytes)
076                  value[valueIndex++] = (byte)((accum >> shift) & 0xFF);
077              }
078              nBytesAccum = 0;
079              accum = 0;
080            }
081          }
082        }
083        return value;
084      }
085    
086      static private byte[] makeWeightTable() {
087        byte[] w = new byte[128];
088        byte n = INVALID;
089        for (int i = 0; i < 128; i++)
090          w[i] = n;
091        n = 0;
092        for (int i = 'A'; i <= 'Z'; i++, n++)
093          w[i] = n;
094        for (int i = 'a'; i <= 'z'; i++, n++)
095          w[i] = n;
096        for (int i = '0'; i <= '9'; i++, n++)
097          w[i] = n;
098        w['+'] = n++;
099        w['/'] = n++;
100        w[' '] = w['\t'] = w['\r'] = w['\n'] = WHITESPACE;
101        w['='] = PADDING;
102        return w;
103      }
104    
105    }