mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
jso: support exporting class constructors
This commit is contained in:
parent
a6fb67817c
commit
72b021fc0b
|
@ -21,6 +21,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR })
|
||||||
public @interface JSExport {
|
public @interface JSExport {
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
private ListableClassReaderSource classSource;
|
private ListableClassReaderSource classSource;
|
||||||
private JSTypeHelper typeHelper;
|
private JSTypeHelper typeHelper;
|
||||||
private RenderingManager context;
|
private RenderingManager context;
|
||||||
|
private int lastExportIndex;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void begin(RenderingManager context, BuildTarget buildTarget) {
|
public void begin(RenderingManager context, BuildTarget buildTarget) {
|
||||||
|
@ -65,19 +66,28 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
.appendGlobal("Symbol").append("('jsoClass')").endDeclaration();
|
.appendGlobal("Symbol").append("('jsoClass')").endDeclaration();
|
||||||
writer.append("(()").ws().append("=>").ws().append("{").softNewLine().indent();
|
writer.append("(()").ws().append("=>").ws().append("{").softNewLine().indent();
|
||||||
writer.append("let c;").softNewLine();
|
writer.append("let c;").softNewLine();
|
||||||
|
var exportedNamesByClass = new HashMap<String, String>();
|
||||||
for (var className : classSource.getClassNames()) {
|
for (var className : classSource.getClassNames()) {
|
||||||
var classReader = classSource.get(className);
|
var classReader = classSource.get(className);
|
||||||
var hasExportedMembers = false;
|
var hasExportedMembers = false;
|
||||||
hasExportedMembers |= exportClassInstanceMembers(classReader);
|
hasExportedMembers |= exportClassInstanceMembers(classReader);
|
||||||
if (!className.equals(context.getEntryPoint())) {
|
if (!className.equals(context.getEntryPoint())) {
|
||||||
hasExportedMembers |= exportClassStaticMembers(classReader);
|
var name = "$rt_export_class_ " + getClassAliasName(classReader) + "_" + lastExportIndex++;
|
||||||
if (hasExportedMembers && !typeHelper.isJavaScriptClass(className)
|
hasExportedMembers |= exportClassStaticMembers(classReader, name);
|
||||||
&& !typeHelper.isJavaScriptImplementation(className)) {
|
if (hasExportedMembers) {
|
||||||
exportClassFromModule(classReader);
|
exportedNamesByClass.put(className, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.outdent().append("})();").newLine();
|
writer.outdent().append("})();").newLine();
|
||||||
|
for (var className : classSource.getClassNames()) {
|
||||||
|
var classReader = classSource.get(className);
|
||||||
|
var name = exportedNamesByClass.get(className);
|
||||||
|
if (name != null && !typeHelper.isJavaScriptClass(className)
|
||||||
|
&& !typeHelper.isJavaScriptImplementation(className)) {
|
||||||
|
exportClassFromModule(classReader, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean exportClassInstanceMembers(ClassReader classReader) {
|
private boolean exportClassInstanceMembers(ClassReader classReader) {
|
||||||
|
@ -125,14 +135,14 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean exportClassStaticMembers(ClassReader classReader) {
|
private boolean exportClassStaticMembers(ClassReader classReader, String name) {
|
||||||
var members = collectMembers(classReader, c -> c.hasModifier(ElementModifier.STATIC));
|
var members = collectMembers(classReader, c -> c.hasModifier(ElementModifier.STATIC));
|
||||||
|
|
||||||
if (members.methods.isEmpty() && members.properties.isEmpty()) {
|
if (members.methods.isEmpty() && members.properties.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.append("c").ws().append("=").ws().appendClass(classReader.getName()).append(";").softNewLine();
|
writer.append("c").ws().append("=").ws().appendFunction(name).append(";").softNewLine();
|
||||||
|
|
||||||
for (var aliasEntry : members.methods.entrySet()) {
|
for (var aliasEntry : members.methods.entrySet()) {
|
||||||
appendMethodAlias(aliasEntry.getKey());
|
appendMethodAlias(aliasEntry.getKey());
|
||||||
|
@ -175,6 +185,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
private Members collectMembers(ClassReader classReader, Predicate<MethodReader> filter) {
|
private Members collectMembers(ClassReader classReader, Predicate<MethodReader> filter) {
|
||||||
var methods = new HashMap<String, MethodDescriptor>();
|
var methods = new HashMap<String, MethodDescriptor>();
|
||||||
var properties = new HashMap<String, PropertyInfo>();
|
var properties = new HashMap<String, PropertyInfo>();
|
||||||
|
MethodDescriptor constructor = null;
|
||||||
for (var method : classReader.getMethods()) {
|
for (var method : classReader.getMethods()) {
|
||||||
if (!filter.test(method)) {
|
if (!filter.test(method)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -195,10 +206,13 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
propInfo.setter = method.getDescriptor();
|
propInfo.setter = method.getDescriptor();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
constructor = method.getDescriptor();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Members(methods, properties);
|
return new Members(methods, properties, constructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportModule() {
|
private void exportModule() {
|
||||||
|
@ -214,7 +228,40 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exportClassFromModule(ClassReader cls) {
|
private void exportClassFromModule(ClassReader cls, String functionName) {
|
||||||
|
var name = getClassAliasName(cls);
|
||||||
|
var constructors = collectMembers(cls, method -> !method.hasModifier(ElementModifier.STATIC));
|
||||||
|
|
||||||
|
var method = constructors.constructor;
|
||||||
|
writer.append("function ").appendFunction(functionName).append("(");
|
||||||
|
if (method != null) {
|
||||||
|
for (var i = 0; i < method.parameterCount(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
writer.append(",").ws();
|
||||||
|
}
|
||||||
|
writer.append("p" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.append(")").ws().appendBlockStart();
|
||||||
|
if (method != null) {
|
||||||
|
writer.appendClass(cls.getName()).append(".call(this);").softNewLine();
|
||||||
|
writer.appendMethod(new MethodReference(cls.getName(), method)).append("(this");
|
||||||
|
for (var i = 0; i < method.parameterCount(); ++i) {
|
||||||
|
writer.append(",").ws().append("p" + i);
|
||||||
|
}
|
||||||
|
writer.append(");").softNewLine();
|
||||||
|
} else {
|
||||||
|
writer.append("throw new Error(\"Can't instantiate this class directly\");").softNewLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.outdent().append("}").append(";").softNewLine();
|
||||||
|
|
||||||
|
writer.appendFunction(functionName).append(".prototype").ws().append("=").ws()
|
||||||
|
.appendClass(cls.getName()).append(".prototype;").softNewLine();
|
||||||
|
context.exportFunction(functionName, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassAliasName(ClassReader cls) {
|
||||||
var name = cls.getSimpleName();
|
var name = cls.getSimpleName();
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = cls.getName().substring(cls.getName().lastIndexOf('.') + 1);
|
name = cls.getName().substring(cls.getName().lastIndexOf('.') + 1);
|
||||||
|
@ -229,7 +276,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.exportClass(cls.getName(), name);
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasClassesToExpose() {
|
private boolean hasClassesToExpose() {
|
||||||
|
@ -263,6 +310,11 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
return new Alias(annot.getValue("name").getString(), AliasKind.SETTER);
|
return new Alias(annot.getValue("name").getString(), AliasKind.SETTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
annot = method.getAnnotations().get(JSConstructorToExpose.class.getName());
|
||||||
|
if (annot != null) {
|
||||||
|
return new Alias(null, AliasKind.CONSTRUCTOR);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,10 +406,13 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
private static class Members {
|
private static class Members {
|
||||||
final Map<String, MethodDescriptor> methods;
|
final Map<String, MethodDescriptor> methods;
|
||||||
final Map<String, PropertyInfo> properties;
|
final Map<String, PropertyInfo> properties;
|
||||||
|
final MethodDescriptor constructor;
|
||||||
|
|
||||||
Members(Map<String, MethodDescriptor> methods, Map<String, PropertyInfo> properties) {
|
Members(Map<String, MethodDescriptor> methods, Map<String, PropertyInfo> properties,
|
||||||
|
MethodDescriptor constructor) {
|
||||||
this.methods = methods;
|
this.methods = methods;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,6 +434,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
|
||||||
private enum AliasKind {
|
private enum AliasKind {
|
||||||
METHOD,
|
METHOD,
|
||||||
GETTER,
|
GETTER,
|
||||||
SETTER
|
SETTER,
|
||||||
|
CONSTRUCTOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2017 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.jso.impl;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.CLASS)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@interface JSConstructorToExpose {
|
||||||
|
}
|
|
@ -56,6 +56,9 @@ class JSDependencyListener extends AbstractDependencyListener {
|
||||||
if (exposeAnnot == null) {
|
if (exposeAnnot == null) {
|
||||||
exposeAnnot = method.getAnnotations().get(JSSetterToExpose.class.getName());
|
exposeAnnot = method.getAnnotations().get(JSSetterToExpose.class.getName());
|
||||||
}
|
}
|
||||||
|
if (exposeAnnot == null) {
|
||||||
|
exposeAnnot = method.getAnnotations().get(JSConstructorToExpose.class.getName());
|
||||||
|
}
|
||||||
if (exposeAnnot != null) {
|
if (exposeAnnot != null) {
|
||||||
MethodDependency methodDep = agent.linkMethod(method.getReference());
|
MethodDependency methodDep = agent.linkMethod(method.getReference());
|
||||||
if (methodDep.getMethod() != null) {
|
if (methodDep.getMethod() != null) {
|
||||||
|
|
|
@ -241,12 +241,17 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
case SETTER:
|
case SETTER:
|
||||||
annotationName = JSSetterToExpose.class.getName();
|
annotationName = JSSetterToExpose.class.getName();
|
||||||
break;
|
break;
|
||||||
|
case CONSTRUCTOR:
|
||||||
|
annotationName = JSConstructorToExpose.class.getName();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
annotationName = JSMethodToExpose.class.getName();
|
annotationName = JSMethodToExpose.class.getName();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var annot = new AnnotationHolder(annotationName);
|
var annot = new AnnotationHolder(annotationName);
|
||||||
annot.getValues().put("name", new AnnotationValue(export.alias));
|
if (export.kind != MethodKind.CONSTRUCTOR) {
|
||||||
|
annot.getValues().put("name", new AnnotationValue(export.alias));
|
||||||
|
}
|
||||||
return annot;
|
return annot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,51 +353,55 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
private MethodExport createMethodExport(MethodReader method) {
|
private MethodExport createMethodExport(MethodReader method) {
|
||||||
String name = null;
|
String name = null;
|
||||||
MethodKind kind = MethodKind.METHOD;
|
MethodKind kind = MethodKind.METHOD;
|
||||||
var methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
if (method.getName().equals("<init>")) {
|
||||||
if (methodAnnot != null) {
|
kind = MethodKind.CONSTRUCTOR;
|
||||||
name = method.getName();
|
|
||||||
var nameVal = methodAnnot.getValue("value");
|
|
||||||
if (nameVal != null) {
|
|
||||||
String nameStr = nameVal.getString();
|
|
||||||
if (!nameStr.isEmpty()) {
|
|
||||||
name = nameStr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
var propertyAnnot = method.getAnnotations().get(JSProperty.class.getName());
|
var methodAnnot = method.getAnnotations().get(JSMethod.class.getName());
|
||||||
if (propertyAnnot != null) {
|
if (methodAnnot != null) {
|
||||||
var nameVal = propertyAnnot.getValue("value");
|
name = method.getName();
|
||||||
|
var nameVal = methodAnnot.getValue("value");
|
||||||
if (nameVal != null) {
|
if (nameVal != null) {
|
||||||
String nameStr = nameVal.getString();
|
String nameStr = nameVal.getString();
|
||||||
if (!nameStr.isEmpty()) {
|
if (!nameStr.isEmpty()) {
|
||||||
name = nameStr;
|
name = nameStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String expectedPrefix;
|
} else {
|
||||||
if (method.parameterCount() == 0) {
|
var propertyAnnot = method.getAnnotations().get(JSProperty.class.getName());
|
||||||
if (method.getResultType() == ValueType.BOOLEAN) {
|
if (propertyAnnot != null) {
|
||||||
expectedPrefix = "is";
|
var nameVal = propertyAnnot.getValue("value");
|
||||||
} else {
|
if (nameVal != null) {
|
||||||
expectedPrefix = "get";
|
String nameStr = nameVal.getString();
|
||||||
|
if (!nameStr.isEmpty()) {
|
||||||
|
name = nameStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String expectedPrefix;
|
||||||
|
if (method.parameterCount() == 0) {
|
||||||
|
if (method.getResultType() == ValueType.BOOLEAN) {
|
||||||
|
expectedPrefix = "is";
|
||||||
|
} else {
|
||||||
|
expectedPrefix = "get";
|
||||||
|
}
|
||||||
|
kind = MethodKind.GETTER;
|
||||||
|
} else {
|
||||||
|
expectedPrefix = "set";
|
||||||
|
kind = MethodKind.SETTER;
|
||||||
}
|
}
|
||||||
kind = MethodKind.GETTER;
|
|
||||||
} else {
|
|
||||||
expectedPrefix = "set";
|
|
||||||
kind = MethodKind.SETTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
name = method.getName();
|
name = method.getName();
|
||||||
if (name.startsWith(expectedPrefix) && name.length() > expectedPrefix.length()
|
if (name.startsWith(expectedPrefix) && name.length() > expectedPrefix.length()
|
||||||
&& Character.isUpperCase(name.charAt(expectedPrefix.length()))) {
|
&& Character.isUpperCase(name.charAt(expectedPrefix.length()))) {
|
||||||
name = Character.toLowerCase(name.charAt(expectedPrefix.length()))
|
name = Character.toLowerCase(name.charAt(expectedPrefix.length()))
|
||||||
+ name.substring(expectedPrefix.length() + 1);
|
+ name.substring(expectedPrefix.length() + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (name == null) {
|
||||||
if (name == null) {
|
name = method.getName();
|
||||||
name = method.getName();
|
}
|
||||||
}
|
}
|
||||||
return new MethodExport(name, kind);
|
return new MethodExport(name, kind);
|
||||||
}
|
}
|
||||||
|
@ -421,7 +430,8 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
|
||||||
enum MethodKind {
|
enum MethodKind {
|
||||||
METHOD,
|
METHOD,
|
||||||
GETTER,
|
GETTER,
|
||||||
SETTER
|
SETTER,
|
||||||
|
CONSTRUCTOR
|
||||||
}
|
}
|
||||||
|
|
||||||
static class MethodExport {
|
static class MethodExport {
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class ModuleWithExportedClasses {
|
||||||
public static class B {
|
public static class B {
|
||||||
private int bar;
|
private int bar;
|
||||||
|
|
||||||
|
@JSExport
|
||||||
public B(int bar) {
|
public B(int bar) {
|
||||||
this.bar = bar;
|
this.bar = bar;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,4 +21,9 @@ export async function test() {
|
||||||
assertEquals(true, o instanceof BB);
|
assertEquals(true, o instanceof BB);
|
||||||
assertEquals(false, o instanceof A);
|
assertEquals(false, o instanceof A);
|
||||||
assertEquals(42, o.bar);
|
assertEquals(42, o.bar);
|
||||||
|
|
||||||
|
let p = new BB(55);
|
||||||
|
assertEquals(true, p instanceof BB);
|
||||||
|
assertEquals(false, p instanceof A);
|
||||||
|
assertEquals(55, p.bar);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user