/*
* JacORB - a free Java ORB
*
* Copyright (C) 1997-2004 Gerald Brose.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.jacorb.orb.giop;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Properties;
import org.slf4j.*;
import org.slf4j.LoggerFactory;
import org.jacorb.config.*;
import org.jacorb.orb.CDRInputStream;
import org.jacorb.orb.CDROutputStream;
import org.omg.CONV_FRAME.CodeSetComponent;
import org.omg.CONV_FRAME.CodeSetComponentInfo;
import org.omg.CONV_FRAME.CodeSetContext;
import org.omg.CONV_FRAME.CodeSetContextHelper;
import org.omg.CORBA.CODESET_INCOMPATIBLE;
import org.omg.CORBA.MARSHAL;
import org.omg.IOP.ServiceContext;
import org.omg.IOP.TAG_CODE_SETS;
/**
* @author Gerald Brose
* @version $Id: CodeSet.java,v 1.26 2009-05-03 21:35:55 andre.spiegel Exp $
*/
public class CodeSet
{
/**
* <code>ISO8859_1</code> represents standard ASCII.
* It is ISO 8859-1:1987; Latin Alphabet No. 1
*/
private static final CodeSet ISO8859_1_CODESET = new Iso8859_1CodeSet();
/**
* <code>UTF8</code> represents UTF8 1-6 bytes for every character
* X/Open UTF-8; UCS Transformation Format 8 (UTF-8)
*/
private static final CodeSet UTF8_CODESET = new Utf8CodeSet();
/**
* <code>UTF16</code> represents extended UCS2, 2 or 4 bytes for every char
* ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form
*/
private static final CodeSet UTF16_CODESET = new Utf16CodeSet();
/**
* <code>UCS2</code> represents UCS2, 2bytes for every char
* ISO/IEC 10646-1:1993; UTF-16, UCS Transformation Format 16-bit form
*/
private static final CodeSet UCS2_CODESET = new Ucs2CodeSet();
/**
* A 'null object' code set instance, used when no matching codeset is found.
*/
private static final CodeSet NULL_CODE_SET = new CodeSet( -1, "NO SUCH CODESET" );
/**
* All of the encodings supported by Jacorb. These should be listed in order of preference.
*/
private static CodeSet[] KNOWN_ENCODINGS = { ISO8859_1_CODESET, UTF16_CODESET, UTF8_CODESET , UCS2_CODESET };
/**
* <code>logger</code> is the static logger for Codeset.
*/
private static Logger logger = LoggerFactory.getLogger("jacorb.codeset");
/** static flag that keeps track of the configuration status. */
private static boolean isConfigured = false;
/** The native code set for character data. */
private static CodeSet nativeCodeSetChar = null; // will select from platform default;
/** The native code set for wide character data. */
private static CodeSet nativeCodeSetWchar = UTF16_CODESET;
/** The definition of locally supported code sets provided to clients. */
private static CodeSetComponentInfo localCodeSetComponentInfo;
/** The standard CORBA identifier associated with this code set; used during negotiation. */
private int id;
/** The canonical name of this code set. */
private String name;
public static String csName(int cs)
{
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
if (cs == KNOWN_ENCODINGS[i].getId()) return KNOWN_ENCODINGS[i].getName();
}
return "Unknown TCS: " + toCodeSetString( cs );
}
/**
* <code>configure</code> configures the logger and codesets. It is
* synchronized as the configuration parameters are static and therefore
* we do not want to 'collide' with another init.
*
* This class does not implement configurable which ideally it should. However
* as this method is static it would conflict with it.
*
* @param config a <code>Configuration</code> value
* @exception ConfigurationException if an error occurs
*/
public synchronized static void configure (Configuration config) throws ConfigurationException
{
// Only do this once per JVM.
if (!isConfigured)
{
String ncsc = config.getAttribute("jacorb.native_char_codeset", "");
String ncsw = config.getAttribute("jacorb.native_wchar_codeset", "");
if (ncsc != null && ! ("".equals (ncsc)))
{
CodeSet codeset = getCodeSet(ncsc);
if (codeset != NULL_CODE_SET)
{
nativeCodeSetChar = codeset;
}
else if (logger.isErrorEnabled())
{
logger.error("Cannot set default NCSC to " + ncsc);
}
}
if (ncsw != null && ! ("".equals (ncsw)))
{
CodeSet codeset = getCodeSet(ncsc);
if (codeset != NULL_CODE_SET)
{
nativeCodeSetWchar = codeset;
}
else if (logger.isErrorEnabled())
{
logger.error("Cannot set default NCSW to " + ncsw);
}
}
logger = config.getLogger("jacorb.codeset");
isConfigured = true;
}
else
{
if (logger.isDebugEnabled())
{
logger.debug
("CodeSet is already configured; further attempts to reconfigure will be ignored!");
}
}
}
/**
* Returns the code set which matches the specified name, which should either be the canonical name of
* a supported encoding or the hex representation of its ID.
* @param name the string used to select a codeset.
* @return the matching code set or NULL_CODE_SET if there are no matches.
*/
public static CodeSet getCodeSet( String name ) {
String ucName = name.toUpperCase();
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
CodeSet codeset = KNOWN_ENCODINGS[i];
if (codeset.getName().equals( ucName )) return codeset;
}
try
{
int id = Integer.parseInt( name, 16 );
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
CodeSet codeset = KNOWN_ENCODINGS[i];
if (id == codeset.getId()) return codeset;
}
return NULL_CODE_SET;
}
catch (NumberFormatException ex)
{
return NULL_CODE_SET;
}
}
/**
* Returns the code set which matches the specified ID.
* @ return the matching code set or NULL_CODE_SET if there are no matches.
*/
public static CodeSet getCodeSet( int id ) {
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
CodeSet codeset = KNOWN_ENCODINGS[i];
if (id == codeset.id) return codeset;
}
return NULL_CODE_SET;
}
public static CodeSet getTCSDefault()
{
if (nativeCodeSetChar != null) return nativeCodeSetChar;
String sysenc = getDefaultEncoding();
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
CodeSet codeset = KNOWN_ENCODINGS[i];
if (codeset.supportsCharacterData( /* wide */ false ) && sysenc.equals( codeset.getName() ))
{
return setNativeCodeSetChar( codeset );
}
}
// didn't match any supported char encodings, default to iso 8859-1
if (logger.isWarnEnabled())
{
logger.warn( "Warning - unknown codeset (" + sysenc + ") - defaulting to ISO-8859-1" );
}
return setNativeCodeSetChar( ISO8859_1_CODESET );
}
private static CodeSet setNativeCodeSetChar( CodeSet codeset )
{
nativeCodeSetChar = codeset;
if( logger.isDebugEnabled() )
{
logger.debug("TCS set to " + codeset.getName() );
}
return codeset;
}
private static String getDefaultEncoding()
{
// See http://java.sun.com/j2se/1.4.1/docs/guide/intl/encoding.doc.html for
// a list of encodings and their canonical names.
//
// http://developer.java.sun.com/developer/bugParade/bugs/4772857.html
//
// This allows me to get the actual canonical name of the encoding as the
// System property may differ depending upon locale and OS.
OutputStreamWriter defaultStream = new OutputStreamWriter( new ByteArrayOutputStream() );
String sysenc = defaultStream.getEncoding();
try
{
defaultStream.close();
}
catch( IOException e ) {}
return sysenc;
}
public static CodeSet getTCSWDefault()
{
return nativeCodeSetWchar;
}
public static ServiceContext createCodesetContext( CodeSet tcs, CodeSet tcsw )
{
// encapsulate context
final CDROutputStream os = new CDROutputStream();
try
{
os.beginEncapsulatedArray();
CodeSetContextHelper.write( os, new CodeSetContext( tcs.getId(), tcsw.getId() ));
return new ServiceContext( TAG_CODE_SETS.value, os.getBufferCopy() );
}
finally
{
os.close();
}
}
public static CodeSetContext getCodeSetContext( ServiceContext[] contexts )
{
for( int i = 0; i < contexts.length; i++ )
{
if( contexts[i].context_id == TAG_CODE_SETS.value )
{
// TAG_CODE_SETS found, demarshall
CDRInputStream is = new CDRInputStream( null, contexts[i].context_data );
is.openEncapsulatedArray();
return CodeSetContextHelper.read( is );
}
}
return null;
}
public static CodeSetComponentInfo getLocalCodeSetComponentInfo()
{
if (localCodeSetComponentInfo == null)
{
localCodeSetComponentInfo = new CodeSetComponentInfo();
localCodeSetComponentInfo.ForCharData = createCodeSetComponent( /* wide */ false, getTCSDefault() );
localCodeSetComponentInfo.ForWcharData = createCodeSetComponent( /* wide */ true, getTCSWDefault() );
}
return localCodeSetComponentInfo;
}
private static CodeSetComponent createCodeSetComponent( boolean wide, CodeSet nativeCodeSet )
{
ArrayList codeSets = new ArrayList( );
codeSets.add( nativeCodeSet );
for (int i = 0; i < KNOWN_ENCODINGS.length; i++)
{
if (KNOWN_ENCODINGS[i].supportsCharacterData( wide ) && !codeSets.contains( KNOWN_ENCODINGS[i] ))
{
codeSets.add( KNOWN_ENCODINGS[i] );
}
}
int nativeSet = ((CodeSet) codeSets.remove( 0 )).getId();
int[] conversionSets = new int[codeSets.size()];
for (int i = 0; i < conversionSets.length; i++)
{
conversionSets[i] = ((CodeSet) codeSets.get(i)).getId();
}
return new CodeSetComponent( nativeSet, conversionSets );
}
public static CodeSet getNegotiatedCodeSet( CodeSetComponentInfo serverCodeSetInfo, boolean wide )
{
return getMatchingCodeSet( getSelectedComponent( getLocalCodeSetComponentInfo(), wide ),
getSelectedComponent( serverCodeSetInfo, wide ),
wide );
}
public static CodeSet getMatchingCodeSet( CodeSetComponent local, CodeSetComponent remote, boolean wide )
{
CodeSet codeSet = getCodeSetIfMatched( local.native_code_set, remote );
if (codeSet != null) return codeSet;
for (int i = 0; i < local.conversion_code_sets.length; i++)
{
codeSet = getCodeSetIfMatched( local.conversion_code_sets[i], remote );
if (codeSet != null) return codeSet;
}
return reportNegotiationFailure( local, remote, wide );
}
public static CodeSet getCodeSetIfMatched( int localCodeSetId, CodeSetComponent remote )
{
if (localCodeSetId == remote.native_code_set)
{
return getCodeSet( localCodeSetId );
}
else
{
for (int i = 0; i < remote.conversion_code_sets.length; i++)
{
if (localCodeSetId == remote.conversion_code_sets[i])
{
return getCodeSet( localCodeSetId );
}
}
}
return null;
}
private static CodeSet reportNegotiationFailure( CodeSetComponent local, CodeSetComponent remote, boolean wide )
{
StringBuffer sb = new StringBuffer( "No matching ");
if (wide) sb.append( "wide " );
sb.append( "code set found. Client knows {" );
appendCodeSetList( sb, local );
sb.append( "}. Server offered {" );
appendCodeSetList( sb, remote );
sb.append( '}' );
throw new CODESET_INCOMPATIBLE( sb.toString() );
}
private static void appendCodeSetList( StringBuffer sb, CodeSetComponent remote )
{
int code_set = remote.native_code_set;
sb.append( toCodeSetString( code_set ) );
for (int i = 0; i < remote.conversion_code_sets.length; i++) {
sb.append( ',' ).append( toCodeSetString( remote.conversion_code_sets[i] ) );
}
}
private static final String CODESET_PREFIX = "0x00000000";
private static String toCodeSetString( int code_set )
{
String rawString = Integer.toHexString( code_set );
return CODESET_PREFIX.substring( 0, CODESET_PREFIX.length() - rawString.length() ) + rawString;
}
private static CodeSetComponent getSelectedComponent( CodeSetComponentInfo info, boolean wide )
{
return wide ? info.ForWcharData : info.ForCharData;
}
/**
* This interface represents a buffer from which character data can be read.
*/
public static interface InputBuffer
{
/** Reads the next byte from an in-memory buffer. */
public byte readByte();
/** Returns the current position in the buffer. */
int get_pos();
/**
* Looks ahead in the buffer to see if a byte-order marker is present. If so, reads it from the buffer
* and returns the result.
* @return true if a marker indicating little-endian was read.
*/
boolean readBOM();
}
/**
* Represents a buffer to which character data may be written.
*/
public static interface OutputBuffer
{
/**
* Writes the specified byte to the buffer.
* @param b the byte to write
*/
void write_byte( byte b );
/**
* Forces short (2-byte) alignment and writes the specified value to the buffer.
* @param value the value to write.
*/
void write_short( short value );
}
public CodeSet( int id, String name )
{
this.id = id;
this.name = name;
}
/**
* Returns true if this codeset supports the specified character type.
* @param wide
*/
public boolean supportsCharacterData( boolean wide )
{
return false;
}
/**
* Returns true if this codeset supports multie-byte characters
*/
public boolean supportsWideCharacterData()
{
return false;
}
/**
* Returns the CORBA-standard id for this code set.
*/
public int getId()
{
return id;
}
/**
* Returns the canonical name of this code set.
*/
public String getName()
{
return name;
}
/**
* Returns true if this code set requires byte-order-markers to be written to the beginning of a stream of text.
* @param configuredForBom true if the orb has been configured to write byte-order-markers.
*/
public boolean write_bom( boolean configuredForBom )
{
return false;
}
/**
* Reads a wide character from the specified buffer.
* @param buffer the buffer containing the data.
* @param giop_minor the low-order byte of the giop version (1.x is assumed)
* @param littleEndian true if the character is to be read low end first
* @return the wide character.
*/
public char read_wchar( InputBuffer buffer, int giop_minor, boolean littleEndian )
{
throw new MARSHAL( "Bad wide char codeSet: " + getName() );
}
/**
* Reads a wide string from the buffer. The length indicator is presumed already to have been read.
* @param buffer the buffer from which to read the string
* @param lengthIndicator the length indicator already read
* @param giop_minor the low-order byte of the giop version (1.x is assumed)
* @param littleEndian true if the characters are to be read low end first
* @return a string possibly containing wide characters.
*/
public String read_wstring( InputBuffer buffer, int lengthIndicator, int giop_minor, boolean littleEndian )
{
throw new MARSHAL( "Bad wide char codeSet: " + getName() );
}
/**
* Writes a character to the buffer with the appropriate encoding.
* @param buffer the buffer to which the character is written
* @param c the character to write
* @param write_bom true if a byte-order-marker (indicating big-endian) should be written
* @param write_length true if the length of the character should be written
* @param giop_minor the low-order byte of the giop version (1.x is assumed)
*/
public void write_char( OutputBuffer buffer, char c, boolean write_bom, boolean write_length, int giop_minor )
{
throw new CODESET_INCOMPATIBLE("Bad codeset: " + getName() );
}
/**
* Returns the length of the string just written to the buffer.
* @param string the string written
* @param startPos the starting position at which the string was written
* @param currentPos the current buffer position
*/
public int get_wstring_size( String string, int startPos, int currentPos )
{
return 0;
}
/**
* Reads a wide string from the buffer according to GIOP 1.2. The length indicator is presumed already to have been read.
* @param buffer the buffer from which to read the string
* @param size the length indicator already read
* @param giop_minor the low-order byte of the giop version (must be >= 2)
* @return a string possibly containing wide characters.
*/
final protected String readGiop12WString( InputBuffer buffer, int size, int giop_minor )
{
char buf[] = new char[ size ];
int endPos = buffer.get_pos() + size;
boolean wchar_litte_endian = buffer.readBOM();
int i = 0;
while( buffer.get_pos() < endPos )
{
buf[ i++ ] = read_wchar( buffer, giop_minor, wchar_litte_endian );
}
return new String( buf, 0, i );
}
static private class Iso8859_1CodeSet extends CodeSet {
private Iso8859_1CodeSet()
{
super( 0x00010001, "ISO8859_1" );
}
/**
* Returns true if 'wide' is not specified.
*/
public boolean supportsCharacterData( boolean wide )
{
return !wide;
}
public void write_char( OutputBuffer buffer, char c, boolean write_bom, boolean write_length_indicator, int giop_minor )
{
buffer.write_byte( (byte) c );
}
}
static private class Utf8CodeSet extends CodeSet {
private Utf8CodeSet( )
{
super( 0x05010001, "UTF8" );
}
/**
* Returns true for both wide and non-wide characters.
*/
public boolean supportsCharacterData( boolean wide )
{
return true;
}
public char read_wchar( InputBuffer buffer, int giop_minor, boolean littleEndian )
{
if (giop_minor < 2)
{
throw new MARSHAL( "GIOP 1." + giop_minor +
" only allows 2 Byte encodings for wchar, but the selected TCSW is UTF-8" );
}
short value = (short) (0xff & buffer.readByte());
if ((value & 0x80) == 0)
{
return (char) value;
}
else if ((value & 0xe0) == 0xc0)
{
return (char) (((value & 0x1F) << 6) | (buffer.readByte() & 0x3F));
}
else
{
short b2 = (short) (0xff & buffer.readByte());
return (char) (((value & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (buffer.readByte() & 0x3F));
}
}
public String read_wstring( InputBuffer source, int lengthIndicator, int giop_minor, boolean little_endian )
{
if (giop_minor < 2) throw new MARSHAL( "Bad wide char codeSet: " + getName() );
return readGiop12WString( source, lengthIndicator, giop_minor );
}
public void write_char( OutputBuffer buffer, char c, boolean write_bom, boolean write_length_indicator, int giop_minor )
{
if( c <= 0x007F )
{
if( giop_minor == 2 && write_length_indicator )
{
//the chars length in bytes
buffer.write_byte( (byte) 1);
}
buffer.write_byte( (byte) c );
}
else if( c > 0x07FF )
{
if( giop_minor == 2 && write_length_indicator )
{
//the chars length in bytes
buffer.write_byte( (byte) 3 );
}
buffer.write_byte( (byte)(0xE0 | ((c >> 12) & 0x0F)) );
buffer.write_byte( (byte)(0x80 | ((c >> 6) & 0x3F)) );
buffer.write_byte( (byte)(0x80 | ((c >> 0) & 0x3F)) );
}
else
{
if( giop_minor == 2 && write_length_indicator )
{
buffer.write_byte( (byte) 2 ); //the chars length in bytes
}
buffer.write_byte( (byte)(0xC0 | ((c >> 6) & 0x1F)) );
buffer.write_byte( (byte)(0x80 | ((c >> 0) & 0x3F)) );
}
}
public int get_wstring_size( String s, int startPos, int currentPos )
{
return currentPos - startPos - 4;
}
}
static abstract private class TwoByteCodeSet extends CodeSet
{
protected TwoByteCodeSet( int id, String name )
{
super( id, name );
}
/**
* Returns true if the character type specified is 'wide'.
*/
public boolean supportsCharacterData( boolean wide )
{
return wide;
}
public char read_wchar( InputBuffer buffer, int giop_minor, boolean littleEndian )
{
if (littleEndian)
{
return (char) ((buffer.readByte() & 0xFF) | (buffer.readByte() << 8));
}
else
{
return (char) ((buffer.readByte() << 8) | (buffer.readByte() & 0xFF));
}
}
public String read_wstring( InputBuffer source, int lengthIndicator, int giop_minor, boolean little_endian )
{
if( giop_minor == 2 )
{
return readGiop12WString( source, lengthIndicator, giop_minor );
}
else //GIOP 1.1 / 1.0 : length indicates number of 2-byte characters
{
char buf[] = new char[lengthIndicator];
int endPos = source.get_pos() + 2* lengthIndicator;
int i = 0;
while( source.get_pos() < endPos )
{
buf[ i++ ] = read_wchar( source, giop_minor, little_endian );
}
if( (i != 0) && (buf[ i - 1 ] == 0) ) //don't return terminating NUL
{
return new String( buf, 0, i - 1 );
}
else //doesn't have a terminating NUL. This is actually not allowed
{
return new String( buf, 0, i );
}
}
}
public void write_char( OutputBuffer buffer, char c, boolean write_bom, boolean write_length_indicator, int giop_minor )
{
if (giop_minor < 2)
{
buffer.write_short( (short) c ); //UTF-16 char is treated as an ushort (write aligned)
}
else
{
if (write_length_indicator) //the chars length in bytes
{
buffer.write_byte( (byte) 2 );
}
if (write_bom) //big endian encoding
{
buffer.write_byte( (byte) 0xFE );
buffer.write_byte( (byte) 0xFF );
}
//write unaligned
buffer.write_byte( (byte)((c >> 8) & 0xFF) );
buffer.write_byte( (byte) (c & 0xFF) );
}
}
public int get_wstring_size( String s, int startPos, int currentPos )
{
return s.length() + 1; // size in chars (+ NUL char)
}
}
static private class Utf16CodeSet extends TwoByteCodeSet
{
private Utf16CodeSet()
{
super( 0x00010109, "UTF16" );
}
/** Returns the configured value to use BOMs only when specifically configured to do so. */
public boolean write_bom( boolean configuredForBom )
{
return false;
}
}
static private class Ucs2CodeSet extends TwoByteCodeSet
{
private Ucs2CodeSet()
{
super( 0x00010100, "UCS2" );
}
/** Returns true to force a BOM at the start of every text stream. */
public boolean write_bom( boolean configuredForBom )
{
return true;
}
}
/*
* This is useful for debugging to print out Operating System details and
* the encoding of that system.
*
* Currently this prints the following information:
* Operating System
* OS Version
* OS Architecture
* User Region
* Java Version
* JacORB Version
* System File Encoding
* Cannonical Encoding
* If we are running on a Unix system and have used the command line argument
* '-a' then it also runs the commands:
* locale
* locale -a
*
* Remember the precendence levels of LC_ALL, LANG, LC_CTYPE etc. Preferred
* way to override for *all* categories is to set LC_ALL. If you just set LANG
* then if any other LC_* categories are set then these will take precedence.
* See http://publib16.boulder.ibm.com/pseries/en_US/aixprggd/nlsgdrf/locale_env.htm
*/
public static void main (String args[])
{
if (args != null && args.length > 0 &&
(args[0].equals("-h") || ! args[0].equals("-a")))
{
System.out.println("Usage: org.jacorb.orb.connection.CodeSet [-a]");
System.exit(1);
}
Properties props = System.getProperties ();
String osName = (String)props.get ("os.name");
System.out.println ("Operating system name: " + osName);
System.out.println ("Operating system version: " + props.get ("os.version"));
System.out.println ("Operating system architecture: " + props.get ("os.arch"));
System.out.println ("User region: " + System.getProperty( "user.region"));
System.out.println ("JVM: " + props.get ("java.vm.version"));
System.out.println ("JacORB: " + org.jacorb.util.Version.longVersion);
System.out.println("System file encoding property: " + System.getProperty( "file.encoding" ));
String defaultIOEncoding = (new OutputStreamWriter(new ByteArrayOutputStream ())).getEncoding();
System.out.println("Cannonical encoding: " + defaultIOEncoding);
System.out.println("Default WChar encoding: " + nativeCodeSetWchar.getName() );
// If we're not using Windows do some extra debug, printing out the locale information.
if ((osName.toLowerCase ()).indexOf ("windows") == -1 &&
args != null &&
args.length == 1 &&
args[0].equals ("-a"))
{
System.out.println("Locale is:");
try
{
Process locale = Runtime.getRuntime().exec ("locale");
BufferedReader buffer = new BufferedReader
(new InputStreamReader (locale.getInputStream()));
while (true)
{
String line = buffer.readLine();
if (line == null)
{
break;
}
System.out.println(" " + line);
}
buffer.close();
}
catch (IOException e)
{
System.err.println("Caught exception " + e);
}
System.out.println("All available locales are:");
try
{
Process locale = Runtime.getRuntime().exec ("locale -a");
BufferedReader buffer = new BufferedReader
(new InputStreamReader (locale.getInputStream()));
while (true)
{
String line = buffer.readLine();
if (line == null)
{
break;
}
System.out.println(" " + line);
}
buffer.close();
}
catch (IOException e)
{
System.err.println("Caught exception " + e);
}
}
}
}