mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
JS: use native string to represent internals of java.lang.String
This commit is contained in:
parent
0e2052d91c
commit
02b3c92912
|
@ -29,6 +29,10 @@ import org.teavm.classlib.impl.currency.CurrencyHelper;
|
|||
import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor;
|
||||
import org.teavm.classlib.impl.record.ObjectMethodsSubstitutor;
|
||||
import org.teavm.classlib.impl.reflection.ReflectionTransformer;
|
||||
import org.teavm.classlib.impl.string.DefaultStringTransformer;
|
||||
import org.teavm.classlib.impl.string.JSStringConstructorGenerator;
|
||||
import org.teavm.classlib.impl.string.JSStringInjector;
|
||||
import org.teavm.classlib.impl.string.JSStringTransformer;
|
||||
import org.teavm.classlib.impl.tz.DateTimeZoneProvider;
|
||||
import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic;
|
||||
import org.teavm.classlib.impl.tz.DateTimeZoneProviderPatch;
|
||||
|
@ -186,6 +190,16 @@ public class JCLPlugin implements TeaVMPlugin {
|
|||
installMetadata(host.getService(MetadataRegistration.class));
|
||||
host.add(new DeclaringClassDependencyListener());
|
||||
applyTimeZoneDetection(host);
|
||||
|
||||
var js = host.getExtension(TeaVMJavaScriptHost.class);
|
||||
if (js != null) {
|
||||
host.add(new JSStringTransformer());
|
||||
js.addInjectorProvider(new JSStringInjector());
|
||||
js.add(new MethodReference(String.class, "<init>", Object.class, void.class),
|
||||
new JSStringConstructorGenerator());
|
||||
} else {
|
||||
host.add(new DefaultStringTransformer());
|
||||
}
|
||||
}
|
||||
|
||||
private void applyTimeZoneDetection(TeaVMHost host) {
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* Copyright 2023 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.string;
|
||||
|
||||
import java.util.List;
|
||||
import org.teavm.model.AccessLevel;
|
||||
import org.teavm.model.ClassHolder;
|
||||
import org.teavm.model.ClassHolderTransformer;
|
||||
import org.teavm.model.ClassHolderTransformerContext;
|
||||
import org.teavm.model.FieldHolder;
|
||||
import org.teavm.model.FieldReference;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.Program;
|
||||
import org.teavm.model.ValueType;
|
||||
import org.teavm.model.instructions.ArrayElementType;
|
||||
import org.teavm.model.instructions.ArrayLengthInstruction;
|
||||
import org.teavm.model.instructions.ConstructArrayInstruction;
|
||||
import org.teavm.model.instructions.GetElementInstruction;
|
||||
import org.teavm.model.instructions.GetFieldInstruction;
|
||||
import org.teavm.model.instructions.IntegerConstantInstruction;
|
||||
import org.teavm.model.instructions.InvocationType;
|
||||
import org.teavm.model.instructions.InvokeInstruction;
|
||||
import org.teavm.model.instructions.PutFieldInstruction;
|
||||
import org.teavm.model.instructions.UnwrapArrayInstruction;
|
||||
|
||||
public class DefaultStringTransformer implements ClassHolderTransformer {
|
||||
@Override
|
||||
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
|
||||
if (cls.getName().equals("java.lang.String")) {
|
||||
transformString(cls);
|
||||
}
|
||||
}
|
||||
|
||||
private void transformString(ClassHolder cls) {
|
||||
var fields = List.copyOf(cls.getFields());
|
||||
for (var field : fields) {
|
||||
cls.removeField(field);
|
||||
}
|
||||
|
||||
var charactersField = new FieldHolder("characters");
|
||||
charactersField.setType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
charactersField.setLevel(AccessLevel.PRIVATE);
|
||||
cls.addField(charactersField);
|
||||
|
||||
for (var field : fields) {
|
||||
cls.addField(field);
|
||||
}
|
||||
|
||||
for (var method : cls.getMethods()) {
|
||||
if (method.getProgram() != null) {
|
||||
transformProgram(method.getProgram());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void transformProgram(Program program) {
|
||||
for (var block : program.getBasicBlocks()) {
|
||||
for (var instruction : block) {
|
||||
if (!(instruction instanceof InvokeInstruction)) {
|
||||
continue;
|
||||
}
|
||||
var invoke = (InvokeInstruction) instruction;
|
||||
if (!invoke.getMethod().getClassName().equals("java.lang.String")) {
|
||||
continue;
|
||||
}
|
||||
switch (invoke.getMethod().getName()) {
|
||||
case "initWithEmptyChars":
|
||||
replaceInitWithEmptyChars(invoke);
|
||||
break;
|
||||
case "borrowChars":
|
||||
replaceBorrowChars(invoke);
|
||||
break;
|
||||
case "initWithCharArray":
|
||||
replaceInitWithCharArray(invoke);
|
||||
break;
|
||||
case "takeCharArray":
|
||||
replaceTakeCharArray(invoke);
|
||||
break;
|
||||
case "charactersLength":
|
||||
replaceCharactersLength(invoke);
|
||||
break;
|
||||
case "charactersGet":
|
||||
replaceCharactersGet(invoke);
|
||||
break;
|
||||
case "copyCharsToArray":
|
||||
replaceCopyCharsToArray(invoke);
|
||||
break;
|
||||
case "fastCharArray":
|
||||
replaceFastCharArray(invoke);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceInitWithEmptyChars(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "EMPTY_CHARS"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setReceiver(program.createVariable());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(getField);
|
||||
|
||||
var putField = new PutFieldInstruction();
|
||||
putField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
putField.setInstance(invoke.getInstance());
|
||||
putField.setValue(getField.getReceiver());
|
||||
putField.setLocation(invoke.getLocation());
|
||||
getField.insertNext(putField);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceBorrowChars(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setInstance(invoke.getArguments().get(0));
|
||||
getField.setReceiver(program.createVariable());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(getField);
|
||||
|
||||
var putField = new PutFieldInstruction();
|
||||
putField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
putField.setInstance(invoke.getInstance());
|
||||
putField.setValue(getField.getReceiver());
|
||||
putField.setLocation(invoke.getLocation());
|
||||
getField.insertNext(putField);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceInitWithCharArray(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var createArray = new ConstructArrayInstruction();
|
||||
createArray.setItemType(ValueType.CHARACTER);
|
||||
createArray.setSize(invoke.getArguments().get(2));
|
||||
createArray.setReceiver(program.createVariable());
|
||||
createArray.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(createArray);
|
||||
|
||||
var zero = new IntegerConstantInstruction();
|
||||
zero.setReceiver(program.createVariable());
|
||||
zero.setLocation(invoke.getLocation());
|
||||
createArray.insertNext(zero);
|
||||
|
||||
var arrayCopy = new InvokeInstruction();
|
||||
arrayCopy.setType(InvocationType.SPECIAL);
|
||||
arrayCopy.setMethod(new MethodReference(System.class, "arraycopy", Object.class, int.class,
|
||||
Object.class, int.class, int.class, void.class));
|
||||
arrayCopy.setArguments(invoke.getArguments().get(0), invoke.getArguments().get(1),
|
||||
createArray.getReceiver(), zero.getReceiver(), invoke.getArguments().get(2));
|
||||
zero.insertNext(arrayCopy);
|
||||
|
||||
var putField = new PutFieldInstruction();
|
||||
putField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
putField.setInstance(program.variableAt(0));
|
||||
putField.setValue(createArray.getReceiver());
|
||||
putField.setLocation(invoke.getLocation());
|
||||
arrayCopy.insertNext(putField);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceTakeCharArray(InvokeInstruction invoke) {
|
||||
var putField = new PutFieldInstruction();
|
||||
putField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
putField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
putField.setInstance(invoke.getInstance());
|
||||
putField.setValue(invoke.getArguments().get(0));
|
||||
putField.setLocation(invoke.getLocation());
|
||||
invoke.replace(putField);
|
||||
}
|
||||
|
||||
private void replaceCharactersLength(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setInstance(invoke.getInstance());
|
||||
getField.setReceiver(program.createVariable());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(getField);
|
||||
|
||||
var unwrapArray = new UnwrapArrayInstruction(ArrayElementType.CHAR);
|
||||
unwrapArray.setArray(getField.getReceiver());
|
||||
unwrapArray.setReceiver(program.createVariable());
|
||||
unwrapArray.setLocation(invoke.getLocation());
|
||||
getField.insertNext(unwrapArray);
|
||||
|
||||
var getLength = new ArrayLengthInstruction();
|
||||
getLength.setArray(unwrapArray.getReceiver());
|
||||
getLength.setReceiver(invoke.getReceiver());
|
||||
getLength.setLocation(invoke.getLocation());
|
||||
unwrapArray.insertNext(getLength);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceCharactersGet(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setInstance(invoke.getInstance());
|
||||
getField.setReceiver(program.createVariable());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(getField);
|
||||
|
||||
var unwrapArray = new UnwrapArrayInstruction(ArrayElementType.CHAR);
|
||||
unwrapArray.setArray(getField.getReceiver());
|
||||
unwrapArray.setReceiver(program.createVariable());
|
||||
unwrapArray.setLocation(invoke.getLocation());
|
||||
getField.insertNext(unwrapArray);
|
||||
|
||||
var getFromArray = new GetElementInstruction(ArrayElementType.CHAR);
|
||||
getFromArray.setArray(unwrapArray.getReceiver());
|
||||
getFromArray.setReceiver(invoke.getReceiver());
|
||||
getFromArray.setIndex(invoke.getArguments().get(0));
|
||||
getFromArray.setLocation(invoke.getLocation());
|
||||
unwrapArray.insertNext(getFromArray);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceCopyCharsToArray(InvokeInstruction invoke) {
|
||||
var program = invoke.getProgram();
|
||||
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setInstance(invoke.getInstance());
|
||||
getField.setReceiver(program.createVariable());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.insertNext(getField);
|
||||
|
||||
var arrayCopy = new InvokeInstruction();
|
||||
arrayCopy.setType(InvocationType.SPECIAL);
|
||||
arrayCopy.setMethod(new MethodReference(System.class, "arraycopy", Object.class, int.class,
|
||||
Object.class, int.class, int.class, void.class));
|
||||
arrayCopy.setArguments(getField.getReceiver(), invoke.getArguments().get(0), invoke.getArguments().get(1),
|
||||
invoke.getArguments().get(2), invoke.getArguments().get(3));
|
||||
getField.insertNext(arrayCopy);
|
||||
|
||||
invoke.delete();
|
||||
}
|
||||
|
||||
private void replaceFastCharArray(InvokeInstruction invoke) {
|
||||
var getField = new GetFieldInstruction();
|
||||
getField.setField(new FieldReference("java.lang.String", "characters"));
|
||||
getField.setFieldType(ValueType.arrayOf(ValueType.CHARACTER));
|
||||
getField.setInstance(invoke.getInstance());
|
||||
getField.setReceiver(invoke.getReceiver());
|
||||
getField.setLocation(invoke.getLocation());
|
||||
invoke.replace(getField);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2023 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.string;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.GeneratorContext;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class JSStringConstructorGenerator implements Generator {
|
||||
@Override
|
||||
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||
writer.append(context.getParameterName(0));
|
||||
writer.append(".").appendField(JSStringInjector.NATIVE_FIELD).ws().append("=").ws();
|
||||
writer.append(context.getParameterName(1)).append(";").softNewLine();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* Copyright 2023 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.string;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
import org.teavm.backend.javascript.ProviderContext;
|
||||
import org.teavm.backend.javascript.spi.Injector;
|
||||
import org.teavm.backend.javascript.spi.InjectorContext;
|
||||
import org.teavm.model.FieldReference;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class JSStringInjector implements Injector, Function<ProviderContext, Injector> {
|
||||
static final FieldReference NATIVE_FIELD = new FieldReference("java.lang.String", "nativeString");
|
||||
|
||||
@Override
|
||||
public Injector apply(ProviderContext providerContext) {
|
||||
switch (providerContext.getMethod().getName()) {
|
||||
case "initWithEmptyChars":
|
||||
case "borrowChars":
|
||||
case "initWithCharArray":
|
||||
case "takeCharArray":
|
||||
case "charactersLength":
|
||||
case "charactersGet":
|
||||
case "copyCharsToArray":
|
||||
case "fastCharArray":
|
||||
case "nativeString":
|
||||
case "substringJS":
|
||||
case "toLowerCaseJS":
|
||||
case "toUpperCaseJS":
|
||||
case "intern":
|
||||
case "stripJS":
|
||||
case "stripLeadingJS":
|
||||
case "stripTrailingJS":
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||
switch (methodRef.getName()) {
|
||||
case "initWithEmptyChars":
|
||||
initWithEmptyChars(context);
|
||||
break;
|
||||
case "borrowChars":
|
||||
borrowChars(context);
|
||||
break;
|
||||
case "initWithCharArray":
|
||||
initWithCharArray(context);
|
||||
break;
|
||||
case "takeCharArray":
|
||||
takeCharArray(context);
|
||||
break;
|
||||
case "charactersLength":
|
||||
charactersLength(context);
|
||||
break;
|
||||
case "charactersGet":
|
||||
charactersGet(context);
|
||||
break;
|
||||
case "copyCharsToArray":
|
||||
copyCharsToArray(context);
|
||||
break;
|
||||
case "fastCharArray":
|
||||
fastCharArray(context);
|
||||
break;
|
||||
case "nativeString":
|
||||
nativeString(context);
|
||||
break;
|
||||
case "substringJS":
|
||||
substringJS(context);
|
||||
break;
|
||||
case "toLowerCaseJS":
|
||||
toLowerCaseJS(context);
|
||||
break;
|
||||
case "toUpperCaseJS":
|
||||
toUpperCaseJS(context);
|
||||
break;
|
||||
case "intern":
|
||||
intern(context);
|
||||
break;
|
||||
case "stripJS":
|
||||
stripJS(context);
|
||||
break;
|
||||
case "stripLeadingJS":
|
||||
stripLeadingJS(context);
|
||||
break;
|
||||
case "stripTrailingJS":
|
||||
stripTrailingJS(context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void initWithEmptyChars(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws().append("\"\"");
|
||||
}
|
||||
|
||||
private void borrowChars(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(".").appendField(NATIVE_FIELD);
|
||||
}
|
||||
|
||||
private void initWithCharArray(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
|
||||
writer.append("$rt_charArrayToString").append("(");
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(".data,").ws();
|
||||
context.writeExpr(context.getArgument(2));
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(3));
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void takeCharArray(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).ws().append("=").ws();
|
||||
writer.append("$rt_fullArrayToString").append("(");
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(".data)");
|
||||
}
|
||||
|
||||
private void charactersLength(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).append(".length");
|
||||
}
|
||||
|
||||
private void charactersGet(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD).append(".charCodeAt(");
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void copyCharsToArray(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
writer.append("$rt_stringToCharArray").append("(");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD);
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(2));
|
||||
writer.append(".data");
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(3));
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(4));
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void substringJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".substring(");
|
||||
context.writeExpr(context.getArgument(1));
|
||||
writer.append(",").ws();
|
||||
context.writeExpr(context.getArgument(2));
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void fastCharArray(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
writer.append("$rt_fastStringToCharArray").append("(");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD);
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void nativeString(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".").appendField(NATIVE_FIELD);
|
||||
}
|
||||
|
||||
private void toLowerCaseJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".toLowerCase()");
|
||||
}
|
||||
|
||||
private void toUpperCaseJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".toUpperCase()");
|
||||
}
|
||||
|
||||
private void intern(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
writer.appendFunction("$rt_intern").append("(");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(")");
|
||||
}
|
||||
|
||||
private void stripJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".trim()");
|
||||
}
|
||||
|
||||
private void stripLeadingJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".trimStart()");
|
||||
}
|
||||
|
||||
private void stripTrailingJS(InjectorContext context) throws IOException {
|
||||
var writer = context.getWriter();
|
||||
context.writeExpr(context.getArgument(0));
|
||||
writer.append(".trimEnd()");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2023 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.string;
|
||||
|
||||
import org.teavm.interop.NoSideEffects;
|
||||
import org.teavm.model.AccessLevel;
|
||||
import org.teavm.model.AnnotationHolder;
|
||||
import org.teavm.model.ClassHolder;
|
||||
import org.teavm.model.ClassHolderTransformer;
|
||||
import org.teavm.model.ClassHolderTransformerContext;
|
||||
import org.teavm.model.ElementModifier;
|
||||
import org.teavm.model.FieldHolder;
|
||||
import org.teavm.model.MethodDescriptor;
|
||||
import org.teavm.model.ValueType;
|
||||
|
||||
public class JSStringTransformer implements ClassHolderTransformer {
|
||||
@Override
|
||||
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
|
||||
if (cls.getName().equals("java.lang.String")) {
|
||||
var charactersField = new FieldHolder("nativeString");
|
||||
charactersField.setType(ValueType.object("java.lang.Object"));
|
||||
charactersField.setLevel(AccessLevel.PRIVATE);
|
||||
cls.addField(charactersField);
|
||||
|
||||
var method = cls.getMethod(new MethodDescriptor("<init>", Object.class, void.class));
|
||||
method.setProgram(null);
|
||||
method.getModifiers().add(ElementModifier.NATIVE);
|
||||
method.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,23 +15,12 @@
|
|||
*/
|
||||
package org.teavm.classlib.java.lang;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.teavm.backend.javascript.codegen.SourceWriter;
|
||||
import org.teavm.backend.javascript.spi.Generator;
|
||||
import org.teavm.backend.javascript.spi.GeneratorContext;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
import org.teavm.dependency.DependencyPlugin;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
import org.teavm.model.MethodReference;
|
||||
|
||||
public class StringNativeGenerator implements Generator, DependencyPlugin {
|
||||
@Override
|
||||
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||
if (methodRef.getName().equals("intern")) {
|
||||
writer.append("return $rt_intern(").append(context.getParameterName(0)).append(");").softNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
public class StringNativeDependency implements DependencyPlugin {
|
||||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||
if (method.getReference().getName().equals("intern")) {
|
|
@ -16,7 +16,7 @@
|
|||
package org.teavm.classlib.java.lang;
|
||||
|
||||
import java.util.Locale;
|
||||
import org.teavm.backend.javascript.spi.GeneratedBy;
|
||||
import org.teavm.classlib.PlatformDetector;
|
||||
import org.teavm.classlib.java.io.TSerializable;
|
||||
import org.teavm.classlib.java.io.TUnsupportedEncodingException;
|
||||
import org.teavm.classlib.java.nio.TByteBuffer;
|
||||
|
@ -35,32 +35,50 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
private static final char[] EMPTY_CHARS = new char[0];
|
||||
private static final TString EMPTY = new TString();
|
||||
public static final TComparator<TString> CASE_INSENSITIVE_ORDER = (o1, o2) -> o1.compareToIgnoreCase(o2);
|
||||
private char[] characters;
|
||||
private transient int hashCode;
|
||||
|
||||
public TString() {
|
||||
this.characters = EMPTY_CHARS;
|
||||
initWithEmptyChars();
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native void initWithEmptyChars();
|
||||
|
||||
public TString(TString other) {
|
||||
characters = other.characters;
|
||||
borrowChars(other);
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native void borrowChars(TString other);
|
||||
|
||||
public TString(char[] characters) {
|
||||
this(characters, 0, characters.length);
|
||||
}
|
||||
|
||||
public TString(char[] value, int offset, int count) {
|
||||
this.characters = new char[count];
|
||||
System.arraycopy(value, offset, this.characters, 0, count);
|
||||
public TString(Object nativeString) {
|
||||
}
|
||||
|
||||
private native Object nativeString();
|
||||
|
||||
public TString(char[] value, int offset, int count) {
|
||||
if (offset < 0 || count < 0 || offset + count > value.length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
initWithCharArray(value, offset, count);
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native void initWithCharArray(char[] value, int offset, int count);
|
||||
|
||||
static TString fromArray(char[] characters) {
|
||||
var s = new TString();
|
||||
s.characters = characters;
|
||||
s.takeCharArray(characters);
|
||||
return s;
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native void takeCharArray(char[] characters);
|
||||
|
||||
public TString(byte[] bytes, int offset, int length, TString charsetName) throws TUnsupportedEncodingException {
|
||||
this(bytes, offset, length, TCharset.forName(charsetName.toString()));
|
||||
}
|
||||
|
@ -86,7 +104,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
|
||||
public TString(int[] codePoints, int offset, int count) {
|
||||
characters = new char[count * 2];
|
||||
var characters = new char[count * 2];
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int codePoint = codePoints[offset++];
|
||||
|
@ -100,16 +118,19 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (charCount < characters.length) {
|
||||
characters = TArrays.copyOf(characters, charCount);
|
||||
}
|
||||
takeCharArray(characters);
|
||||
}
|
||||
|
||||
private void initWithBytes(byte[] bytes, int offset, int length, TCharset charset) {
|
||||
TCharBuffer buffer = charset.decode(TByteBuffer.wrap(bytes, offset, length));
|
||||
char[] characters;
|
||||
if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) {
|
||||
characters = buffer.array();
|
||||
} else {
|
||||
characters = new char[buffer.remaining()];
|
||||
buffer.get(characters);
|
||||
}
|
||||
takeCharArray(characters);
|
||||
}
|
||||
|
||||
public TString(TStringBuilder sb) {
|
||||
|
@ -117,7 +138,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
|
||||
private TString(int length) {
|
||||
this.characters = new char[length];
|
||||
takeCharArray(new char[length]);
|
||||
}
|
||||
|
||||
private static TString allocate(int size) {
|
||||
|
@ -126,10 +147,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
if (index < 0 || index >= characters.length) {
|
||||
if (index < 0 || index >= charactersLength()) {
|
||||
throw new TStringIndexOutOfBoundsException();
|
||||
}
|
||||
return characters[index];
|
||||
return charactersGet(index);
|
||||
}
|
||||
|
||||
public int codePointAt(int index) {
|
||||
|
@ -150,17 +171,23 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
|
||||
@Override
|
||||
public int length() {
|
||||
return characters.length;
|
||||
return charactersLength();
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native int charactersLength();
|
||||
|
||||
@NoSideEffects
|
||||
private native char charactersGet(int index);
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return characters.length == 0;
|
||||
return charactersLength() == 0;
|
||||
}
|
||||
|
||||
public boolean isBlank() {
|
||||
for (int i = 0; i < characters.length; i++) {
|
||||
if (characters[i] != ' ') {
|
||||
for (int i = 0; i < charactersLength(); i++) {
|
||||
if (charactersGet(i) != ' ') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -172,15 +199,18 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
|| dstBegin + (srcEnd - srcBegin) > dst.length) {
|
||||
throw new TIndexOutOfBoundsException();
|
||||
}
|
||||
System.arraycopy(characters, srcBegin, dst, dstBegin, srcEnd - srcBegin);
|
||||
copyCharsToArray(srcBegin, dst, dstBegin, srcEnd - srcBegin);
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native void copyCharsToArray(int begin, char[] dst, int dstBegin, int length);
|
||||
|
||||
public boolean contentEquals(TStringBuffer buffer) {
|
||||
if (characters.length != buffer.length()) {
|
||||
if (charactersLength() != buffer.length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
if (characters[i] != buffer.charAt(i)) {
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
if (charactersGet(i) != buffer.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -191,11 +221,11 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (this == charSeq) {
|
||||
return true;
|
||||
}
|
||||
if (characters.length != charSeq.length()) {
|
||||
if (charactersLength() != charSeq.length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
if (characters[i] != charSeq.charAt(i)) {
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
if (charactersGet(i) != charSeq.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -302,8 +332,8 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
fromIndex = Math.max(0, fromIndex);
|
||||
if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||
char bmpChar = (char) ch;
|
||||
for (int i = fromIndex; i < characters.length; ++i) {
|
||||
if (characters[i] == bmpChar) {
|
||||
for (int i = fromIndex; i < charactersLength(); ++i) {
|
||||
if (charactersGet(i) == bmpChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -311,8 +341,8 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
} else {
|
||||
char hi = TCharacter.highSurrogate(ch);
|
||||
char lo = TCharacter.lowSurrogate(ch);
|
||||
for (int i = fromIndex; i < characters.length - 1; ++i) {
|
||||
if (characters[i] == hi && characters[i + 1] == lo) {
|
||||
for (int i = fromIndex; i < charactersLength() - 1; ++i) {
|
||||
if (charactersGet(i) == hi && charactersGet(i + 1) == lo) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +359,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (ch < TCharacter.MIN_SUPPLEMENTARY_CODE_POINT) {
|
||||
char bmpChar = (char) ch;
|
||||
for (int i = fromIndex; i >= 0; --i) {
|
||||
if (characters[i] == bmpChar) {
|
||||
if (charactersGet(i) == bmpChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +368,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
char hi = TCharacter.highSurrogate(ch);
|
||||
char lo = TCharacter.lowSurrogate(ch);
|
||||
for (int i = fromIndex; i >= 1; --i) {
|
||||
if (characters[i] == lo && characters[i - 1] == hi) {
|
||||
if (charactersGet(i) == lo && charactersGet(i - 1) == hi) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
|
@ -394,12 +424,21 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (beginIndex == endIndex) {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var nativeSubstring = substringJS(nativeString(), beginIndex, endIndex);
|
||||
return nativeSubstring != nativeString() ? new TString(nativeSubstring) : this;
|
||||
}
|
||||
|
||||
if (beginIndex == 0 && endIndex == length()) {
|
||||
return this;
|
||||
}
|
||||
return new TString(characters, beginIndex, endIndex - beginIndex);
|
||||
return new TString(fastCharArray(), beginIndex, endIndex - beginIndex);
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native static Object substringJS(Object nativeString, int start, int end);
|
||||
|
||||
public TString substring(int beginIndex) {
|
||||
return substring(beginIndex, length());
|
||||
}
|
||||
|
@ -485,6 +524,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
|
||||
public TString strip() {
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var result = stripJS(nativeString());
|
||||
return result != nativeString() ? new TString(result) : this;
|
||||
}
|
||||
var lower = 0;
|
||||
var upper = length() - 1;
|
||||
while (lower <= upper && Character.isWhitespace(charAt(lower))) {
|
||||
|
@ -496,7 +539,13 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return substring(lower, upper + 1);
|
||||
}
|
||||
|
||||
private static native Object stripJS(Object nativeString);
|
||||
|
||||
public TString stripLeading() {
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var result = stripLeadingJS(nativeString());
|
||||
return result != nativeString() ? new TString(result) : this;
|
||||
}
|
||||
var lower = 0;
|
||||
while (lower < length() && Character.isWhitespace(charAt(lower))) {
|
||||
++lower;
|
||||
|
@ -504,7 +553,13 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return substring(lower, length());
|
||||
}
|
||||
|
||||
private static native Object stripLeadingJS(Object nativeString);
|
||||
|
||||
public TString stripTrailing() {
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var result = stripTrailingJS(nativeString());
|
||||
return result != nativeString() ? new TString(result) : this;
|
||||
}
|
||||
var upper = length() - 1;
|
||||
while (0 <= upper && Character.isWhitespace(charAt(upper))) {
|
||||
--upper;
|
||||
|
@ -512,15 +567,17 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return substring(0, upper + 1);
|
||||
}
|
||||
|
||||
private static native Object stripTrailingJS(Object nativeString);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (String) (Object) this;
|
||||
}
|
||||
|
||||
public char[] toCharArray() {
|
||||
char[] array = new char[characters.length];
|
||||
char[] array = new char[charactersLength()];
|
||||
for (int i = 0; i < array.length; ++i) {
|
||||
array[i] = characters[i];
|
||||
array[i] = charAt(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
@ -577,16 +634,20 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (!(other instanceof TString)) {
|
||||
return false;
|
||||
}
|
||||
TString str = (TString) other;
|
||||
if (str.length() != length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
if (charAt(i) != str.charAt(i)) {
|
||||
var str = (TString) other;
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
return nativeString() == str.nativeString();
|
||||
} else {
|
||||
if (str.length() != length()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < str.length(); ++i) {
|
||||
if (charAt(i) != str.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equalsIgnoreCase(TString other) {
|
||||
|
@ -616,7 +677,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
|
||||
public byte[] getBytes(TCharset charset) {
|
||||
TByteBuffer buffer = charset.encode(TCharBuffer.wrap(characters));
|
||||
TByteBuffer buffer = charset.encode(TCharBuffer.wrap(fastCharArray()));
|
||||
if (buffer.hasArray() && buffer.position() == 0 && buffer.limit() == buffer.capacity()) {
|
||||
return buffer.array();
|
||||
} else {
|
||||
|
@ -626,24 +687,33 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private native char[] fastCharArray();
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (hashCode == 0) {
|
||||
for (char c : characters) {
|
||||
hashCode = 31 * hashCode + c;
|
||||
for (var i = 0; i < charactersLength(); ++i) {
|
||||
hashCode = 31 * hashCode + charactersGet(i);
|
||||
}
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public TString toLowerCase() {
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var lowerCase = toLowerCaseJS(nativeString());
|
||||
return lowerCase != nativeString() ? new TString(lowerCase) : this;
|
||||
}
|
||||
|
||||
if (isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var hasCharsToTransform = false;
|
||||
var hasSurrogates = false;
|
||||
for (var c : characters) {
|
||||
for (var i = 0; i < charactersLength(); ++i) {
|
||||
var c = charactersGet(i);
|
||||
if (Character.toLowerCase(c) != c) {
|
||||
hasCharsToTransform = true;
|
||||
break;
|
||||
|
@ -658,24 +728,28 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return hasSurrogates ? toLowerCaseCodePoints() : toLowerCaseChars();
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private static native Object toLowerCaseJS(Object nativeString);
|
||||
|
||||
private TString toLowerCaseChars() {
|
||||
var chars = new char[characters.length];
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
chars[i] = TCharacter.toLowerCase(characters[i]);
|
||||
var chars = new char[charactersLength()];
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
chars[i] = TCharacter.toLowerCase(charactersGet(i));
|
||||
}
|
||||
return new TString(chars);
|
||||
}
|
||||
|
||||
private TString toLowerCaseCodePoints() {
|
||||
int[] codePoints = new int[characters.length];
|
||||
int[] codePoints = new int[charactersLength()];
|
||||
int codePointCount = 0;
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
if (i == characters.length - 1 || !TCharacter.isHighSurrogate(characters[i])
|
||||
|| !TCharacter.isLowSurrogate(characters[i + 1])) {
|
||||
codePoints[codePointCount++] = TCharacter.toLowerCase(characters[i]);
|
||||
var length = charactersLength();
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
if (i == length - 1 || !TCharacter.isHighSurrogate(charactersGet(i))
|
||||
|| !TCharacter.isLowSurrogate(charactersGet(i + 1))) {
|
||||
codePoints[codePointCount++] = TCharacter.toLowerCase(charactersGet(i));
|
||||
} else {
|
||||
codePoints[codePointCount++] = TCharacter.toLowerCase(TCharacter.toCodePoint(
|
||||
characters[i], characters[i + 1]));
|
||||
charactersGet(i), charactersGet(i + 1)));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
@ -687,13 +761,19 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
}
|
||||
|
||||
public TString toUpperCase() {
|
||||
if (PlatformDetector.isJavaScript()) {
|
||||
var upperCase = toUpperCaseJS(nativeString());
|
||||
return upperCase != nativeString() ? new TString(upperCase) : this;
|
||||
}
|
||||
|
||||
if (isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var hasCharsToTransform = false;
|
||||
var hasSurrogates = false;
|
||||
for (var c : characters) {
|
||||
for (var i = 0; i < charactersLength(); ++i) {
|
||||
var c = charactersGet(i);
|
||||
if (Character.toUpperCase(c) != c) {
|
||||
hasCharsToTransform = true;
|
||||
break;
|
||||
|
@ -708,24 +788,27 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return hasSurrogates ? toUpperCaseCodePoints() : toUpperCaseChars();
|
||||
}
|
||||
|
||||
@NoSideEffects
|
||||
private static native Object toUpperCaseJS(Object nativeString);
|
||||
|
||||
private TString toUpperCaseChars() {
|
||||
var chars = new char[characters.length];
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
chars[i] = TCharacter.toUpperCase(characters[i]);
|
||||
var chars = new char[charactersLength()];
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
chars[i] = TCharacter.toUpperCase(charactersGet(i));
|
||||
}
|
||||
return new TString(chars);
|
||||
}
|
||||
|
||||
private TString toUpperCaseCodePoints() {
|
||||
int[] codePoints = new int[characters.length];
|
||||
int[] codePoints = new int[charactersLength()];
|
||||
int codePointCount = 0;
|
||||
for (int i = 0; i < characters.length; ++i) {
|
||||
if (i == characters.length - 1 || !TCharacter.isHighSurrogate(characters[i])
|
||||
|| !TCharacter.isLowSurrogate(characters[i + 1])) {
|
||||
codePoints[codePointCount++] = TCharacter.toUpperCase(characters[i]);
|
||||
for (int i = 0; i < charactersLength(); ++i) {
|
||||
if (i == charactersLength() - 1 || !TCharacter.isHighSurrogate(charactersGet(i))
|
||||
|| !TCharacter.isLowSurrogate(charactersGet(i + 1))) {
|
||||
codePoints[codePointCount++] = TCharacter.toUpperCase(charactersGet(i));
|
||||
} else {
|
||||
codePoints[codePointCount++] = TCharacter.toUpperCase(TCharacter.toCodePoint(
|
||||
characters[i], characters[i + 1]));
|
||||
charactersGet(i), charactersGet(i + 1)));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
@ -736,8 +819,7 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
return toUpperCase();
|
||||
}
|
||||
|
||||
@GeneratedBy(StringNativeGenerator.class)
|
||||
@PluggableDependency(StringNativeGenerator.class)
|
||||
@PluggableDependency(StringNativeDependency.class)
|
||||
@NoSideEffects
|
||||
public native TString intern();
|
||||
|
||||
|
@ -820,10 +902,10 @@ public class TString extends TObject implements TSerializable, TComparable<TStri
|
|||
if (count == 1) {
|
||||
return this;
|
||||
}
|
||||
if (characters.length == 0 || count == 0) {
|
||||
if (charactersLength() == 0 || count == 0) {
|
||||
return EMPTY;
|
||||
}
|
||||
var chars = new char[characters.length * count];
|
||||
var chars = new char[charactersLength() * count];
|
||||
var j = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
getChars(0, length(), chars, j);
|
||||
|
|
|
@ -266,9 +266,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
|
|||
dep.getResult().propagate(dependencyAnalyzer.getType("java.lang.Class"));
|
||||
dep.use();
|
||||
|
||||
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", char[].class, void.class));
|
||||
dep = dependencyAnalyzer.linkMethod(new MethodReference(String.class, "<init>", Object.class, void.class));
|
||||
dep.getVariable(0).propagate(stringType);
|
||||
dep.getVariable(1).propagate(dependencyAnalyzer.getType("[C"));
|
||||
dep.use();
|
||||
|
||||
dependencyAnalyzer.linkField(new FieldReference(String.class.getName(), "characters"));
|
||||
|
|
|
@ -263,7 +263,8 @@ public class Renderer implements RenderingManager {
|
|||
"$rt_createShortArray", "$rt_createCharArray", "$rt_createIntArray", "$rt_createLongArray",
|
||||
"$rt_createFloatArray", "$rt_createDoubleArray", "$rt_compare",
|
||||
"$rt_castToClass", "$rt_castToInterface", "$rt_equalDoubles",
|
||||
"Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO",
|
||||
"$rt_str", "Long_toNumber", "Long_fromInt", "Long_fromNumber", "Long_create", "Long_ZERO",
|
||||
"$rt_intern", "$rt_substring",
|
||||
"Long_hi", "Long_lo");
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ public class RuntimeRenderer {
|
|||
renderRuntimeThrowableMethods();
|
||||
renderRuntimeNullCheck();
|
||||
renderRuntimeIntern();
|
||||
renderStringClassInit();
|
||||
renderRuntimeThreads();
|
||||
renderRuntimeCreateException();
|
||||
renderCreateStackTraceElement();
|
||||
|
@ -123,33 +124,21 @@ public class RuntimeRenderer {
|
|||
}
|
||||
|
||||
private void renderRuntimeString() throws IOException {
|
||||
MethodReference stringCons = new MethodReference(String.class, "<init>", char[].class, void.class);
|
||||
writer.append("function $rt_str(str) {").indent().softNewLine();
|
||||
MethodReference stringCons = new MethodReference(String.class, "<init>", Object.class, void.class);
|
||||
writer.append("function $rt_str(str)").ws().append("{").indent().softNewLine();
|
||||
writer.append("if (str === null) {").indent().softNewLine();
|
||||
writer.append("return null;").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
writer.append("var characters = $rt_createCharArray(str.length);").softNewLine();
|
||||
writer.append("var charsBuffer = characters.data;").softNewLine();
|
||||
writer.append("for (var i = 0; i < str.length; i = (i + 1) | 0) {").indent().softNewLine();
|
||||
writer.append("charsBuffer[i] = str.charCodeAt(i) & 0xFFFF;").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
writer.append("return ").appendInit(stringCons).append("(characters);").softNewLine();
|
||||
writer.append("return ").appendInit(stringCons).append("(str);").softNewLine();
|
||||
writer.outdent().append("}").newLine();
|
||||
}
|
||||
|
||||
private void renderRuntimeUnwrapString() throws IOException {
|
||||
FieldReference stringChars = new FieldReference(STRING_CLASS, "characters");
|
||||
writer.append("function $rt_ustr(str) {").indent().softNewLine();
|
||||
writer.append("if (str === null) {").indent().softNewLine();
|
||||
writer.append("return null;").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
|
||||
writer.append("var data = str.").appendField(stringChars).append(".data;").softNewLine();
|
||||
writer.append("var result = \"\";").softNewLine();
|
||||
writer.append("for (var i = 0; i < data.length; i = (i + 1) | 0) {").indent().softNewLine();
|
||||
writer.append("result += String.fromCharCode(data[i]);").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
writer.append("return result;").softNewLine();
|
||||
FieldReference stringChars = new FieldReference(STRING_CLASS, "nativeString");
|
||||
writer.append("function $rt_ustr(str)").ws().append("{").indent().softNewLine();
|
||||
writer.append("return str").ws().append("!==").ws().append("null");
|
||||
writer.ws().append("?").ws().append("str.").appendField(stringChars);
|
||||
writer.ws().append(":").ws().append("null").append(";").softNewLine();
|
||||
writer.outdent().append("}").newLine();
|
||||
}
|
||||
|
||||
|
@ -169,17 +158,15 @@ public class RuntimeRenderer {
|
|||
writer.outdent().append("}").softNewLine();
|
||||
} else {
|
||||
renderHandWrittenRuntime("intern.js");
|
||||
writer.append("function $rt_stringHash(s)").ws().append("{").indent().softNewLine();
|
||||
writer.append("return ").appendMethodBody(String.class, "hashCode", int.class)
|
||||
.append("(s);").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
writer.append("function $rt_stringEquals(a,").ws().append("b)").ws().append("{").indent().softNewLine();
|
||||
writer.append("return ").appendMethodBody(String.class, "equals", Object.class, boolean.class)
|
||||
.append("(a").ws().append(",b);").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderStringClassInit() throws IOException {
|
||||
writer.append("function $rt_stringClassInit(str)").ws().append("{").indent().softNewLine();
|
||||
writer.appendClassInit("java.lang.String").append("();").softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
}
|
||||
|
||||
private boolean needInternMethod() {
|
||||
ClassReader cls = classSource.get(STRING_CLASS);
|
||||
if (cls == null) {
|
||||
|
|
|
@ -16,49 +16,34 @@
|
|||
"use strict";
|
||||
|
||||
var $rt_intern = function() {
|
||||
var table = new Array(100);
|
||||
var size = 0;
|
||||
var map = Object.create(null);
|
||||
|
||||
function get(str) {
|
||||
var hash = $rt_stringHash(str);
|
||||
var bucket = getBucket(hash);
|
||||
for (var i = 0; i < bucket.length; ++i) {
|
||||
if ($rt_stringEquals(bucket[i], str)) {
|
||||
return bucket[i];
|
||||
var get;
|
||||
if (typeof WeakRef !== 'undefined') {
|
||||
var registry = new FinalizationRegistry(value => {
|
||||
delete map[value];
|
||||
});
|
||||
|
||||
get = function(str) {
|
||||
var key = $rt_ustr(str);
|
||||
var ref = map[key];
|
||||
var result = typeof ref !== 'undefined' ? ref.deref() : void 0;
|
||||
if (typeof result !== 'object') {
|
||||
result = str;
|
||||
map[key] = new WeakRef(result);
|
||||
registry.register(result, key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bucket.push(str);
|
||||
return str;
|
||||
}
|
||||
|
||||
function getBucket(hash) {
|
||||
while (true) {
|
||||
var position = hash % table.length;
|
||||
var bucket = table[position];
|
||||
if (typeof bucket !== "undefined") {
|
||||
return bucket;
|
||||
}
|
||||
if (++size / table.length > 0.5) {
|
||||
rehash();
|
||||
} else {
|
||||
bucket = [];
|
||||
table[position] = bucket;
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rehash() {
|
||||
var old = table;
|
||||
table = new Array(table.length * 2);
|
||||
size = 0;
|
||||
for (var i = 0; i < old.length; ++i) {
|
||||
var bucket = old[i];
|
||||
if (typeof bucket !== "undefined") {
|
||||
for (var j = 0; j < bucket.length; ++j) {
|
||||
get(bucket[j]);
|
||||
}
|
||||
} else {
|
||||
get = function(str) {
|
||||
var key = $rt_ustr(str);
|
||||
var result = map[key];
|
||||
if (typeof result !== 'object') {
|
||||
result = str;
|
||||
map[key] = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -644,6 +644,7 @@ function $rt_mainStarter(f) {
|
|||
}
|
||||
var $rt_stringPool_instance;
|
||||
function $rt_stringPool(strings) {
|
||||
$rt_stringClassInit();
|
||||
$rt_stringPool_instance = new Array(strings.length);
|
||||
for (var i = 0; i < strings.length; ++i) {
|
||||
$rt_stringPool_instance[i] = $rt_intern($rt_str(strings[i]));
|
||||
|
@ -937,4 +938,36 @@ function $rt_classWithoutFields(superclass) {
|
|||
return function() {
|
||||
superclass.call(this);
|
||||
};
|
||||
}
|
||||
}
|
||||
function $rt_charArrayToString(array, offset, count) {
|
||||
var result = "";
|
||||
var limit = offset + count;
|
||||
for (var i = offset; i < limit; i = (i + 1024) | 0) {
|
||||
var next = Math.min(limit, (i + 1024) | 0);
|
||||
result += String.fromCharCode.apply(null, array.subarray(i, next));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function $rt_fullArrayToString(array) {
|
||||
return $rt_charArrayToString(array, 0, array.length);
|
||||
}
|
||||
function $rt_stringToCharArray(string, begin, dst, dstBegin, count) {
|
||||
for (var i = 0; i < count; i = (i + 1) | 0) {
|
||||
dst[dstBegin + i] = string.charCodeAt(begin + i);
|
||||
}
|
||||
}
|
||||
function $rt_fastStringToCharArray(string) {
|
||||
var array = new Uint16Array(string.length);
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
array[i] = string.charCodeAt(i);
|
||||
}
|
||||
return $rt_createNumericArray($rt_charcls(), array);
|
||||
}
|
||||
function $rt_substring(string, start, end) {
|
||||
if (start === 0 && end === string.length) {
|
||||
return string;
|
||||
}
|
||||
var result = start.substring(start, end - 1) + start.substring(end - 1, end);
|
||||
$rt_substringSink = ($rt_substringSink + result.charCodeAt(result.length - 1)) | 0;
|
||||
}
|
||||
var $rt_substringSink = 0;
|
|
@ -125,7 +125,7 @@ public class JavaScriptConvGenerator implements Generator {
|
|||
|
||||
writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.String")
|
||||
.append(") {").indent().softNewLine();
|
||||
writer.append("return $rt_str(" + obj + ");").softNewLine();
|
||||
writer.append("return ").appendFunction("$rt_str").append("(").append(obj).append(");").softNewLine();
|
||||
|
||||
writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.Boolean")
|
||||
.append(") {").indent().softNewLine();
|
||||
|
@ -179,7 +179,7 @@ public class JavaScriptConvGenerator implements Generator {
|
|||
writer.append("return arr;").softNewLine();
|
||||
|
||||
writer.outdent().append("} else if (typeof " + obj + " === 'string') {").indent().softNewLine();
|
||||
writer.append("return $rt_str(" + obj + ");").softNewLine();
|
||||
writer.append("return ").appendFunction("$rt_str").append("(" + obj + ");").softNewLine();
|
||||
|
||||
writer.outdent().append("} else if (typeof " + obj + " === 'number') {").indent().softNewLine();
|
||||
writer.append("if ((" + obj + "|0) === " + obj + ") {").indent().softNewLine();
|
||||
|
|
|
@ -166,7 +166,7 @@ public class JSNativeGenerator implements Injector, DependencyPlugin, Generator
|
|||
}
|
||||
break;
|
||||
case "unwrapString":
|
||||
writer.append("$rt_str(");
|
||||
writer.appendFunction("$rt_str").append("(");
|
||||
context.writeExpr(context.getArgument(0), Precedence.min());
|
||||
writer.append(")");
|
||||
break;
|
||||
|
|
|
@ -87,7 +87,7 @@ class ResourceAccessorInjector implements Injector {
|
|||
context.getWriter().append('(');
|
||||
context.writeExpr(context.getArgument(0));
|
||||
context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws();
|
||||
context.getWriter().append("$rt_str(");
|
||||
context.getWriter().appendFunction("$rt_str").append("(");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
context.getWriter().append(")").ws().append(':').ws().append("null)");
|
||||
break;
|
||||
|
|
|
@ -354,5 +354,5 @@ public class StringTest {
|
|||
assertTrue(new String(new char[] { ' ', ' ' }).isBlank());
|
||||
assertFalse(new String(new char[] { ' ', 'x', ' ' }).isBlank());
|
||||
assertFalse(new String(new char[] { 'a', ' ' }).isBlank());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ public class EntryPointGenerator implements Injector {
|
|||
public void generate(InjectorContext context, MethodReference methodRef) throws IOException {
|
||||
context.getWriter().append("main.result = (");
|
||||
context.writeExpr(context.getArgument(0));
|
||||
context.getWriter().append(").").appendField(new FieldReference("java.lang.String", "characters"));
|
||||
context.getWriter().append(".data");
|
||||
context.getWriter().append(").").appendField(new FieldReference("java.lang.String", "nativeString"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ import org.mozilla.javascript.ScriptRuntime;
|
|||
import org.mozilla.javascript.Scriptable;
|
||||
import org.mozilla.javascript.ScriptableObject;
|
||||
import org.mozilla.javascript.Undefined;
|
||||
import org.mozilla.javascript.typedarrays.NativeUint16Array;
|
||||
import org.teavm.ast.AsyncMethodNode;
|
||||
import org.teavm.backend.javascript.JavaScriptTarget;
|
||||
import org.teavm.cache.AlwaysStaleCacheStatus;
|
||||
|
@ -169,12 +168,7 @@ public class IncrementalTest {
|
|||
Function main = (Function) scope.get("main", scope);
|
||||
ScriptRuntime.doTopCall(main, rhinoContext, scope, scope,
|
||||
new Object[] { new NativeArray(0), Undefined.instance });
|
||||
NativeUint16Array jsChars = (NativeUint16Array) main.get("result", main);
|
||||
char[] chars = new char[jsChars.getArrayLength()];
|
||||
for (int i = 0; i < chars.length; ++i) {
|
||||
chars[i] = (char) jsChars.get(i).intValue();
|
||||
}
|
||||
return new String(chars);
|
||||
return main.get("result", main).toString();
|
||||
}
|
||||
|
||||
private static String getSimpleName(String name) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user