package textbender.g.util; // Copyright 2007, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Textbender Software"), to deal in the Textbender Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Textbender Software, and to permit persons to whom the Textbender Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Textbender Software. THE TEXTBENDER SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TEXTBENDER SOFTWARE OR THE USE OR OTHER DEALINGS IN THE TEXTBENDER SOFTWARE. import java.io.*; //import java.nio.*; import java.util.*; import textbender.g.io.*; import textbender.g.lang.*; import textbender.o.*; /** UUID-to-string converter. */ public @ThreadRestricted final class UUIDStringifier { public UUIDStringifier() { stringOut = new StringOutputStream ( new StringBuilder(/*initial capacity*/digitsPerUUID + 1), "US-ASCII" ); // + 1 spare base64Out = new Base64.OutputStream ( stringOut, Base64.ENCODE|Base64.DONT_BREAK_LINES|Base64.URL_SAFE ); // DONT_BREAK_LINES is superfluous, given the short length of input } /** Converts the UUID to a Base64 string, valid as an XML 'name'. * The encoding is the "URL and filename safe" variation of Base64 * described in Section 4 of RFC3548, * with two alterations. First, if the leading character is not valid * for an XML name, then an underscore '_' is prefixed to the result string. * Second, any trailing pad characters '=' are deleted. * The result is either a string of 22 characters, in the pattern: *
* [A-Za-z_][A-Za-z_0-9-]{21} *
*

* Or 23 characters, in the pattern: *

*
* _[0-9-][A-Za-z_0-9-]{21} *
*/ public String toBase64Name( UUID uuid ) { stringOut.reset(); try // can find no standard for UUID encoding in Base 64, so just wing it: { writeBase64( uuid.getMostSignificantBits() ); writeBase64( uuid.getLeastSignificantBits() ); base64Out.flushBase64(); base64Out.flush(); } catch( IOException x ) { throw new RuntimeException( x ); } // not expected final StringBuilder b = stringOut.stringBuilder(); for( int cLast = b.length() - 1, p = 0; p < 3; ++p ) // up to 3 trailing frame pads '=' (actually, exactly 2 for 128 bits of input) { int c = cLast - p; if( b.charAt(c) != '=' ) break; b.deleteCharAt( c ); // trim it } final char c0 = b.charAt( 0 ); boolean c0Allowed = // per XML name c0 >= 'A' && c0 <= 'Z' || c0 >= 'a' && c0 <= 'z' || c0 == '_'; if( !c0Allowed ) b.insert( 0, '_' ); return b.toString(); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private final Base64.OutputStream base64Out; private static final int digitsPerUUID; static { final int bitsPerUUID = 128; final int bitsPerFrame = 24; // Base 64 outputs in frames final int bitsPerDigit = 6; // 64 = 2^6 final int digitsPerFrame = bitsPerFrame / bitsPerDigit; final int framesPerUUID = (int)Math.ceil( (float)bitsPerUUID / bitsPerFrame ); digitsPerUUID = framesPerUUID * digitsPerFrame; } private final StringOutputStream stringOut; private void writeBase64( long n ) throws IOException { final int highByteIndex = 7; for( int bitIndex = highByteIndex * 8; bitIndex >= 0; bitIndex -= 8 ) // big endian { // base64Out.write( (byte)( n >> bitIndex )); base64Out.write( (int)( n >> bitIndex )); // write(int) writes the low byte } } }