this is in addition to 200_success's answer,
Better Test Cases
byte[] bytes1 = {(byte) 0b11001111, (byte) 0b10111111};
System.out.println(validate(bytes1)); // true
byte[] bytes2 = {(byte) 0b11101111, (byte) 0b10101010, (byte) 0b10111111};
System.out.println(validate(bytes2)); // true
You are using simple print statements for testing, instead you can use mature Unit Testing Framework such as JUnit. JUnit can simplify the the process of unit testing greatly. And also instead of simply commenting the expected results you can test against them.
Writing unit test code is labor-intensive, hence it is often not done
as an integral part of programming. However, unit testing is a
practical approach to increasing the correctness and quality of
software; for example, the Extreme Programming approach relies on
frequent unit testing [1].
Additionally I sense that you are repeating yourself in the test cases. Therefore making it harder for you to add new unit tests later [2].
In software engineering, don't repeat yourself (DRY) is a principle of
software development, aimed at reducing repetition of information of
all kinds [3].
You can also consider adding string based unit tests as well.
private static final String[] stringTests = {
"A",
"Z",
"Because I'm Batman"
};
You can use an array to store all your strings and then loop through it.
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
using a constant UTF8_CHARSET
(thanks Vogel612).
private static final byte[][] byteTests = {
{(byte) 0b11001111, (byte) 0b10111111},
{(byte) 0b11101111, (byte) 0b10101010, (byte) 0b10111111},
{(byte) 0b10001111, (byte) 0b10111111},
{(byte) 0b11101111, (byte) 0b10101010, (byte) 0b00111111}
};
private static final boolean[] byteTestsExpectedResults = {
true,
true,
false,
false
};
You can store the expected results and byte arrays in arrays, which you can easily loop and access the values.
Eventually I created this unit test
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import org.junit.Assert;
import org.junit.Test;
/**
* Test ValidateUtf8
*/
public class ValidateUtf8Test {
//for byte based tests
private static final byte[][] byteTests = {
{(byte) 0b11001111, (byte) 0b10111111},
{(byte) 0b11101111, (byte) 0b10101010, (byte) 0b10111111},
{(byte) 0b10001111, (byte) 0b10111111},
{(byte) 0b11101111, (byte) 0b10101010, (byte) 0b00111111}
};
private static final boolean[] byteTestsExpectedResults = {
true,
true,
false,
false
};
//for string based tests
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final String[] stringTests = {
"A",
"Z",
"Because I'm Batman"
};
@Test
public void testValidate() {
for (int i = 0; i < byteTests.length; i++) {
Assert.assertEquals(String.format("validate(byteTests[%d])", i),
ValidateUtf8.validate(byteTests[i]),
byteTestsExpectedResults[i]);
}
}
@Test
public void testValidateFromString() throws UnsupportedEncodingException {
for (String toTest : stringTests) {
Assert.assertTrue(String.format("validate('%s')", toTest),
ValidateUtf8.validate(
toTest.getBytes(UTF8_CHARSET)));
}
}
@Test
public void testCharLengthFromString() throws UnsupportedEncodingException {
for (String toTest : stringTests) {
Assert.assertEquals(String.format("charLength('%s')", toTest),
ValidateUtf8.charLength(toTest.getBytes(UTF8_CHARSET)),
toTest.length());
}
}
}
I decided to write a validate()
method based on the charLength()
method by 200_success.
public static boolean validate(byte[] bytes) {
return (charLength(bytes) != -1);
}
and finally ValidateUtf8 class
public class ValidateUtf8 {
/**
* Returns the number of UTF-8 characters, or -1 if the array does not
* contain a valid UTF-8 string. Overlong encodings, null characters,
* invalid Unicode values, and surrogates are accepted.
*
* @param bytes byte array to check length
* @return length
*/
public static int charLength(byte[] bytes) {
int charCount = 0, expectedLen;
for (int i = 0; i < bytes.length; i++) {
charCount++;
// Lead byte analysis
if ((bytes[i] & 0b10000000) == 0b00000000) {
continue;
} else if ((bytes[i] & 0b11100000) == 0b11000000) {
expectedLen = 2;
} else if ((bytes[i] & 0b11110000) == 0b11100000) {
expectedLen = 3;
} else if ((bytes[i] & 0b11111000) == 0b11110000) {
expectedLen = 4;
} else if ((bytes[i] & 0b11111100) == 0b11111000) {
expectedLen = 5;
} else if ((bytes[i] & 0b11111110) == 0b11111100) {
expectedLen = 6;
} else {
return -1;
}
// Count trailing bytes
while (--expectedLen > 0) {
if (++i >= bytes.length) {
return -1;
}
if ((bytes[i] & 0b11000000) != 0b10000000) {
return -1;
}
}
}
return charCount;
}
/**
* Validate a UTF-8 byte array
*
* @param bytes byte array to validate
* @return true if UTF-8
*/
public static boolean validate(byte[] bytes) {
return (charLength(bytes) != -1);
}
}
After running test cases

All the test cases will pass
[1]Y. Cheon and G. Leavens, “A Simple and Practical Approach to Unit Testing: The JML and JUnit Way,” in ECOOP 2002 — Object-Oriented Programming, vol. 2374, B. Magnusson, Ed. Springer Berlin Heidelberg, 2002, pp. 231–255.
[2]“Match Simple Sentence or Partial Sentence.” [Online]. Available: Match Simple Sentence or Partial Sentence. [Accessed: 27-Aug-2014].
[3]“Don’t repeat yourself,” Wikipedia, the free encyclopedia. 24-Aug-2014.
CharsetDecoder
, so thanks for the heads-up :) \$\endgroup\$CoderResult result = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT).decode(ByteBuffer.wrap(bytesToTest), CharBuffer.allocate(1024), true);
\$\endgroup\$