blob: b71e28cdd567abe189dcb51ef310106bf3156ba5 [file] [log] [blame]
/*
**********************************************************************
* Copyright (c) 2002-2008, International Business Machines
* Corporation and others. All Rights Reserved.
**********************************************************************
*/
package com.ibm.icu.dev.test.perf;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PushbackInputStream;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import com.ibm.icu.dev.tool.UOption;
import com.ibm.icu.impl.LocaleUtility;
/**
* Base class for performance testing framework. To use, the subclass can simply
* define one or more instance methods with names beginning with "test" (case
* ignored). The prototype of the method is
*
* PerfTest.Function testTheName()
*
* The actual performance test will execute on the returned Commond object
* (refer to Command Pattern). To call a test from command line, the 'test'
* prefix of the test method name can be ignored/removed.
*
* In addition, the subclass should define a main() method that calls
* PerfTest.run() as defined here.
*
* If the subclasses uses any command line arguments (beyond those handled
* automatically by this calss) then it should override PerfTest.setup() to
* handle its arguments. If the subclasse needs more sophisticated management
* for controlling finding/calling test method, it can replace the default
* implementation for PerfTest.testProvider before calling PerfTest.run().
*
* Example invocation: java -cp classes -verbose:gc
* com.ibm.icu.dev.test.perf.UnicodeSetPerf --gc --passes 4 --iterations 100
* UnicodeSetAdd [[:l:][:c:]]
*
* Example output: [GC 511K->192K(1984K), 0.0086170 secs] [GC 704K->353K(1984K),
* 0.0059619 secs] [Full GC 618K->371K(1984K), 0.0242779 secs] [Full GC
* 371K->371K(1984K), 0.0228649 secs] = testUnicodeSetAdd begin 100 =
* testUnicodeSetAdd end 11977 1109044 = testUnicodeSetAdd begin 100 =
* testUnicodeSetAdd end 12047 1109044 = testUnicodeSetAdd begin 100 =
* testUnicodeSetAdd end 11987 1109044 = testUnicodeSetAdd begin 100 =
* testUnicodeSetAdd end 11978 1109044
*
* The [] lines are emitted by the JVM as a result of the -verbose:gc switch.
*
* Lines beginning with '=' are emitted by PerfTest: = testUnicodeSetAdd begin
* 100 A 'begin' statement contains the name of the setup method, which
* determines what test function is measures, and the number of iterations that
* will be times. = testUnicodeSetAdd end 12047 1109044 An 'end' statement gives
* the name of the setup method again, and then two integers. The first is the
* total elapsed time in milliseconds, and the second is the number of events
* per iteration. In this example, the time per event is 12047 / (100 * 1109044)
* or 108.6 ns/event.
*
* Raw times are given as integer ms, because this is what the system measures.
*
* @author Alan Liu
* @since ICU 2.4
*/
public abstract class PerfTest {
// Command-line options set these:
protected boolean verbose;
protected String sourceDir;
protected String fileName;
// protected String resolvedFileName;
protected String encoding;
protected String testName;
protected boolean uselen;
protected int iterations;
protected int passes;
protected int time;
protected boolean line_mode;
protected boolean bulk_mode;
protected Locale locale;
protected boolean doPriorGC;
protected int threads;
protected TestCmdProvider testProvider = new TestPrefixProvider(this);
static interface TestCmdProvider {
/**
* @return The names for all available test.
*/
public Set getAllTestCmdNames();
/**
* @param name
* @return Whether the given name is a test name. The implementation may
* have more sophisticated naming control here.
* TestCmdProvider.isTestCmd() != Set.contains()
*/
public boolean isTestCmd(String name);
/**
* @param name
* @return the test Command or null
*/
public PerfTest.Function getTestCmd(String name);
}
/**
* Treat all method beginning with 'test' prefix (ignoring case) for given
* object as the test methods.
*/
static class TestPrefixProvider implements TestCmdProvider {
private Map theTests = null; // Map<string(no case), string(with case)>
private Set orgNames = null; // shadow reference, ==theTests, for better output
private Object refer;
TestPrefixProvider(Object theProvider) {
refer = theProvider;
}
public Set getAllTestCmdNames() {
if (theTests == null) {
theTests = new HashMap();
orgNames = new HashSet();
Method[] methods = refer.getClass().getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
String org = methods[i].getName();
String name = org.toLowerCase(); // ignoring case
// beginning with 'test'
// Note: methods named 'test()' are ignored
if (name.length() > 4 && name.startsWith("test")) {
if (theTests.containsKey(name)) {
throw new Error(
"Duplicate method name ignoring case: "
+ name);
}
theTests.put(name, org);
orgNames.add(org);
}
}
}
return orgNames; // begining with 'test', keeping case
}
/**
* The given name will map to a method of the same name, or a method
* named "test" + name. Case is ignored.
*/
private String isTestCmd_impl(String name) {
getAllTestCmdNames();
String tn1 = name.toLowerCase();
String tn2 = "test" + tn1;
if (theTests.containsKey(tn1)) {
return tn1;
} else if (theTests.containsKey(tn2)) {
return tn2;
}
return null;
}
public boolean isTestCmd(String name) {
return isTestCmd_impl(name) != null;
}
public Function getTestCmd(String aname) {
String name = (String) theTests.get(isTestCmd_impl(aname));
if (name == null) {
return null;
}
try {
Method m = refer.getClass().getDeclaredMethod(name,
(Class[]) null);
return (Function) m.invoke(refer, new Object[] {});
} catch (Exception e) {
throw new Error(
"TestPrefixProvider implementation error. Finding: "
+ name, e);
}
}
}
/**
* Subclasses of PerfTest will need to create subclasses of Function that
* define a call() method which contains the code to be timed. They then
* call setTestFunction() in their "Test..." method to establish this as the
* current test functor.
*/
public abstract static class Function {
/**
* Subclasses should implement this method to do the action to be
* measured if the action is thread-safe
*/
public void call() { call(0); }
/**
* Subclasses should implement this method if the action is not thread-safe
*/
public void call(int i) { call(); }
/**
* Subclasses may implement this method to return positive integer
* indicating the number of operations in a single call to this object's
* call() method. If subclasses do not override this method, the default
* implementation returns 1.
*/
public long getOperationsPerIteration() {
return 1;
}
/**
* Subclasses may implement this method to return either positive or
* negative integer indicating the number of events in a single call to
* this object's call() method. If subclasses do not override this
* method, the default implementation returns -1, indicating that events
* are not applicable to this test. e.g: Number of breaks / iterations
* for break iterator
*/
public long getEventsPerIteration() {
return -1;
}
/**
* Call call() n times in a tight loop and return the elapsed
* milliseconds. If n is small and call() is fast the return result may
* be zero. Small return values have limited meaningfulness, depending
* on the underlying VM and OS.
*/
public final long time(long n) {
long start, stop;
start = System.currentTimeMillis();
while (n-- > 0) {
call();
}
stop = System.currentTimeMillis();
return stop - start; // ms
}
/**
* init is called each time before looping through call
*/
public void init() {}
public final int getID() {
return id;
}
public final void setID(int id) {
this.id = id;
}
private int id;
}
private class FunctionRunner implements Runnable {
public FunctionRunner(Function f, long loops, int id) {
this.f = f;
this.loops = loops;
this.id = id;
}
public void run() {
long n = loops;
while (n-- > 0)
f.call(id);
}
private Function f;
private long loops;
private int id;
}
/**
* Exception indicating a usage error.
*/
public static class UsageException extends Exception {
/**
* For serialization
*/
private static final long serialVersionUID = -1201256240606806242L;
public UsageException(String message) {
super(message);
}
public UsageException() {
super();
}
}
/**
* Constructor.
*/
protected PerfTest() {
}
/**
* Framework method. Default implementation does not parse any extra
* arguments. Subclasses may override this to parse extra arguments.
* Subclass implementations should NOT call the base class implementation.
*/
protected void setup(String[] args) {
if (args.length > 0) {
throw new RuntimeException("Extra arguments received");
}
}
/**
* These must be kept in sync with getOptions().
*/
static final int HELP1 = 0;
static final int HELP2 = 1;
static final int VERBOSE = 2;
static final int SOURCEDIR = 3;
static final int ENCODING = 4;
static final int USELEN = 5;
static final int FILE_NAME = 6;
static final int PASSES = 7;
static final int ITERATIONS = 8;
static final int TIME = 9;
static final int LINE_MODE = 10;
static final int BULK_MODE = 11;
static final int LOCALE = 12;
static final int TEST_NAME = 13;
static final int THREADS = 14;
// Options above here are identical to those in C; keep in sync with C
// Options below here are unique to Java; shift down as necessary
static final int GARBAGE_COLLECT = 14;
static final int LIST = 15;
UOption[] getOptions() {
return new UOption[] {
UOption.HELP_H(),
UOption.HELP_QUESTION_MARK(),
UOption.VERBOSE(),
UOption.SOURCEDIR(),
UOption.ENCODING(),
UOption.DEF("uselen", 'u', UOption.NO_ARG),
UOption.DEF("filename", 'f', UOption.REQUIRES_ARG),
UOption.DEF("passes", 'p', UOption.REQUIRES_ARG),
UOption.DEF("iterations", 'i', UOption.REQUIRES_ARG),
UOption.DEF("time", 't', UOption.REQUIRES_ARG),
UOption.DEF("line-mode", 'l', UOption.NO_ARG),
UOption.DEF("bulk-mode", 'b', UOption.NO_ARG),
UOption.DEF("locale", 'L', UOption.REQUIRES_ARG),
UOption.DEF("testname", 'T', UOption.REQUIRES_ARG),
UOption.DEF("threads", 'r', UOption.REQUIRES_ARG),
// Options above here are identical to those in C; keep in sync
// Options below here are unique to Java
UOption.DEF("gc", 'g', UOption.NO_ARG),
UOption.DEF("list", (char) -1, UOption.NO_ARG), };
}
/**
* Subclasses should call this method in their main(). run() will in turn
* call setup() with any arguments it does not parse. This method parses the
* command line and runs the tests given on the command line, with the given
* parameters. See the class description for details.
*/
protected final void run(String[] args) throws Exception {
Set testList = parseOptions(args);
// Run the tests
for (Iterator iter = testList.iterator(); iter.hasNext();) {
String meth = (String) iter.next();
// Call meth to set up the test
// long eventsPerCall = -1;
Function testFunction = testProvider.getTestCmd(meth);
if (testFunction == null) {
throw new RuntimeException(meth
+ " failed to return a test function");
}
if (testFunction.getOperationsPerIteration() < 1) {
throw new RuntimeException(meth
+ " returned an illegal operations/iteration()");
}
long t;
// long b = System.currentTimeMillis();
long loops = getIteration(meth, testFunction);
// System.out.println("The guess cost: " + (System.currentTimeMillis() - b)/1000. + " s.");
for (int j = 0; j < passes; ++j) {
long events = -1;
if (verbose) {
if (iterations > 0) {
System.out.println("= " + meth + " begin " + iterations);
} else {
System.out.println("= " + meth + " begin " + time + " seconds");
}
} else {
System.out.println("= " + meth + " begin ");
}
t = performLoops(testFunction, loops);
events = testFunction.getEventsPerIteration();
if (verbose) {
if (events == -1) {
System.out.println("= " + meth + " end " + (t / 1000.0) + " loops: " + loops + " operations: "
+ testFunction.getOperationsPerIteration());
} else {
System.out.println("= " + meth + " end " + (t / 1000.0) + " loops: " + loops + " operations: "
+ testFunction.getOperationsPerIteration() + " events: " + events);
}
} else {
if (events == -1) {
System.out.println("= " + meth + " end " + (t / 1000.0) + " " + loops + " "
+ testFunction.getOperationsPerIteration());
} else {
System.out.println("= " + meth + " end " + (t / 1000.0) + " " + loops + " "
+ testFunction.getOperationsPerIteration() + " " + events);
}
}
}
}
}
/**
* @param args
* @return the method list to call
* @throws UsageException
*/
private Set parseOptions(String[] args) throws UsageException {
doPriorGC = false;
encoding = "";
uselen = false;
fileName = null;
sourceDir = null;
line_mode = false;
verbose = false;
bulk_mode = false;
passes = iterations = time = -1;
locale = null;
testName = null;
threads = 1;
UOption[] options = getOptions();
int remainingArgc = UOption.parseArgs(args, options);
if (args.length == 0 || options[HELP1].doesOccur || options[HELP2].doesOccur)
throw new UsageException();
if (options[LIST].doesOccur) {
System.err.println("Available tests:");
Set testNames = testProvider.getAllTestCmdNames();
for (Iterator iter = testNames.iterator(); iter.hasNext();) {
String name = (String) iter.next();
System.err.println(" " + name);
}
System.exit(0);
}
if (options[TIME].doesOccur && options[ITERATIONS].doesOccur)
throw new UsageException("Cannot specify both '-t <seconds>' and '-i <iterations>'");
else if (!options[TIME].doesOccur && !options[ITERATIONS].doesOccur)
throw new UsageException("Either '-t <seconds>' or '-i <iterations>' must be specified");
else if (options[ITERATIONS].doesOccur) {
try {
iterations = Integer.parseInt(options[ITERATIONS].value);
} catch (NumberFormatException ex) {
throw new UsageException("'-i <iterations>' requires an integer number of iterations");
}
} else { //if (options[TIME].doesOccur)
try {
time = Integer.parseInt(options[TIME].value);
} catch (NumberFormatException ex) {
throw new UsageException("'-r <seconds>' requires an integer number of seconds");
}
}
if (!options[PASSES].doesOccur)
throw new UsageException("'-p <passes>' must be specified");
else
passes = Integer.parseInt(options[PASSES].value);
if (options[LINE_MODE].doesOccur && options[BULK_MODE].doesOccur)
throw new UsageException("Cannot specify both '-l' (line mode) and '-b' (bulk mode)");
if (options[THREADS].doesOccur) {
try {
threads = Integer.parseInt(options[THREADS].value);
} catch (NumberFormatException ex) {
throw new UsageException("'-r <threads>' requires an integer number of threads");
}
if (threads <= 0)
throw new UsageException("'-r <threads>' requires an number of threads greater than 0");
}
line_mode = options[LINE_MODE].doesOccur;
bulk_mode = options[BULK_MODE].doesOccur;
verbose = options[VERBOSE].doesOccur;
uselen = options[USELEN].doesOccur;
doPriorGC = options[GARBAGE_COLLECT].doesOccur;
if (options[SOURCEDIR].doesOccur) sourceDir = options[SOURCEDIR].value;
if (options[ENCODING].doesOccur) encoding = options[ENCODING].value;
if (options[FILE_NAME].doesOccur) fileName = options[FILE_NAME].value;
if (options[TEST_NAME].doesOccur) testName = options[TEST_NAME].value;
if (options[LOCALE].doesOccur) locale = LocaleUtility.getLocaleFromName(options[LOCALE].value);
// build the test list
Set testList = new HashSet();
int i, j;
for (i = 0; i < remainingArgc; ++i) {
// is args[i] a method name?
if (testProvider.isTestCmd(args[i])) {
testList.add(args[i]);
} else {
// args[i] is neither a method name nor a number. Pass
// everything from here on through to the subclass via
// setup().
break;
}
}
// if no tests were specified, put all the tests in the test list
if (testList.size() == 0) {
Set testNames = testProvider.getAllTestCmdNames();
Iterator iter = testNames.iterator();
while (iter.hasNext())
testList.add((String)iter.next());
}
// pass remaining arguments, if any, through to the subclass via setup() method.
String[] subclassArgs = new String[remainingArgc - i];
for (j = 0; i < remainingArgc; j++)
subclassArgs[j] = args[i++];
setup(subclassArgs);
// Put the heap in a consistent state
if (doPriorGC)
gc();
return testList;
}
/**
* Translate '-t time' to iterations (or just return '-i iteration')
*
* @param meth
* @param fn
* @return rt
*/
private long getIteration(String methName, Function fn) throws InterruptedException {
long iter = 0;
if (time < 0) { // && iterations > 0
iter = iterations;
} else { // && iterations < 0
// Translate time to iteration
// Assuming there is a linear relation between time and iterations
if (verbose) {
System.out.println("= " + methName + " calibrating " + time
+ " seconds");
}
long base = time * 1000;
// System.out.println("base :" + base);
long seed = 1;
long t = 0;
while (t < base * 0.9 || base * 1.1 < t) { // + - 10%
if (iter == 0 || t == 0) {
iter = seed; // start up from 1
seed *= 100; // if the method is too fast (t == 0),
// multiply 100 times
// 100 is rational because 'base' is always larger than 1000
} else {
// If 't' is large enough, use linear function to calculate
// new iteration
//
// new iter(base) old iter
// -------------- = -------- = k
// new time old time
//
// System.out.println("before guess t: " + t);
// System.out.println("before guess iter: " + iter);
iter = (long) ((double) iter / t * base); // avoid long
// cut, eg. 1/10
// == 0
if (iter == 0) {
throw new RuntimeException(
"Unable to converge on desired duration");
}
}
t = performLoops(fn, iter);
}
// System.out.println("final t : " + t);
// System.out.println("final i : " + iter);
}
return iter;
}
private long performLoops(Function function, long loops) throws InterruptedException {
function.init();
if (threads > 1) {
Thread[] threadList = new Thread[threads];
for (int i=0; i<threads; i++)
threadList[i] = new Thread(new FunctionRunner(function, loops, i));
long start = System.currentTimeMillis();
for (int i=0; i<threads; i++)
threadList[i].start();
for (int i=0; i<threads; i++)
threadList[i].join();
return System.currentTimeMillis() - start;
} else {
return function.time(loops); // ms
}
}
/**
* Invoke the runtime's garbage collection procedure repeatedly until the
* amount of free memory stabilizes to within 10%.
*/
protected void gc() {
if (false) {
long last;
long free = 1;
Runtime runtime = Runtime.getRuntime();
do {
runtime.gc();
last = free;
free = runtime.freeMemory();
} while (((double) Math.abs(free - last)) / free > 0.1);
// Wait for the change in free memory to drop under 10%
// between successive calls.
}
// From "Java Platform Performance". This is the procedure
// recommended by Javasoft.
try {
System.gc();
Thread.sleep(100);
System.runFinalization();
Thread.sleep(100);
System.gc();
Thread.sleep(100);
System.runFinalization();
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
public static char[] readToEOS(Reader reader) {
ArrayList vec = new ArrayList();
int count = 0;
int pos = 0;
final int MAXLENGTH = 0x8000; // max buffer size - 32K
int length = 0x80; // start with small buffers and work up
do {
pos = 0;
length = length >= MAXLENGTH ? MAXLENGTH : length * 2;
char[] buffer = new char[length];
try {
do {
int n = reader.read(buffer, pos, length - pos);
if (n == -1) {
break;
}
pos += n;
} while (pos < length);
}
catch (IOException e) {
}
vec.add(buffer);
count += pos;
} while (pos == length);
char[] data = new char[count];
pos = 0;
for (int i = 0; i < vec.size(); ++i) {
char[] buf = (char[]) vec.get(i);
int len = Math.min(buf.length, count - pos);
System.arraycopy(buf, 0, data, pos, len);
pos += len;
}
return data;
}
public static byte[] readToEOS(InputStream stream) {
ArrayList vec = new ArrayList();
int count = 0;
int pos = 0;
final int MAXLENGTH = 0x8000; // max buffer size - 32K
int length = 0x80; // start with small buffers and work up
do {
pos = 0;
length = length >= MAXLENGTH ? MAXLENGTH : length * 2;
byte[] buffer = new byte[length];
try {
do {
int n = stream.read(buffer, pos, length - pos);
if (n == -1) {
break;
}
pos += n;
} while (pos < length);
}
catch (IOException e) {
}
vec.add(buffer);
count += pos;
} while (pos == length);
byte[] data = new byte[count];
pos = 0;
for (int i = 0; i < vec.size(); ++i) {
byte[] buf = (byte[]) vec.get(i);
int len = Math.min(buf.length, count - pos);
System.arraycopy(buf, 0, data, pos, len);
pos += len;
}
return data;
}
protected String[] readLines(String filename, String srcEncoding, boolean bulkMode) {
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream(filename);
isr = new InputStreamReader(fis, srcEncoding);
br = new BufferedReader(isr);
} catch (Exception e) {
System.err.println("Error: File access exception: " + e.getMessage() + "!");
System.exit(1);
}
ArrayList list = new ArrayList();
while (true) {
String line = null;
try {
line = readDataLine(br);
} catch (Exception e) {
System.err.println("Read File Error" + e.getMessage() + "!");
System.exit(1);
}
if (line == null) break;
if (line.length() == 0) continue;
list.add(line);
}
int size = list.size();
String[] lines = null;
if (bulkMode) {
lines = new String[1];
StringBuffer buffer = new StringBuffer("");
for (int i = 0; i < size; ++i) {
buffer.append((String) list.get(i));
/*if (i < (size - 1)) {
buffer.append("\r\n");
}*/
}
lines[0] = buffer.toString();
} else {
lines = new String[size];
for (int i = 0; i < size; ++i) {
lines[i] = (String) list.get(i);
}
}
return lines;
}
public String readDataLine(BufferedReader br) throws Exception {
String originalLine = "";
String line = "";
try {
line = originalLine = br.readLine();
if (line == null) return null;
if (line.length() > 0 && line.charAt(0) == 0xFEFF) line = line.substring(1);
int commentPos = line.indexOf('#');
if (commentPos >= 0) line = line.substring(0, commentPos);
line = line.trim();
} catch (Exception e) {
throw new Exception("Line \"{0}\", \"{1}\"" + originalLine + " "
+ line + " " + e.toString());
}
return line;
}
public static class BOMFreeReader extends Reader {
InputStreamReader reader;
String encoding;
int MAX_BOM_LENGTH = 5;
/**
* Creates a new reader, skipping a BOM associated with the given
* encoding. Equivalent to BOMFreeReader(in, null).
*
* @param in
* The input stream.
* @throws IOException
* Thrown if reading for a BOM causes an IOException.
*/
public BOMFreeReader(InputStream in) throws IOException {
this(in, null);
}
/**
* Creates a new reader, skipping a BOM associated with the given
* encoding. If encoding is null, attempts to detect the encoding by the
* BOM.
*
* @param in
* The input stream.
* @param encoding
* The encoding to use. Can be null.
* @throws IOException
* Thrown if reading for a BOM causes an IOException.
*/
public BOMFreeReader(InputStream in, String encoding) throws IOException {
PushbackInputStream pushback = new PushbackInputStream(in, MAX_BOM_LENGTH);
this.encoding = encoding;
byte[] start = new byte[MAX_BOM_LENGTH];
Arrays.fill(start, (byte)0xa5);
int amountRead = pushback.read(start, 0, MAX_BOM_LENGTH);
int bomLength = detectBOMLength(start);
if (amountRead > bomLength)
pushback.unread(start, bomLength, amountRead - bomLength);
reader = (encoding == null) ? new InputStreamReader(pushback) : new InputStreamReader(pushback, encoding);
}
/**
* Determines the length of a BOM in the beginning of start. Assumes
* start is at least a length 5 array. If encoding is null, the check
* will not be encoding specific and it will set the encoding of this
* BOMFreeReader.
*
* @param start
* The starting bytes.
* @param encoding
* The encoding. Can be null.
* @return The length of a detected BOM.
*/
private int detectBOMLength(byte[] start) {
if ((encoding == null || "UTF-16BE".equals(encoding)) && start[0] == (byte) 0xFE && start[1] == (byte) 0xFF) {
if (encoding == null) this.encoding = "UTF-16BE";
return 2; // "UTF-16BE";
} else if (start[0] == (byte) 0xFF && start[1] == (byte) 0xFE) {
if ((encoding == null || "UTF-32LE".equals(encoding)) && start[2] == (byte) 0x00
&& start[3] == (byte) 0x00) {
if (encoding == null) this.encoding = "UTF-32LE";
return 4; // "UTF-32LE";
} else if ((encoding == null || "UTF-16LE".equals(encoding))) {
if (encoding == null) this.encoding = "UTF-16LE";
return 2; // "UTF-16LE";
}
} else if ((encoding == null || "UTF-8".equals(encoding)) && start[0] == (byte) 0xEF
&& start[1] == (byte) 0xBB && start[2] == (byte) 0xBF) {
if (encoding == null) this.encoding = "UTF-8";
return 3; // "UTF-8";
} else if ((encoding == null || "UTF-32BE".equals(encoding)) && start[0] == (byte) 0x00
&& start[1] == (byte) 0x00 && start[2] == (byte) 0xFE && start[3] == (byte) 0xFF) {
if (encoding == null) this.encoding = "UTF-32BE";
return 4; // "UTF-32BE";
} else if ((encoding == null || "SCSU".equals(encoding)) && start[0] == (byte) 0x0E
&& start[1] == (byte) 0xFE && start[2] == (byte) 0xFF) {
if (encoding == null) this.encoding = "SCSU";
return 3; // "SCSU";
} else if ((encoding == null || "BOCU-1".equals(encoding)) && start[0] == (byte) 0xFB
&& start[1] == (byte) 0xEE && start[2] == (byte) 0x28) {
if (encoding == null) this.encoding = "BOCU-1";
return 3; // "BOCU-1";
} else if ((encoding == null || "UTF-7".equals(encoding)) && start[0] == (byte) 0x2B
&& start[1] == (byte) 0x2F && start[2] == (byte) 0x76) {
if (start[3] == (byte) 0x38 && start[4] == (byte) 0x2D) {
if (encoding == null) this.encoding = "UTF-7";
return 5; // "UTF-7";
} else if (start[3] == (byte) 0x38 || start[3] == (byte) 0x39 || start[3] == (byte) 0x2B
|| start[3] == (byte) 0x2F) {
if (encoding == null) this.encoding = "UTF-7";
return 4; // "UTF-7";
}
} else if ((encoding == null || "UTF-EBCDIC".equals(encoding)) && start[0] == (byte) 0xDD
&& start[2] == (byte) 0x73 && start[2] == (byte) 0x66 && start[3] == (byte) 0x73) {
if (encoding == null) this.encoding = "UTF-EBCDIC";
return 4; // "UTF-EBCDIC";
}
/* no known Unicode signature byte sequence recognized */
return 0;
}
public int read(char[] cbuf, int off, int len) throws IOException {
return reader.read(cbuf, off, len);
}
public void close() throws IOException {
reader.close();
}
}
}
// eof