diff --git a/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassDependencyListener.java b/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassDependencyListener.java index d75a24c9b..c66f682c3 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassDependencyListener.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassDependencyListener.java @@ -23,14 +23,26 @@ import org.teavm.model.ClassReader; public class DeclaringClassDependencyListener extends AbstractDependencyListener { @Override public void methodReached(DependencyAgent agent, MethodDependency method) { - if (method.getReference().getClassName().equals("java.lang.Class") - && method.getReference().getName().equals("getDeclaringClass")) { - method.getVariable(0).getClassValueNode().addConsumer(t -> { - ClassReader cls = agent.getClassSource().get(t.getName()); - if (cls != null && cls.getOwnerName() != null) { - agent.linkClass(cls.getOwnerName()); + if (method.getReference().getClassName().equals("java.lang.Class")) { + switch (method.getReference().getName()) { + case "getEnclosingClass": + method.getVariable(0).getClassValueNode().addConsumer(t -> { + ClassReader cls = agent.getClassSource().get(t.getName()); + if (cls != null && cls.getOwnerName() != null) { + agent.linkClass(cls.getOwnerName()); + } + }); + break; + case "getDeclaringClass": { + method.getVariable(0).getClassValueNode().addConsumer(t -> { + ClassReader cls = agent.getClassSource().get(t.getName()); + if (cls != null && cls.getDeclaringClassName() != null) { + agent.linkClass(cls.getDeclaringClassName()); + } + }); + break; } - }); + } } } } diff --git a/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassGenerator.java b/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassGenerator.java deleted file mode 100644 index 9be6ed519..000000000 --- a/classlib/src/main/java/org/teavm/classlib/impl/DeclaringClassGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2019 konsoletyper. - * - * 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; - -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.MethodDependencyInfo; -import org.teavm.model.ClassReader; -import org.teavm.model.MethodReference; - -public class DeclaringClassGenerator implements Generator { - private static final MethodReference METHOD = new MethodReference(Class.class, "getDeclaringClass", - Class.class); - - @Override - public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { - writer.append("var p").ws().append("=").ws().append("\"teavm_declaredClass\";").softNewLine(); - writer.append("if").ws().append("(!").appendMethodBody(methodRef).append(".initialized").append(")") - .ws().append("{").indent().softNewLine(); - - MethodDependencyInfo methodDep = context.getDependency().getMethod(METHOD); - if (methodDep != null) { - for (String type : methodDep.getVariable(0).getClassValueNode().getTypes()) { - ClassReader cls = context.getClassSource().get(type); - if (cls != null) { - writer.appendClass(type).append("[p]").ws().append("=").ws(); - if (cls.getOwnerName() != null) { - writer.appendClass(cls.getOwnerName()); - } else { - writer.append("null"); - } - writer.append(";").softNewLine(); - } - } - } - - writer.appendMethodBody(methodRef).append(".initialized").ws().append("=").ws().append("true;").softNewLine(); - writer.outdent().append("}").softNewLine(); - writer.append("return ").append(context.getParameterName(1)).append("[p];").softNewLine(); - } -} diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 339266486..3a751b6f6 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -144,12 +144,6 @@ public class JCLPlugin implements TeaVMPlugin { installMetadata(host.getService(MetadataRegistration.class)); host.add(new DeclaringClassDependencyListener()); - - TeaVMJavaScriptHost jsExtension = host.getExtension(TeaVMJavaScriptHost.class); - if (jsExtension != null) { - jsExtension.add(new MethodReference(Class.class, "getDeclaringClassImpl", PlatformClass.class, - PlatformClass.class), new DeclaringClassGenerator()); - } } private void installMetadata(MetadataRegistration reg) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 2ca247b4a..516e65f73 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -53,6 +53,7 @@ import org.teavm.runtime.RuntimeObject; public class TClass extends TObject implements TAnnotatedElement { String name; String simpleName; + String canonicalName; private PlatformClass platformClass; private TAnnotation[] annotationsCache; private Map, TAnnotation> annotationsByType; @@ -115,51 +116,110 @@ public class TClass extends TObject implements TAnnotatedElement { } public String getSimpleName() { - String simpleName = getSimpleNameCache(); + String simpleName = getSimpleNameCache(this); if (simpleName == null) { if (isArray()) { simpleName = getComponentType().getSimpleName() + "[]"; - setSimpleNameCache(simpleName); - return simpleName; - } - String name = Platform.getName(platformClass); - int lastDollar = name.lastIndexOf('$'); - if (lastDollar != -1) { - name = name.substring(lastDollar + 1); - if (name.charAt(0) >= '0' && name.charAt(0) <= '9') { - name = ""; + } else if (getEnclosingClass() != null) { + simpleName = Platform.getSimpleName(platformClass); + if (simpleName == null) { + simpleName = ""; } } else { - int lastDot = name.lastIndexOf('.'); - if (lastDot != -1) { - name = name.substring(lastDot + 1); + String name = Platform.getName(platformClass); + int lastDollar = name.lastIndexOf('$'); + if (lastDollar != -1) { + name = name.substring(lastDollar + 1); + if (name.charAt(0) >= '0' && name.charAt(0) <= '9') { + name = ""; + } + } else { + int lastDot = name.lastIndexOf('.'); + if (lastDot != -1) { + name = name.substring(lastDot + 1); + } } + simpleName = name; } - simpleName = name; - setSimpleNameCache(simpleName); + setSimpleNameCache(this, simpleName); } return simpleName; } @DelegateTo("getSimpleNameCacheLowLevel") - private String getSimpleNameCache() { - return simpleName; + private static String getSimpleNameCache(TClass self) { + return self.simpleName; } @Unmanaged @PluggableDependency(ClassDependencyListener.class) - private RuntimeObject getSimpleNameCacheLowLevel() { - return Address.ofObject(this).toStructure().simpleName; + private static RuntimeObject getSimpleNameCacheLowLevel(RuntimeClass self) { + return self.simpleNameCache; } @DelegateTo("setSimpleNameCacheLowLevel") - private void setSimpleNameCache(String value) { - simpleName = value; + private static void setSimpleNameCache(TClass self, String value) { + self.simpleName = value; } @Unmanaged - private void setSimpleNameCacheLowLevel(RuntimeObject object) { - Address.ofObject(this).toStructure().simpleName = object; + private static void setSimpleNameCacheLowLevel(RuntimeClass self, RuntimeObject object) { + self.simpleNameCache = object; + } + + public String getCanonicalName() { + String result = getCanonicalNameCache(); + if (result == null) { + if (isArray()) { + String componentName = getComponentType().getCanonicalName(); + if (componentName == null) { + return null; + } + result = componentName + "[]"; + } else if (getEnclosingClass() != null) { + if (getDeclaringClass() == null || isSynthetic()) { + return null; + } + String enclosingName = getDeclaringClass().getCanonicalName(); + if (enclosingName == null) { + return null; + } + result = enclosingName + "." + getSimpleName(); + } else { + result = getName(); + } + setCanonicalNameCache(result); + } + return result; + } + + private boolean isSynthetic() { + if (PlatformDetector.isJavaScript()) { + return (platformClass.getMetadata().getAccessLevel() & Flags.SYNTHETIC) != 0; + } else { + return (RuntimeClass.getClass(Address.ofObject(this).toStructure()).flags & RuntimeClass.SYNTHETIC) != 0; + } + } + + @DelegateTo("getCanonicalNameCacheLowLevel") + private String getCanonicalNameCache() { + return canonicalName; + } + + @Unmanaged + @PluggableDependency(ClassDependencyListener.class) + private RuntimeObject getCanonicalNameCacheLowLevel() { + return Address.ofObject(this).toStructure().canonicalName; + } + + @DelegateTo("setCanonicalNameCacheLowLevel") + private void setCanonicalNameCache(String value) { + canonicalName = value; + } + + @Unmanaged + private void setCanonicalNameCacheLowLevel(RuntimeObject object) { + Address.ofObject(this).toStructure().canonicalName = object; } public boolean isPrimitive() { @@ -178,6 +238,15 @@ public class TClass extends TObject implements TAnnotatedElement { return (platformClass.getMetadata().getFlags() & Flags.INTERFACE) != 0; } + public boolean isLocalClass() { + return (platformClass.getMetadata().getFlags() & Flags.SYNTHETIC) != 0 + && getEnclosingClass() != null; + } + + public boolean isMemberClass() { + return getDeclaringClass() != null; + } + @PluggableDependency(ClassGenerator.class) public TClass getComponentType() { return getClass(Platform.getArrayItem(platformClass)); @@ -597,11 +666,14 @@ public class TClass extends TObject implements TAnnotatedElement { } public TClass getDeclaringClass() { - PlatformClass result = getDeclaringClassImpl(getPlatformClass()); + PlatformClass result = Platform.getDeclaringClass(getPlatformClass()); + return result != null ? getClass(result) : null; + } + + public TClass getEnclosingClass() { + PlatformClass result = Platform.getEnclosingClass(getPlatformClass()); return result != null ? getClass(result) : null; } - - private static native PlatformClass getDeclaringClassImpl(PlatformClass cls); @SuppressWarnings("unchecked") public TClass asSubclass(TClass clazz) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CNameProvider.java b/core/src/main/java/org/teavm/backend/c/generate/CNameProvider.java index 45656d0cc..9db839354 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CNameProvider.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CNameProvider.java @@ -64,7 +64,7 @@ public class CNameProvider extends LowLevelNameProvider { preserveFieldNames(RuntimeClass.class.getName(), "size", "flags", "tag", "canary", "name", "itemType", "arrayType", "isSupertypeOf", "init", "enumValues", "layout", "simpleName", "superinterfaceCount", - "superinterfaces"); + "superinterfaces", "simpleNameCache", "declaringClass", "enclosingClass", "canonicalName"); memberFieldNames.put(new FieldReference(RuntimeClass.class.getName(), "parent"), "superclass"); preserveFieldNames(RuntimeReference.class.getName(), "queue", "object", "next"); preserveFieldNames(RuntimeReferenceQueue.class.getName(), "first", "last"); diff --git a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java index 41a055642..fc0fb9f2d 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java @@ -667,6 +667,9 @@ public class ClassGenerator { String initFunction = "NULL"; String superinterfaceCount = "0"; String superinterfaces = "NULL"; + String simpleName = null; + String declaringClass = "NULL"; + String enclosingClass = "NULL"; if (type instanceof ValueType.Object) { String className = ((ValueType.Object) type).getClassName(); @@ -682,8 +685,13 @@ public class ClassGenerator { } else { sizeExpr = "0"; } - if (cls != null && cls.hasModifier(ElementModifier.ENUM)) { - flags |= RuntimeClass.ENUM; + if (cls != null) { + if (cls.hasModifier(ElementModifier.ENUM)) { + flags |= RuntimeClass.ENUM; + } + if (cls.hasModifier(ElementModifier.SYNTHETIC)) { + flags |= RuntimeClass.SYNTHETIC; + } } List ranges = tagRegistry != null ? tagRegistry.getRanges(className) : null; tag = !context.isIncremental() && ranges != null && !ranges.isEmpty() ? ranges.get(0).lower : 0; @@ -730,6 +738,22 @@ public class ClassGenerator { break; } + simpleName = cls.getSimpleName(); + + if (cls.getDeclaringClassName() != null + && context.getDependencies().getClass(cls.getDeclaringClassName()) != null) { + declaringClass = "(TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.object(cls.getDeclaringClassName())); + includes.includeClass(cls.getDeclaringClassName()); + } + + if (cls.getOwnerName() != null + && context.getDependencies().getClass(cls.getOwnerName()) != null) { + enclosingClass = "(TeaVM_Class*) &" + context.getNames().forClassInstance( + ValueType.object(cls.getOwnerName())); + includes.includeClass(cls.getOwnerName()); + } + } else if (type instanceof ValueType.Array) { includes.includeClass("java.lang.Object"); parent = "(TeaVM_Class*) &" + context.getNames().forClassInstance(ValueType.object("java.lang.Object")); @@ -766,13 +790,20 @@ public class ClassGenerator { arrayTypeExpr = "NULL"; } + if (simpleName == null) { + simpleName = "NULL"; + } else { + int simpleNameIndex = context.getStringPool().getStringIndex(simpleName); + simpleName = "(TeaVM_Object**) &TEAVM_GET_STRING(" + simpleNameIndex + ")"; + } + includes.includePath("strings.h"); codeWriter.println(".size = " + sizeExpr + ","); codeWriter.println(".flags = " + flags + ","); codeWriter.println(".tag = " + tag + ","); codeWriter.println(".canary = 0,"); codeWriter.println(".name = (TeaVM_Object**) &TEAVM_GET_STRING(" + nameRef + "),"); - codeWriter.println(".simpleName = NULL,"); + codeWriter.println(".simpleName = " + simpleName + ","); codeWriter.println(".arrayType = " + arrayTypeExpr + ","); codeWriter.println(".itemType = " + itemTypeExpr + ","); codeWriter.println(".isSupertypeOf = &" + superTypeFunction + ","); @@ -781,6 +812,8 @@ public class ClassGenerator { codeWriter.println(".superinterfaces = " + superinterfaces + ","); codeWriter.println(".layout = " + layout + ","); codeWriter.println(".enumValues = " + enumConstants + ","); + codeWriter.println(".declaringClass = " + declaringClass + ","); + codeWriter.println(".enclosingClass = " + enclosingClass + ","); codeWriter.print(".init = " + initFunction); if (context.isHeapDump() && type instanceof ValueType.Object) { @@ -1161,7 +1194,7 @@ public class ClassGenerator { throw new AssertionError(); } } else if (type instanceof ValueType.Array) { - return nameOfType(((ValueType.Array) type).getItemType()) + "[]"; + return type.toString().replace('/', '.'); } else if (type == ValueType.VOID) { return "void"; } else if (type instanceof ValueType.Object) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java index 74ce4d85f..86e63668e 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java @@ -802,7 +802,8 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } private boolean isWrappedNativeCall(MethodReader method) { - if (!method.hasModifier(ElementModifier.NATIVE)) { + if (!method.hasModifier(ElementModifier.NATIVE) + || method.getAnnotations().get(DelegateTo.class.getName()) != null) { return false; } if (method.getAnnotations().get(Variable.class.getName()) != null) { diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformClassMetadataIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformClassMetadataIntrinsic.java index 3bb7fd4d2..e3f0ea8fa 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformClassMetadataIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/PlatformClassMetadataIntrinsic.java @@ -28,6 +28,12 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic { RuntimeClass.class.getName(), "parent"); private static final FieldReference NAME_FIELD = new FieldReference( RuntimeClass.class.getName(), "name"); + private static final FieldReference SIMPLE_NAME_FIELD = new FieldReference( + RuntimeClass.class.getName(), "simpleName"); + private static final FieldReference DECLARING_CLASS_FIELD = new FieldReference( + RuntimeClass.class.getName(), "declaringClass"); + private static final FieldReference ENCLOSING_CLASS_FIELD = new FieldReference( + RuntimeClass.class.getName(), "enclosingClass"); @Override public boolean canHandle(MethodReference method) { @@ -38,6 +44,9 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic { case "getArrayItem": case "getSuperclass": case "getName": + case "getSimpleName": + case "getDeclaringClass": + case "getEnclosingClass": return true; } return false; @@ -56,6 +65,19 @@ public class PlatformClassMetadataIntrinsic implements Intrinsic { context.writer().print("*"); printFieldAccess(context, invocation, NAME_FIELD); break; + case "getSimpleName": + context.writer().print("("); + printFieldAccess(context, invocation, SIMPLE_NAME_FIELD); + context.writer().print(" ? *"); + printFieldAccess(context, invocation, SIMPLE_NAME_FIELD); + context.writer().print(" : NULL)"); + break; + case "getDeclaringClass": + printFieldAccess(context, invocation, DECLARING_CLASS_FIELD); + break; + case "getEnclosingClass": + printFieldAccess(context, invocation, ENCLOSING_CLASS_FIELD); + break; } } diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index a54cc08cf..555436473 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -475,7 +475,7 @@ public class Renderer implements RenderingManager { return; } - Set classesRequiringName = findClassesRequiringName(); + Map classesRequiringName = findRequiredClassMetadata(); int start = writer.getOffset(); try { @@ -496,7 +496,7 @@ public class Renderer implements RenderingManager { } private void renderClassMetadataPortion(List classes, ObjectIntMap packageIndexes, - Set classesRequiringName) throws IOException { + Map metadataRequirements) throws IOException { writer.append("$rt_metadata(["); boolean first = true; for (PreparedClass cls : classes) { @@ -507,7 +507,8 @@ public class Renderer implements RenderingManager { debugEmitter.emitClass(cls.getName()); writer.appendClass(cls.getName()).append(",").ws(); - if (classesRequiringName.contains(cls.getName())) { + RequiredClassMetadata requiredMetadata = metadataRequirements.get(cls.getName()); + if (requiredMetadata != null && requiredMetadata.name) { String className = cls.getName(); int dotIndex = className.lastIndexOf('.') + 1; String packageName = className.substring(0, dotIndex); @@ -539,6 +540,33 @@ public class Renderer implements RenderingManager { writer.append(ElementModifier.pack(cls.getClassHolder().getModifiers())).append(',').ws(); writer.append(cls.getClassHolder().getLevel().ordinal()).append(',').ws(); + if (requiredMetadata == null || !(requiredMetadata.enclosingClass + || requiredMetadata.declaringClass || requiredMetadata.simpleName)) { + writer.append("0"); + } else { + writer.append('['); + if (requiredMetadata.enclosingClass && cls.getClassHolder().getOwnerName() != null) { + writer.appendClass(cls.getClassHolder().getOwnerName()); + } else { + writer.append('0'); + } + writer.append(','); + if (requiredMetadata.declaringClass && cls.getClassHolder().getDeclaringClassName() != null) { + writer.appendClass(cls.getClassHolder().getDeclaringClassName()); + } else { + writer.append('0'); + } + writer.append(','); + if (requiredMetadata.simpleName && cls.getClassHolder().getSimpleName() != null) { + writer.append("\"").append(RenderingUtil.escapeString(cls.getClassHolder().getSimpleName())) + .append("\""); + } else { + writer.append('0'); + } + writer.append(']'); + } + writer.append(",").ws(); + MethodReader clinit = classSource.get(cls.getName()).getMethod(CLINIT_METHOD); if (clinit != null && context.isDynamicInitializer(cls.getName())) { writer.appendClassInit(cls.getName()); @@ -561,13 +589,14 @@ public class Renderer implements RenderingManager { writer.append("]);").newLine(); } - private ObjectIntMap generatePackageMetadata(List classes, Set classesRequiringName) - throws IOException { + private ObjectIntMap generatePackageMetadata(List classes, + Map metadataRequirements) throws IOException { PackageNode root = new PackageNode(null); for (PreparedClass classNode : classes) { String className = classNode.getName(); - if (!classesRequiringName.contains(className)) { + RequiredClassMetadata requiredMetadata = metadataRequirements.get(className); + if (requiredMetadata == null || !requiredMetadata.name) { continue; } @@ -626,23 +655,50 @@ public class Renderer implements RenderingManager { } } - private Set findClassesRequiringName() { - Set classesRequiringName = new HashSet<>(); + private Map findRequiredClassMetadata() { + Map requirements = new HashMap<>(); + MethodDependencyInfo getNameMethod = context.getDependencyInfo().getMethod( new MethodReference(Class.class, "getName", String.class)); if (getNameMethod != null) { - addClassesRequiringName(classesRequiringName, getNameMethod.getVariable(0).getClassValueNode().getTypes()); + addClassesRequiringName(requirements, getNameMethod.getVariable(0).getClassValueNode().getTypes()); } + MethodDependencyInfo getSimpleNameMethod = context.getDependencyInfo().getMethod( new MethodReference(Class.class, "getSimpleName", String.class)); if (getSimpleNameMethod != null) { - addClassesRequiringName(classesRequiringName, - getSimpleNameMethod.getVariable(0).getClassValueNode().getTypes()); + String[] classNames = getSimpleNameMethod.getVariable(0).getClassValueNode().getTypes(); + addClassesRequiringName(requirements, classNames); + for (String className : classNames) { + RequiredClassMetadata requiredMetadata = requirements.computeIfAbsent(className, + k -> new RequiredClassMetadata()); + requiredMetadata.simpleName = true; + requiredMetadata.enclosingClass = true; + } } - return classesRequiringName; + + MethodDependencyInfo getDeclaringClassMethod = context.getDependencyInfo().getMethod( + new MethodReference(Class.class, "getDeclaringClass", Class.class)); + if (getDeclaringClassMethod != null) { + String[] classNames = getDeclaringClassMethod.getVariable(0).getClassValueNode().getTypes(); + for (String className : classNames) { + requirements.computeIfAbsent(className, k -> new RequiredClassMetadata()).declaringClass = true; + } + } + + MethodDependencyInfo getEnclosingClassMethod = context.getDependencyInfo().getMethod( + new MethodReference(Class.class, "getEnclosingClass", Class.class)); + if (getEnclosingClassMethod != null) { + String[] classNames = getEnclosingClassMethod.getVariable(0).getClassValueNode().getTypes(); + for (String className : classNames) { + requirements.computeIfAbsent(className, k -> new RequiredClassMetadata()).enclosingClass = true; + } + } + + return requirements; } - private void addClassesRequiringName(Set target, String[] source) { + private void addClassesRequiringName(Map target, String[] source) { for (String typeName : source) { if (typeName.startsWith("[")) { if (!typeName.endsWith(";")) { @@ -654,7 +710,7 @@ public class Renderer implements RenderingManager { } typeName = typeName.substring(index, typeName.length() - 1).replace('/', '.'); } - target.add(typeName); + target.computeIfAbsent(typeName, k -> new RequiredClassMetadata()).name = true; } } @@ -1161,4 +1217,11 @@ public class Renderer implements RenderingManager { private boolean isVirtual(MethodReference method) { return context.isVirtual(method); } + + static class RequiredClassMetadata { + boolean name; + boolean simpleName; + boolean declaringClass; + boolean enclosingClass; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java index 96201dc95..d6f65c2e6 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java @@ -74,6 +74,8 @@ public class WasmClassGenerator { DataPrimitives.ADDRESS, /* name */ DataPrimitives.ADDRESS, /* item type */ DataPrimitives.ADDRESS, /* array type */ + DataPrimitives.ADDRESS, /* declaring class */ + DataPrimitives.ADDRESS, /* enclosing class */ DataPrimitives.INT, /* isInstance function */ DataPrimitives.INT, /* init function */ DataPrimitives.ADDRESS, /* parent */ @@ -81,7 +83,9 @@ public class WasmClassGenerator { DataPrimitives.ADDRESS, /* interfaces */ DataPrimitives.ADDRESS, /* enum values */ DataPrimitives.ADDRESS, /* layout */ - DataPrimitives.ADDRESS /* simple name */); + DataPrimitives.ADDRESS, /* simple name */ + DataPrimitives.ADDRESS, /* simple name cache */ + DataPrimitives.ADDRESS /* canonical name cache */); private IntegerArray staticGcRoots = new IntegerArray(1); private int staticGcRootsAddress; @@ -92,12 +96,14 @@ public class WasmClassGenerator { private static final int CLASS_NAME = 5; private static final int CLASS_ITEM_TYPE = 6; private static final int CLASS_ARRAY_TYPE = 7; - private static final int CLASS_IS_INSTANCE = 8; - private static final int CLASS_INIT = 9; - private static final int CLASS_PARENT = 10; - private static final int CLASS_ENUM_VALUES = 13; - private static final int CLASS_LAYOUT = 14; - private static final int CLASS_SIMPLE_NAME = 15; + private static final int CLASS_DECLARING_CLASS = 8; + private static final int CLASS_ENCLOSING_CLASS = 9; + private static final int CLASS_IS_INSTANCE = 10; + private static final int CLASS_INIT = 11; + private static final int CLASS_PARENT = 12; + private static final int CLASS_ENUM_VALUES = 15; + private static final int CLASS_LAYOUT = 16; + private static final int CLASS_SIMPLE_NAME = 17; public WasmClassGenerator(ClassReaderSource processedClassSource, ClassReaderSource classSource, VirtualTableProvider vtableProvider, TagRegistry tagRegistry, BinaryWriter binaryWriter, @@ -275,6 +281,19 @@ public class WasmClassGenerator { functionTable.add(names.forSupertypeFunction(ValueType.object(name))); header.setAddress(CLASS_PARENT, parentPtr); + ClassReader cls = processedClassSource.get(name); + + if (cls.getSimpleName() != null) { + header.setAddress(CLASS_SIMPLE_NAME, stringPool.getStringPointer(cls.getSimpleName())); + } + + if (cls.getOwnerName() != null && processedClassSource.get(cls.getOwnerName()) != null) { + header.setAddress(CLASS_ENCLOSING_CLASS, getClassPointer(ValueType.object(cls.getOwnerName()))); + } + if (cls.getDeclaringClassName() != null && processedClassSource.get(cls.getDeclaringClassName()) != null) { + header.setAddress(CLASS_DECLARING_CLASS, getClassPointer(ValueType.object(cls.getDeclaringClassName()))); + } + if (vtable != null) { fillVirtualTable(vtable, array); } @@ -296,10 +315,14 @@ public class WasmClassGenerator { staticGcRoots.add(binaryData.fieldLayout.get(field.getFieldName())); } - ClassReader cls = processedClassSource.get(name); - if (cls != null && cls.hasModifier(ElementModifier.ENUM)) { - header.setAddress(CLASS_ENUM_VALUES, generateEnumValues(cls, binaryData)); - flags |= RuntimeClass.ENUM; + if (cls != null) { + if (cls.hasModifier(ElementModifier.ENUM)) { + header.setAddress(CLASS_ENUM_VALUES, generateEnumValues(cls, binaryData)); + flags |= RuntimeClass.ENUM; + } + if (cls.hasModifier(ElementModifier.SYNTHETIC)) { + flags |= RuntimeClass.SYNTHETIC; + } } if (cls != null && binaryData.start >= 0 @@ -311,7 +334,6 @@ public class WasmClassGenerator { } header.setInt(CLASS_FLAGS, flags); - header.setAddress(CLASS_SIMPLE_NAME, 0); return vtable != null ? wrapper : header; } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformClassMetadataIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformClassMetadataIntrinsic.java index d8ebe0100..65ff99b94 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformClassMetadataIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformClassMetadataIntrinsic.java @@ -25,12 +25,13 @@ import org.teavm.runtime.RuntimeClass; public class PlatformClassMetadataIntrinsic implements WasmIntrinsic { private static final String PLATFORM_CLASS_METADATA = "org.teavm.platform.PlatformClassMetadata"; - private static final FieldReference ITEM_TYPE_FIELD = new FieldReference( - RuntimeClass.class.getName(), "itemType"); - private static final FieldReference SUPERCLASS_FIELD = new FieldReference( - RuntimeClass.class.getName(), "parent"); - private static final FieldReference NAME_FIELD = new FieldReference( - RuntimeClass.class.getName(), "name"); + private static final String RUNTIME_CLASS = RuntimeClass.class.getName(); + private static final FieldReference ITEM_TYPE_FIELD = new FieldReference(RUNTIME_CLASS, "itemType"); + private static final FieldReference SUPERCLASS_FIELD = new FieldReference(RUNTIME_CLASS, "parent"); + private static final FieldReference NAME_FIELD = new FieldReference(RUNTIME_CLASS, "name"); + private static final FieldReference SIMPLE_NAME_FIELD = new FieldReference(RUNTIME_CLASS, "simpleName"); + private static final FieldReference ENCLOSING_CLASS_FIELD = new FieldReference(RUNTIME_CLASS, "enclosingClass"); + private static final FieldReference DECLARING_CLASS_FIELD = new FieldReference(RUNTIME_CLASS, "declaringClass"); @Override public boolean isApplicable(MethodReference methodReference) { @@ -41,6 +42,9 @@ public class PlatformClassMetadataIntrinsic implements WasmIntrinsic { case "getArrayItem": case "getSuperclass": case "getName": + case "getSimpleName": + case "getEnclosingClass": + case "getDeclaringClass": return true; } return false; @@ -55,6 +59,12 @@ public class PlatformClassMetadataIntrinsic implements WasmIntrinsic { return fieldAccess(manager, invocation, SUPERCLASS_FIELD); case "getName": return fieldAccess(manager, invocation, NAME_FIELD); + case "getSimpleName": + return fieldAccess(manager, invocation, SIMPLE_NAME_FIELD); + case "getEnclosingClass": + return fieldAccess(manager, invocation, ENCLOSING_CLASS_FIELD); + case "getDeclaringClass": + return fieldAccess(manager, invocation, DECLARING_CLASS_FIELD); default: return new WasmUnreachable(); } diff --git a/core/src/main/java/org/teavm/cache/CachedClassReader.java b/core/src/main/java/org/teavm/cache/CachedClassReader.java index dd5b69ce2..c9cb4143d 100644 --- a/core/src/main/java/org/teavm/cache/CachedClassReader.java +++ b/core/src/main/java/org/teavm/cache/CachedClassReader.java @@ -30,6 +30,8 @@ class CachedClassReader extends CachedElement implements ClassReader { GenericTypeParameter[] parameters; GenericValueType.Object genericParent; String owner; + String declaringClass; + String simpleName; Set interfaces; Set genericInterfaces; Map methods; @@ -84,4 +86,14 @@ class CachedClassReader extends CachedElement implements ClassReader { public String getOwnerName() { return owner; } + + @Override + public String getSimpleName() { + return simpleName; + } + + @Override + public String getDeclaringClassName() { + return declaringClass; + } } diff --git a/core/src/main/java/org/teavm/cache/ClassIO.java b/core/src/main/java/org/teavm/cache/ClassIO.java index 4498a47b5..18eba882c 100644 --- a/core/src/main/java/org/teavm/cache/ClassIO.java +++ b/core/src/main/java/org/teavm/cache/ClassIO.java @@ -59,6 +59,9 @@ public class ClassIO { output.writeUnsigned(packModifiers(cls.readModifiers())); output.writeUnsigned(cls.getParent() != null ? symbolTable.lookup(cls.getParent()) + 1 : 0); output.writeUnsigned(cls.getOwnerName() != null ? symbolTable.lookup(cls.getOwnerName()) + 1 : 0); + output.writeUnsigned(cls.getDeclaringClassName() != null + ? symbolTable.lookup(cls.getDeclaringClassName()) + 1 : 0); + output.writeUnsigned(cls.getSimpleName() != null ? symbolTable.lookup(cls.getSimpleName()) + 1 : 0); output.writeUnsigned(cls.getInterfaces().size()); for (String iface : cls.getInterfaces()) { output.writeUnsigned(symbolTable.lookup(iface)); @@ -84,6 +87,11 @@ public class ClassIO { cls.parent = parentIndex > 0 ? referenceCache.getCached(symbolTable.at(parentIndex - 1)) : null; int ownerIndex = input.readUnsigned(); cls.owner = ownerIndex > 0 ? referenceCache.getCached(symbolTable.at(ownerIndex - 1)) : null; + int declaringClassIndex = input.readUnsigned(); + cls.declaringClass = declaringClassIndex > 0 + ? referenceCache.getCached(symbolTable.at(declaringClassIndex - 1)) : null; + int simpleNameIndex = input.readUnsigned(); + cls.simpleName = simpleNameIndex > 0 ? referenceCache.getCached(symbolTable.at(simpleNameIndex - 1)) : null; int ifaceCount = input.readUnsigned(); Set interfaces = new LinkedHashSet<>(); for (int i = 0; i < ifaceCount; ++i) { diff --git a/core/src/main/java/org/teavm/model/ClassHolder.java b/core/src/main/java/org/teavm/model/ClassHolder.java index 0aeec5582..2d7f8b100 100644 --- a/core/src/main/java/org/teavm/model/ClassHolder.java +++ b/core/src/main/java/org/teavm/model/ClassHolder.java @@ -26,6 +26,8 @@ public class ClassHolder extends ElementHolder implements ClassReader { private Map methods = new LinkedHashMap<>(); private Map fields = new LinkedHashMap<>(); private String ownerName; + private String simpleName; + private String declaringClassName; public ClassHolder(String name) { super(name); @@ -138,4 +140,22 @@ public class ClassHolder extends ElementHolder implements ClassReader { public void setOwnerName(String ownerName) { this.ownerName = ownerName; } + + @Override + public String getSimpleName() { + return simpleName; + } + + public void setSimpleName(String simpleName) { + this.simpleName = simpleName; + } + + @Override + public String getDeclaringClassName() { + return declaringClassName; + } + + public void setDeclaringClassName(String declaringClassName) { + this.declaringClassName = declaringClassName; + } } diff --git a/core/src/main/java/org/teavm/model/ClassReader.java b/core/src/main/java/org/teavm/model/ClassReader.java index a28c875f5..733fbbdfc 100644 --- a/core/src/main/java/org/teavm/model/ClassReader.java +++ b/core/src/main/java/org/teavm/model/ClassReader.java @@ -38,4 +38,8 @@ public interface ClassReader extends ElementReader { Collection getFields(); String getOwnerName(); + + String getSimpleName(); + + String getDeclaringClassName(); } diff --git a/core/src/main/java/org/teavm/model/util/ModelUtils.java b/core/src/main/java/org/teavm/model/util/ModelUtils.java index c0ed52820..1f002b39f 100644 --- a/core/src/main/java/org/teavm/model/util/ModelUtils.java +++ b/core/src/main/java/org/teavm/model/util/ModelUtils.java @@ -49,6 +49,8 @@ public final class ModelUtils { target.addField(copyField(field)); } target.setOwnerName(original.getOwnerName()); + target.setDeclaringClassName(original.getDeclaringClassName()); + target.setSimpleName(original.getSimpleName()); copyAnnotations(original.getAnnotations(), target.getAnnotations()); return target; } diff --git a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java index 71a60304f..6d5b6d1b1 100644 --- a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java +++ b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java @@ -89,6 +89,9 @@ public class ClassRefsRenamer extends AbstractInstructionVisitor { if (cls.getOwnerName() != null) { renamedCls.setOwnerName(classNameMapper.apply(cls.getOwnerName())); } + if (cls.getDeclaringClassName() != null) { + renamedCls.setDeclaringClassName(classNameMapper.apply(cls.getDeclaringClassName())); + } rename(cls.getAnnotations(), renamedCls.getAnnotations()); for (String iface : cls.getInterfaces()) { String mappedIfaceName = classNameMapper.apply(iface); diff --git a/core/src/main/java/org/teavm/parsing/Parser.java b/core/src/main/java/org/teavm/parsing/Parser.java index 1a2e85b76..527b96c0d 100644 --- a/core/src/main/java/org/teavm/parsing/Parser.java +++ b/core/src/main/java/org/teavm/parsing/Parser.java @@ -29,6 +29,7 @@ import org.objectweb.asm.commons.JSRInlinerAdapter; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InnerClassNode; import org.objectweb.asm.tree.MethodNode; import org.teavm.common.Graph; import org.teavm.common.GraphUtils; @@ -302,14 +303,24 @@ public class Parser { cls.addMethod(method); method.updateReference(referenceCache); } + if (node.outerClass != null) { cls.setOwnerName(node.outerClass.replace('/', '.')); - } else { - int lastIndex = node.name.lastIndexOf('$'); - if (lastIndex != -1) { - cls.setOwnerName(node.name.substring(0, lastIndex).replace('/', '.')); + } + + if (node.innerClasses != null && !node.innerClasses.isEmpty()) { + for (InnerClassNode innerClassNode : node.innerClasses) { + if (node.name.equals(innerClassNode.name)) { + if (innerClassNode.outerName != null) { + cls.setDeclaringClassName(innerClassNode.outerName.replace('/', '.')); + cls.setOwnerName(cls.getDeclaringClassName()); + } + cls.setSimpleName(innerClassNode.innerName); + break; + } } } + parseAnnotations(cls.getAnnotations(), node.visibleAnnotations, node.invisibleAnnotations); return cls; } diff --git a/core/src/main/java/org/teavm/runtime/RuntimeClass.java b/core/src/main/java/org/teavm/runtime/RuntimeClass.java index aa46f0cee..8bff02a36 100644 --- a/core/src/main/java/org/teavm/runtime/RuntimeClass.java +++ b/core/src/main/java/org/teavm/runtime/RuntimeClass.java @@ -22,6 +22,7 @@ public class RuntimeClass extends RuntimeObject { public static final int INITIALIZED = 1; public static final int PRIMITIVE = 2; public static final int ENUM = 4; + public static final int SYNTHETIC = 1024; public static final int PRIMITIVE_SHIFT = 3; public static final int PRIMITIVE_MASK = 15; @@ -49,6 +50,8 @@ public class RuntimeClass extends RuntimeObject { public RuntimeObjectPtr name; public RuntimeClass itemType; public RuntimeClass arrayType; + public RuntimeClass declaringClass; + public RuntimeClass enclosingClass; public IsSupertypeFunction isSupertypeOf; public InitFunction init; public RuntimeClass parent; @@ -56,7 +59,9 @@ public class RuntimeClass extends RuntimeObject { public RuntimeClassPointer superinterfaces; public Address enumValues; public Address layout; - public RuntimeObject simpleName; + public RuntimeObjectPtr simpleName; + public RuntimeObject simpleNameCache; + public RuntimeObject canonicalName; @Unmanaged public static int computeCanary(int size, int tag) { diff --git a/core/src/main/java/org/teavm/runtime/RuntimeObjectPtr.java b/core/src/main/java/org/teavm/runtime/RuntimeObjectPtr.java index 68f2c7422..b8ce318cd 100644 --- a/core/src/main/java/org/teavm/runtime/RuntimeObjectPtr.java +++ b/core/src/main/java/org/teavm/runtime/RuntimeObjectPtr.java @@ -18,5 +18,5 @@ package org.teavm.runtime; import org.teavm.interop.Structure; public class RuntimeObjectPtr extends Structure { - RuntimeObject value; + public RuntimeObject value; } diff --git a/core/src/main/resources/org/teavm/backend/c/core.h b/core/src/main/resources/org/teavm/backend/c/core.h index a815c3844..de53f1ec4 100644 --- a/core/src/main/resources/org/teavm/backend/c/core.h +++ b/core/src/main/resources/org/teavm/backend/c/core.h @@ -30,6 +30,8 @@ typedef struct TeaVM_Class { TeaVM_Object** name; struct TeaVM_Class* itemType; struct TeaVM_Class* arrayType; + struct TeaVM_Class* declaringClass; + struct TeaVM_Class* enclosingClass; int32_t (*isSupertypeOf)(struct TeaVM_Class*); void (*init)(); struct TeaVM_Class* superclass; @@ -37,7 +39,9 @@ typedef struct TeaVM_Class { struct TeaVM_Class** superinterfaces; void* enumValues; void* layout; - TeaVM_Object* simpleName; + TeaVM_Object** simpleName; + TeaVM_Object* simpleNameCache; + TeaVM_Object* canonicalName; #if TEAVM_HEAP_DUMP TeaVM_FieldDescriptors* fieldDescriptors; TeaVM_StaticFieldDescriptors* staticFieldDescriptors; diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 9addff0b2..b317fc8f5 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -108,8 +108,18 @@ function $rt_arraycls(cls) { if (result === null) { var arraycls = {}; var name = "[" + cls.$meta.binaryName; - arraycls.$meta = { item : cls, supertypes : [$rt_objcls()], primitive : false, superclass : $rt_objcls(), - name : name, binaryName : name, enum : false }; + arraycls.$meta = { + item: cls, + supertypes: [$rt_objcls()], + primitive: false, + superclass: $rt_objcls(), + name: name, + binaryName: name, + enum: false, + simpleName: null, + declaringClass: null, + enclosingClass: null + }; arraycls.classObject = null; arraycls.$array = null; result = arraycls; @@ -121,7 +131,7 @@ function $rt_createcls() { return { $array : null, classObject : null, - $meta : { + $meta: { supertypes : [], superclass : null } @@ -134,6 +144,9 @@ function $rt_createPrimitiveCls(name, binaryName) { cls.$meta.binaryName = binaryName; cls.$meta.enum = false; cls.$meta.item = null; + cls.$meta.simpleName = null; + cls.$meta.declaringClass = null; + cls.$meta.enclosingClass = null; return cls; } var $rt_booleanclsCache = null; @@ -453,6 +466,20 @@ function $rt_metadata(data) { m.accessLevel = data[i++]; + var innerClassInfo = data[i++]; + if (innerClassInfo === 0) { + m.simpleName = null; + m.declaringClass = null; + m.enclosingClass = null; + } else { + var enclosingClass = innerClassInfo[0]; + m.enclosingClass = enclosingClass !== 0 ? enclosingClass : null; + var declaringClass = innerClassInfo[1]; + m.declaringClass = declaringClass !== 0 ? declaringClass : null; + var simpleName = innerClassInfo[2]; + m.simpleName = simpleName !== 0 ? simpleName : null; + } + var clinit = data[i++]; cls.$clinit = clinit !== 0 ? clinit : function() {}; diff --git a/platform/src/main/java/org/teavm/platform/Platform.java b/platform/src/main/java/org/teavm/platform/Platform.java index e84bc5cbb..cdb040d71 100644 --- a/platform/src/main/java/org/teavm/platform/Platform.java +++ b/platform/src/main/java/org/teavm/platform/Platform.java @@ -246,6 +246,24 @@ public final class Platform { return cls.getMetadata().getName(); } + @Unmanaged + @PluggableDependency(PlatformGenerator.class) + public static String getSimpleName(PlatformClass cls) { + return cls.getMetadata().getSimpleName(); + } + + @Unmanaged + @PluggableDependency(PlatformGenerator.class) + public static PlatformClass getEnclosingClass(PlatformClass cls) { + return cls.getMetadata().getEnclosingClass(); + } + + @Unmanaged + @PluggableDependency(PlatformGenerator.class) + public static PlatformClass getDeclaringClass(PlatformClass cls) { + return cls.getMetadata().getDeclaringClass(); + } + @PlatformMarker(Platforms.LOW_LEVEL) private static boolean isLowLevel() { return false; diff --git a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java index 51739473c..b6ff7f97c 100644 --- a/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java +++ b/platform/src/main/java/org/teavm/platform/PlatformClassMetadata.java @@ -46,4 +46,16 @@ public interface PlatformClassMetadata extends JSObject { @JSProperty int getAccessLevel(); + + @JSProperty + @Unmanaged + String getSimpleName(); + + @JSProperty + @Unmanaged + PlatformClass getEnclosingClass(); + + @JSProperty + @Unmanaged + PlatformClass getDeclaringClass(); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index 676fb3c34..0f75cb495 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -60,8 +60,13 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin method.getResult().propagate(agent.getType("[Ljava/lang/Enum;")); break; case "getName": + case "getSimpleName": method.getResult().propagate(agent.getType("java.lang.String")); break; + case "getEnclosingClass": + case "getDeclaringClass": + method.getResult().propagate(agent.getType("java.lang.Class")); + break; } } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java index 8d429669d..a3fb30d07 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ClassTest.java @@ -39,12 +39,7 @@ public class ClassTest { @Test public void classSimpleNameEvaluated() { - assertEquals("Object", Object.class.getSimpleName()); - assertEquals("Object[]", Object[].class.getSimpleName()); - assertEquals("int", int.class.getSimpleName()); - assertEquals("int[]", int[].class.getSimpleName()); - assertEquals("InnerClass", InnerClass.class.getSimpleName()); - assertEquals("", new Object() { }.getClass().getSimpleName()); + } @Test @@ -126,8 +121,36 @@ public class ClassTest { } @Test - public void declaringClassFound() { - assertEquals(ClassTest.class, new A().getClass().getDeclaringClass()); + public void classProperties() { + class B { + } + + @SuppressWarnings("Convert2Lambda") + Runnable r = new Runnable() { + @Override + public void run() { + } + }; + + String testName = "org.teavm.classlib.java.lang.ClassTest"; + + testClassProperties(getClass(), "ClassTest", testName, null, null); + testClassProperties(new ClassTest[0].getClass(), "ClassTest[]", testName + "[]", null, null); + testClassProperties(int.class, "int", "int", null, null); + testClassProperties(new int[0].getClass(), "int[]", "int[]", null, null); + testClassProperties(new A().getClass(), "A", testName + ".A", ClassTest.class, ClassTest.class); + testClassProperties(new A[0].getClass(), "A[]", testName + ".A[]", null, null); + testClassProperties(new B().getClass(), "B", null, null, ClassTest.class); + testClassProperties(new B[0].getClass(), "B[]", null, null, null); + testClassProperties(r.getClass(), "", null, null, ClassTest.class); + } + + private void testClassProperties(Class cls, String expectedSimpleName, String expectedCanonicalName, + Class expectedDeclaringClass, Class expectedEnclosingClass) { + assertEquals(expectedSimpleName, cls.getSimpleName()); + assertEquals(expectedCanonicalName, cls.getCanonicalName()); + assertEquals(expectedDeclaringClass, cls.getDeclaringClass()); + assertEquals(expectedEnclosingClass, cls.getEnclosingClass()); } @Test