Adds Character.digit implementation with honest Unicode support.

This commit is contained in:
konsoletyper 2014-02-10 15:37:42 +04:00
parent 8055edd547
commit 0f2bf64975
16 changed files with 24980 additions and 42 deletions

View File

@ -0,0 +1,59 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.unicode;
/**
*
* @author Alexey Andreev
*/
public class UnicodeHelper {
static char hexDigit(int value) {
return value < 10 ? (char)('0' + value) : (char)('A' + value);
}
static int valueOfHexDigit(char digit) {
return digit <= '9' ? digit - '0' : digit - 'A' + 10;
}
public static String encodeIntByte(int[] data) {
char[] chars = new char[data.length / 2 * 5];
int j = 0;
for (int i = 0; i < data.length;) {
int val = data[i++];
int shift = 32;
for (int k = 0; k < 4; ++k) {
shift -= 8;
chars[j++] = (char)('z' + ((val >> shift) & 0xFF));
}
chars[j++] = (char)('z' + (data[i++] & 0xFF));
}
return new String(chars);
}
public static int[] decodeIntByte(String text) {
int[] data = new int[2 * (text.length() / 5)];
int j = 0;
for (int i = 0; i < data.length;) {
int val = 0;
for (int k = 0; k < 4; ++k) {
val = (val << 8) | (text.charAt(j++) - 'z');
}
data[i++] = val;
data[i++] = text.charAt(j++) - 'z';
}
return data;
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.unicode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.teavm.common.IntegerArray;
/**
*
* @author Alexey Andreev
*/
public class UnicodeSupport {
private static AtomicBoolean filled = new AtomicBoolean();
private static volatile CountDownLatch latch = new CountDownLatch(0);
private static int[] digitValues;
private static void parseUnicodeData() {
IntegerArray digitValues = new IntegerArray(4096);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(UnicodeHelper.class
.getResourceAsStream("UnicodeData.txt")))) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
if (line.isEmpty()) {
continue;
}
String[] fields = splitLine(line);
int charCode = parseHex(fields[0]);
if (!fields[6].isEmpty()) {
int digit = Integer.parseInt(fields[6]);
digitValues.add(charCode);
digitValues.add(digit);
}
}
} catch (IOException e) {
throw new RuntimeException("Error reading unicode data", e);
}
IntegerArray letterDigitValues = new IntegerArray(256);
for (int i = 'A'; i <= 'Z'; ++i) {
letterDigitValues.add(i);
letterDigitValues.add(i - 'A' + 10);
}
for (int i = 'a'; i <= 'z'; ++i) {
letterDigitValues.add(i);
letterDigitValues.add(i - 'a' + 10);
}
for (int i = '\uFF21'; i <= '\uFF3A'; ++i) {
letterDigitValues.add(i);
letterDigitValues.add(i - '\uFF21' + 10);
}
for (int i = '\uFF41'; i <= '\uFF5A'; ++i) {
letterDigitValues.add(i);
letterDigitValues.add(i - '\uFF41' + 10);
}
UnicodeSupport.digitValues = mergePairs(digitValues.getAll(), letterDigitValues.getAll());
}
private static String[] splitLine(String line) {
List<String> parts = new ArrayList<>();
int index = 0;
while (true) {
int next = line.indexOf(';', index);
if (next == -1) {
break;
}
parts.add(line.substring(index, next));
index = next + 1;
}
parts.add(line.substring(index));
return parts.toArray(new String[parts.size()]);
}
private static int[] mergePairs(int[] a, int[] b) {
int[] result = new int[a.length + b.length];
int i = 0;
int j = 0;
int t = 0;
while (true) {
if (i == a.length) {
while (i < a.length) {
result[t++] = a[i++];
}
break;
} else if (j == b.length) {
while (j < b.length) {
result[t++] = b[j++];
}
break;
}
if (a[i] < b[j]) {
result[t++] = a[i++];
result[t++] = a[i++];
} else {
result[t++] = b[j++];
result[t++] = b[j++];
}
}
return result;
}
private static int parseHex(String text) {
int value = 0;
for (int i = 0; i < text.length(); ++i) {
value = value << 4 | UnicodeHelper.valueOfHexDigit(text.charAt(i));
}
return value;
}
private static void ensureUnicodeData() {
if (filled.compareAndSet(false, true)) {
parseUnicodeData();
latch = null;
} else {
CountDownLatch latchCopy = latch;
if (latchCopy != null) {
try {
latchCopy.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
public static int[] getDigitValues() {
ensureUnicodeData();
return digitValues;
}
}

View File

@ -16,7 +16,11 @@
package org.teavm.classlib.java.lang;
import java.io.IOException;
import org.teavm.classlib.impl.unicode.UnicodeHelper;
import org.teavm.classlib.impl.unicode.UnicodeSupport;
import org.teavm.codegen.SourceWriter;
import org.teavm.dependency.DependencyChecker;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.MethodReference;
@ -26,7 +30,7 @@ import org.teavm.model.ValueType;
*
* @author Alexey Andreev
*/
public class CharacterNativeGenerator implements Generator {
public class CharacterNativeGenerator implements Generator, DependencyPlugin {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
@ -37,6 +41,18 @@ public class CharacterNativeGenerator implements Generator {
generateToLowerCaseInt(context, writer);
}
break;
case "obtainDigitMapping":
generateObtainDigitMapping(writer);
break;
}
}
@Override
public void methodAchieved(DependencyChecker checker, MethodReference method) {
switch (method.getName()) {
case "obtainDigitMapping":
achieveObtainDigitMapping(checker, method);
break;
}
}
@ -49,4 +65,13 @@ public class CharacterNativeGenerator implements Generator {
writer.append("return String.fromCharCode(").append(context.getParameterName(1))
.append(").toLowerCase().charCodeAt(0);").softNewLine();
}
private void generateObtainDigitMapping(SourceWriter writer) throws IOException {
writer.append("return $rt_str(\"").append(UnicodeHelper.encodeIntByte(UnicodeSupport.getDigitValues()))
.append("\");").softNewLine();
}
private void achieveObtainDigitMapping(DependencyChecker checker, MethodReference method) {
checker.attachMethodGraph(method).getResultNode().propagate("java.lang.String");
}
}

View File

@ -19,42 +19,72 @@ import java.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class ClassNativeGenerator implements Generator {
public class ClassNativeGenerator implements Generator, Injector {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef)
throws IOException {
switch (methodRef.getName()) {
case "isInstance":
generateIsInstance(context, writer);
break;
case "isAssignable":
generateIsAssignableFrom(context, writer);
break;
case "getComponentType0":
generateGetComponentType(context, writer);
break;
}
}
private void generateIsInstance(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("return $rt_isInstance(").append(context.getParameterName(1)).append(", ")
.append(context.getParameterName(0)).append(".$data);").softNewLine();
}
private void generateIsAssignableFrom(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("return $rt_isAssignable(").append(context.getParameterName(1)).append(".$data, ")
.append(context.getParameterName(0)).append(".$data;").softNewLine();
}
private void generateGetComponentType(GeneratorContext context, SourceWriter writer) throws IOException {
String thisArg = context.getParameterName(0);
writer.append("var item = " + thisArg + ".$data.$meta.item;").softNewLine();
writer.append("return item != null ? $rt_cls(item) : null;").softNewLine();
}
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "isInstance":
generateIsInstance(context);
break;
case "isAssignableFrom":
generateIsAssignableFrom(context);
break;
case "booleanClass":
generateBooleanClass(context);
break;
case "intClass":
generateIntClass(context);
break;
}
}
private void generateIsAssignableFrom(InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();
writer.append("$rt_isAssignable(");
context.writeExpr(context.getArgument(1));
writer.append(".$data,").ws();
context.writeExpr(context.getArgument(0));
writer.append(".$data)");
}
private void generateIsInstance(InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();
writer.append("$rt_isInstance(");
context.writeExpr(context.getArgument(1));
writer.append(",").ws();
context.writeExpr(context.getArgument(0));
writer.append(".$data)");
}
private void generateBooleanClass(InjectorContext context) throws IOException {
context.getWriter().append("$rt_cls($rt_booleancls())");
}
private void generateIntClass(InjectorContext context) throws IOException {
context.getWriter().append("$rt_cls($rt_intcls())");
}
}

View File

@ -22,6 +22,8 @@ import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodGraph;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -30,24 +32,30 @@ import org.teavm.model.ValueType;
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public class ObjectNativeGenerator implements Generator, DependencyPlugin {
public class ObjectNativeGenerator implements Generator, Injector, DependencyPlugin {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
switch (methodRef.getDescriptor().getName()) {
case "<init>":
generateInit(context, writer);
break;
case "getClass":
generateGetClass(context, writer);
break;
case "hashCode":
generateHashCode(context, writer);
break;
case "clone":
generateClone(context, writer);
break;
}
}
@Override
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "getClass":
generateGetClass(context);
break;
case "wrap":
generateWrap(context, writer);
generateWrap(context);
break;
}
}
@ -71,9 +79,11 @@ public class ObjectNativeGenerator implements Generator, DependencyPlugin {
writer.append(context.getParameterName(0)).append(".$id = $rt_nextId();").softNewLine();
}
private void generateGetClass(GeneratorContext context, SourceWriter writer) throws IOException {
String thisArg = context.getParameterName(0);
writer.append("return $rt_cls(").append(thisArg).append(".constructor);").softNewLine();
private void generateGetClass(InjectorContext context) throws IOException {
SourceWriter writer = context.getWriter();
writer.append("$rt_cls(");
context.writeExpr(context.getArgument(0));
writer.append(".constructor)");
}
private void achieveGetClass(DependencyChecker checker, MethodReference method) {
@ -102,8 +112,8 @@ public class ObjectNativeGenerator implements Generator, DependencyPlugin {
graph.getVariableNode(0).connect(graph.getResultNode());
}
private void generateWrap(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("return ").append(context.getParameterName(1)).append(";").softNewLine();
private void generateWrap(InjectorContext context) throws IOException {
context.writeExpr(context.getArgument(0));
}
private void achieveWrap(DependencyChecker checker, MethodReference method) {

View File

@ -16,26 +16,26 @@
package org.teavm.classlib.java.lang;
import java.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.javascript.ni.Injector;
import org.teavm.javascript.ni.InjectorContext;
import org.teavm.model.MethodReference;
/**
*
* @author Alexey Andreev
*/
public class StringNativeGenerator implements Generator {
public class StringNativeGenerator implements Injector {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
switch (methodRef.getName()) {
case "wrap":
generateWrap(context, writer);
generateWrap(context);
break;
}
}
private void generateWrap(GeneratorContext context, SourceWriter writer) throws IOException {
writer.append("return ").append(context.getParameterName(1)).softNewLine();
private void generateWrap(InjectorContext context) throws IOException {
context.writeExpr(context.getArgument(0));
}
}

View File

@ -26,7 +26,7 @@ import org.teavm.javascript.ni.Rename;
public class TBoolean extends TObject implements TSerializable, TComparable<TBoolean> {
public static final TBoolean TRUE = new TBoolean(true);
public static final TBoolean FALSE = new TBoolean(false);
public static final Class<Boolean> TYPE = boolean.class;
public static final TClass<TBoolean> TYPE = TClass.booleanClass();
private boolean value;
public TBoolean(boolean value) {

View File

@ -15,6 +15,8 @@
*/
package org.teavm.classlib.java.lang;
import org.teavm.classlib.impl.unicode.UnicodeHelper;
import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.ni.GeneratedBy;
/**
@ -22,9 +24,54 @@ import org.teavm.javascript.ni.GeneratedBy;
* @author Alexey Andreev
*/
public class TCharacter {
public static final int MIN_RADIX = 2;
public static final int MAX_RADIX = 36;
private static int[] digitMapping;
@GeneratedBy(CharacterNativeGenerator.class)
public static native char toLowerCase(char ch);
@GeneratedBy(CharacterNativeGenerator.class)
public static native int toLowerCase(int ch);
public static int digit(char ch, int radix) {
return digit((int)ch, radix);
}
public static int digit(int codePoint, int radix) {
if (radix < MIN_RADIX || radix > MAX_RADIX) {
return -1;
}
int d = digit(codePoint);
return d <= radix ? d : -1;
}
static int digit(int codePoint) {
int[] digitMapping = getDigitMapping();
int l = 0;
int u = (digitMapping.length / 2) - 1;
while (u >= l) {
int idx = (l + u) / 2;
int val = digitMapping[idx * 2];
if (codePoint > val) {
l = idx + 1;
} else if (codePoint < val) {
u = idx - 1;
} else {
return digitMapping[idx * 2 + 1];
}
}
return -1;
}
private static int[] getDigitMapping() {
if (digitMapping == null) {
digitMapping = UnicodeHelper.decodeIntByte(obtainDigitMapping());
}
return digitMapping;
}
@GeneratedBy(CharacterNativeGenerator.class)
@PluggableDependency(CharacterNativeGenerator.class)
private static native String obtainDigitMapping();
}

View File

@ -16,6 +16,7 @@
package org.teavm.classlib.java.lang;
import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.InjectedBy;
/**
*
@ -32,9 +33,12 @@ public class TClass<T> extends TObject {
return new TClass<>();
}
@GeneratedBy(ClassNativeGenerator.class)
@InjectedBy(ClassNativeGenerator.class)
public native boolean isInstance(TObject obj);
@InjectedBy(ClassNativeGenerator.class)
public native boolean isAssignableFrom(TClass<?> obj);
public String getName() {
return name;
}
@ -57,4 +61,10 @@ public class TClass<T> extends TObject {
@GeneratedBy(ClassNativeGenerator.class)
private native TClass<?> getComponentType0();
@InjectedBy(ClassNativeGenerator.class)
static native TClass<TBoolean> booleanClass();
@InjectedBy(ClassNativeGenerator.class)
static native TClass<TInteger> integerClass();
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.lang;
/**
*
* @author Alexey Andreev
*/
public class TInteger extends TNumber implements TComparable<TInteger> {
public static final int SIZE = 32;
public static final int MIN_VALUE = (2 << (SIZE - 1));
public static final int MAX_VALUE = (2 << (SIZE - 1)) - 1;
public static final TClass<TInteger> TYPE = TClass.integerClass();
private int value;
public TInteger(int value) {
this.value = value;
}
@Override
public int compareTo(TInteger other) {
return compare(value, other.value);
}
@Override
public int intValue() {
return value;
}
@Override
public long longValue() {
return value;
}
@Override
public float floatValue() {
return value;
}
@Override
public double doubleValue() {
return value;
}
public static int compare(int x, int y) {
return x > y ? 1 : x < y ? -1 : 0;
}
/*public static int parseInt(TString s, int radix) throws TNumberFormatException {
return 0;
}*/
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.lang;
import org.teavm.classlib.java.io.TSerializable;
/**
*
* @author Alexey Andreev
*/
public abstract class TNumber extends TObject implements TSerializable {
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue() {
return (byte)intValue();
}
public short shortValue() {
return (short)intValue();
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.lang;
/**
*
* @author Alexey Andreev
*/
public class TNumberFormatException extends TIllegalArgumentException {
private static final long serialVersionUID = 6872736757931546482L;
public TNumberFormatException() {
super();
}
public TNumberFormatException(TString message) {
super(message);
}
}

View File

@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang;
import org.teavm.dependency.PluggableDependency;
import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.InjectedBy;
import org.teavm.javascript.ni.Rename;
import org.teavm.javascript.ni.Superclass;
@ -34,7 +35,7 @@ public class TObject {
@Rename("<init>")
private native void init();
@GeneratedBy(ObjectNativeGenerator.class)
@InjectedBy(ObjectNativeGenerator.class)
@Rename("getClass")
@PluggableDependency(ObjectNativeGenerator.class)
public native final TClass<?> getClass0();
@ -82,7 +83,7 @@ public class TObject {
protected void finalize() throws TThrowable {
}
@GeneratedBy(ObjectNativeGenerator.class)
@InjectedBy(ObjectNativeGenerator.class)
@PluggableDependency(ObjectNativeGenerator.class)
public static native TObject wrap(Object obj);
}

View File

@ -19,7 +19,7 @@ import org.teavm.classlib.impl.charset.*;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.io.TUnsupportedEncodingException;
import org.teavm.classlib.java.util.TArrays;
import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.javascript.ni.InjectedBy;
import org.teavm.javascript.ni.Rename;
/**
@ -548,7 +548,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
return hashCode;
}
@GeneratedBy(StringNativeGenerator.class)
@InjectedBy(StringNativeGenerator.class)
public static native TString wrap(String str);
public TString toLowerCase() {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.java.lang;
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author Alexey Andreev
*/
public class CharacterTest {
@Test
public void digitsRecognized() {
assertEquals(2, Character.digit('2', 10));
assertEquals(-1, Character.digit('.', 10));
assertEquals(6, Character.digit('\u096C', 10));
assertEquals(15, Character.digit('F', 16));
}
}