WASM: refactoring resource generator

This commit is contained in:
Alexey Andreev 2017-04-12 23:06:45 +03:00
parent f1da7a417e
commit f716a4be62
21 changed files with 416 additions and 238 deletions

View File

@ -93,7 +93,6 @@ import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
@ -397,14 +396,14 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
}
private class IntrinsicFactoryContext implements WasmIntrinsicFactoryContext {
private ClassReaderSource classSource;
private ListableClassReaderSource classSource;
public IntrinsicFactoryContext(ClassReaderSource classSource) {
public IntrinsicFactoryContext(ListableClassReaderSource classSource) {
this.classSource = classSource;
}
@Override
public ClassReaderSource getClassSource() {
public ListableClassReaderSource getClassSource() {
return classSource;
}

View File

@ -107,6 +107,7 @@ import org.teavm.backend.wasm.model.expression.WasmStoreInt64;
import org.teavm.backend.wasm.model.expression.WasmSwitch;
import org.teavm.backend.wasm.model.expression.WasmUnreachable;
import org.teavm.backend.wasm.render.WasmTypeInference;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.Address;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReference;
@ -1534,6 +1535,11 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
public BinaryWriter getBinaryWriter() {
return binaryWriter;
}
@Override
public Diagnostics getDiagnostics() {
return context.getDiagnostics();
}
};
private WasmLocal getTemporary(WasmType type) {

View File

@ -17,10 +17,10 @@ package org.teavm.backend.wasm.intrinsics;
import java.util.Properties;
import org.teavm.common.ServiceRepository;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource;
public interface WasmIntrinsicFactoryContext {
ClassReaderSource getClassSource();
ListableClassReaderSource getClassSource();
ClassLoader getClassLoader();

View File

@ -18,9 +18,12 @@ package org.teavm.backend.wasm.intrinsics;
import org.teavm.ast.Expr;
import org.teavm.backend.wasm.binary.BinaryWriter;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.diagnostics.Diagnostics;
public interface WasmIntrinsicManager {
WasmExpression generate(Expr expr);
BinaryWriter getBinaryWriter();
Diagnostics getDiagnostics();
}

View File

@ -22,10 +22,6 @@ import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceArray;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceArray<T extends Resource> implements ResourceArray<T>, ResourceWriter {
private List<T> data = new ArrayList<>();

View File

@ -15,10 +15,6 @@
*/
package org.teavm.platform.plugin;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceGetter implements BuildTimeResourceMethod {
private int index;

View File

@ -22,10 +22,6 @@ import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceMap;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceMap<T extends Resource> implements ResourceMap<T>, ResourceWriter {
private Map<String, T> data = new HashMap<>();

View File

@ -16,21 +16,12 @@
package org.teavm.platform.plugin;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceArray;
import org.teavm.platform.metadata.ResourceMap;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceProxyBuilder {
private Map<Class<?>, BuildTimeResourceProxyFactory> factories = new HashMap<>();
private static Set<Class<?>> allowedPropertyTypes = new HashSet<>(Arrays.asList(
boolean.class, byte.class, short.class, int.class, float.class, double.class,
String.class, ResourceArray.class, ResourceMap.class));
private static Map<Class<?>, Object> defaultValues = new HashMap<>();
static {
@ -43,41 +34,41 @@ class BuildTimeResourceProxyBuilder {
}
public BuildTimeResourceProxy buildProxy(Class<?> iface) {
BuildTimeResourceProxyFactory factory = factories.get(iface);
if (factory == null) {
factory = createFactory(iface);
factories.put(iface, factory);
}
return factory.create();
return factories.computeIfAbsent(iface, k -> createFactory(iface)).create();
}
private BuildTimeResourceProxyFactory createFactory(Class<?> iface) {
return new ProxyFactoryCreation(iface).create();
return new ProxyFactoryCreation(new ResourceTypeDescriptor(iface)).create();
}
private static class ProxyFactoryCreation {
private Class<?> rootIface;
Map<String, Class<?>> getters = new HashMap<>();
Map<String, Class<?>> setters = new HashMap<>();
Map<Method, BuildTimeResourceMethod> methods = new HashMap<>();
private Map<String, Integer> propertyIndexes = new HashMap<>();
private Object[] initialData;
private Map<String, Class<?>> propertyTypes = new HashMap<>();
private ResourceTypeDescriptor descriptor;
public ProxyFactoryCreation(Class<?> iface) {
this.rootIface = iface;
public ProxyFactoryCreation(ResourceTypeDescriptor descriptor) {
this.descriptor = descriptor;
}
BuildTimeResourceProxyFactory create() {
if (!rootIface.isInterface()) {
throw new IllegalArgumentException("Error creating a new resource of type " + rootIface.getName()
+ " that is not an interface");
for (Map.Entry<Method, ResourceMethodDescriptor> entry : descriptor.getMethods().entrySet()) {
Method method = entry.getKey();
ResourceMethodDescriptor methodDescriptor = entry.getValue();
int index = getPropertyIndex(methodDescriptor.getPropertyName());
switch (methodDescriptor.getType()) {
case GETTER:
methods.put(method, new BuildTimeResourceGetter(index));
break;
case SETTER:
methods.put(method, new BuildTimeResourceSetter(index));
break;
}
}
scanIface(rootIface);
// Fill default values
initialData = new Object[propertyIndexes.size()];
for (Map.Entry<String, Class<?>> property : propertyTypes.entrySet()) {
for (Map.Entry<String, Class<?>> property : descriptor.getPropertyTypes().entrySet()) {
String propertyName = property.getKey();
Class<?> propertyType = property.getValue();
initialData[propertyIndexes.get(propertyName)] = defaultValues.get(propertyType);
@ -92,147 +83,31 @@ class BuildTimeResourceProxyBuilder {
}
// Create factory
String[] properties = new String[propertyIndexes.size()];
String[] properties = new String[descriptor.getPropertyTypes().size()];
for (Map.Entry<String, Integer> entry : propertyIndexes.entrySet()) {
properties[entry.getValue()] = entry.getKey();
}
methods.put(writeMethod, new BuildTimeResourceWriterMethod(properties));
for (Method method : ResourceTypeDescriptorProvider.class.getDeclaredMethods()) {
switch (method.getName()) {
case "getDescriptor":
methods.put(method, (proxy, args) -> descriptor);
break;
case "getValues":
methods.put(method, (proxy, args) -> initialData.clone());
break;
case "getPropertyIndex":
methods.put(method, (proxy, args) -> propertyIndexes.getOrDefault(args[0], -1));
break;
}
}
return new BuildTimeResourceProxyFactory(methods, initialData);
}
private void scanIface(Class<?> iface) {
if (!Resource.class.isAssignableFrom(iface)) {
throw new IllegalArgumentException("Error creating a new resource of type " + iface.getName() + "."
+ " This type does not implement the " + Resource.class.getName() + " interface");
}
// Scan methods
getters.clear();
setters.clear();
for (Method method : iface.getDeclaredMethods()) {
if (method.getName().startsWith("get")) {
scanGetter(method);
} else if (method.getName().startsWith("is")) {
scanBooleanGetter(method);
} else if (method.getName().startsWith("set")) {
scanSetter(method);
} else {
throwInvalidMethod(method);
}
}
// Verify consistency of getters and setters
for (Map.Entry<String, Class<?>> property : getters.entrySet()) {
String propertyName = property.getKey();
Class<?> getterType = property.getValue();
Class<?> setterType = setters.get(propertyName);
if (setterType == null) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a getter, but does not have a setter");
}
if (!setterType.equals(getterType)) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a getter and a setter of different types");
}
}
for (String propertyName : setters.keySet()) {
if (!getters.containsKey(propertyName)) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a setter, but does not have a getter");
}
}
// Verify types of properties
for (Map.Entry<String, Class<?>> property : getters.entrySet()) {
String propertyName = property.getKey();
Class<?> propertyType = property.getValue();
if (!allowedPropertyTypes.contains(propertyType)) {
if (!propertyType.isInterface() || !Resource.class.isAssignableFrom(propertyType)) {
throw new IllegalArgumentException("Property " + rootIface.getName() + "." + propertyName
+ " has an illegal type " + propertyType.getName());
}
}
if (!propertyTypes.containsKey(propertyName)) {
propertyTypes.put(propertyName, propertyType);
}
}
// Scan superinterfaces
for (Class<?> superIface : iface.getInterfaces()) {
scanIface(superIface);
}
}
private void throwInvalidMethod(Method method) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is not likely to be either getter or setter");
}
private void scanGetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(3));
if (propertyName == null || method.getReturnType().equals(void.class)
|| method.getParameterTypes().length > 0) {
throwInvalidMethod(method);
}
if (getters.put(propertyName, method.getReturnType()) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate getter for property " + propertyName);
}
methods.put(method, new BuildTimeResourceGetter(getPropertyIndex(propertyName)));
}
private void scanBooleanGetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(2));
if (propertyName == null || !method.getReturnType().equals(boolean.class)
|| method.getParameterTypes().length > 0) {
throwInvalidMethod(method);
}
if (getters.put(propertyName, method.getReturnType()) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate getter for property " + propertyName);
}
methods.put(method, new BuildTimeResourceGetter(getPropertyIndex(propertyName)));
}
private void scanSetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(3));
if (propertyName == null || !method.getReturnType().equals(void.class)
|| method.getParameterTypes().length != 1) {
throwInvalidMethod(method);
}
if (setters.put(propertyName, method.getParameterTypes()[0]) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate setter for property " + propertyName);
}
methods.put(method, new BuildTimeResourceSetter(getPropertyIndex(propertyName)));
}
private String extractPropertyName(String propertyName) {
if (propertyName.isEmpty()) {
return null;
}
char c = propertyName.charAt(0);
if (c != Character.toUpperCase(c)) {
return null;
}
if (propertyName.length() == 1) {
return propertyName.toLowerCase();
}
c = propertyName.charAt(1);
if (c == Character.toUpperCase(c)) {
return propertyName;
} else {
return Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
}
}
private int getPropertyIndex(String propertyName) {
Integer index = propertyIndexes.get(propertyName);
if (index == null) {
index = propertyIndexes.size();
propertyIndexes.put(propertyName, index);
}
return index;
return propertyIndexes.computeIfAbsent(propertyName, k -> propertyIndexes.size());
}
}
}

View File

@ -19,10 +19,6 @@ import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceProxyFactory {
private Map<Method, BuildTimeResourceMethod> methods = new HashMap<>();
private Object[] initialData;

View File

@ -17,10 +17,6 @@ package org.teavm.platform.plugin;
import org.teavm.backend.javascript.codegen.SourceWriter;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceWriterMethod implements BuildTimeResourceMethod {
private String[] propertyNames;

View File

@ -20,10 +20,6 @@ import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.model.FieldReference;
import org.teavm.platform.metadata.StaticFieldResource;
/**
*
* @author Alexey Andreev
*/
class BuildTimeStaticFieldResource implements StaticFieldResource, ResourceWriter {
private FieldReference field;

View File

@ -20,36 +20,53 @@ import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.model.AnnotationReader;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.common.ServiceRepository;
import org.teavm.model.CallLocation;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.platform.metadata.MetadataGenerator;
import org.teavm.platform.metadata.MetadataProvider;
import org.teavm.platform.metadata.Resource;
public class MetadataIntrinsic implements WasmIntrinsic {
private ClassReaderSource classSource;
private ListableClassReaderSource classSource;
private ClassLoader classLoader;
private ServiceRepository services;
private Properties properties;
public MetadataIntrinsic(ClassReaderSource classSource, ClassLoader classLoader, Properties properties) {
public MetadataIntrinsic(ListableClassReaderSource classSource, ClassLoader classLoader,
ServiceRepository services, Properties properties) {
this.classSource = classSource;
this.classLoader = classLoader;
this.services = services;
this.properties = properties;
}
@Override
public boolean isApplicable(MethodReference methodReference) {
ClassReader cls = classSource.get(methodReference.getClassName());
if (cls == null) {
MethodReader method = classSource.resolve(methodReference);
if (method == null) {
return false;
}
AnnotationReader annot = cls.getAnnotations().get(MetadataProvider.class.getName());
return annot != null;
return method.getAnnotations().get(MetadataProvider.class.getName()) != null;
}
@Override
public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) {
MethodReader method = classSource.resolve(invocation.getMethod());
MetadataGenerator generator = MetadataUtils.createMetadataGenerator(classLoader, method,
new CallLocation(invocation.getMethod()), manager.getDiagnostics());
if (generator == null) {
return new WasmInt32Constant(0);
}
DefaultMetadataGeneratorContext metadataContext = new DefaultMetadataGeneratorContext(classSource,
classLoader, properties, services);
Resource resource = generator.generateMetadata(metadataContext, invocation.getMethod());
return null;
}
}

View File

@ -16,12 +16,14 @@
package org.teavm.platform.plugin;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
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.*;
import org.teavm.model.AnnotationReader;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.platform.metadata.MetadataGenerator;
import org.teavm.platform.metadata.MetadataProvider;
import org.teavm.platform.metadata.Resource;
@ -36,33 +38,12 @@ public class MetadataProviderNativeGenerator implements Generator {
AnnotationReader refAnnot = method.getAnnotations().get(MetadataProviderRef.class.getName());
methodRef = MethodReference.parse(refAnnot.getValue("value").getString());
// Find and instantiate metadata generator
ValueType generatorType = providerAnnot.getValue("value").getJavaClass();
String generatorClassName = ((ValueType.Object) generatorType).getClassName();
Class<?> generatorClass;
try {
generatorClass = Class.forName(generatorClassName, true, context.getClassLoader());
} catch (ClassNotFoundException e) {
context.getDiagnostics().error(new CallLocation(methodRef), "Can't find metadata provider class {{c0}}",
generatorClassName);
return;
}
Constructor<?> cons;
try {
cons = generatorClass.getConstructor();
} catch (NoSuchMethodException e) {
context.getDiagnostics().error(new CallLocation(methodRef), "Metadata generator {{c0}} does not have "
+ "a public no-arg constructor", generatorClassName);
return;
}
MetadataGenerator generator;
try {
generator = (MetadataGenerator) cons.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
context.getDiagnostics().error(new CallLocation(methodRef), "Error instantiating metadata "
+ "generator {{c0}}", generatorClassName);
MetadataGenerator generator = MetadataUtils.createMetadataGenerator(context.getClassLoader(),
method, new CallLocation(methodRef), context.getDiagnostics());
if (generator == null) {
return;
}
DefaultMetadataGeneratorContext metadataContext = new DefaultMetadataGeneratorContext(context.getClassSource(),
context.getClassLoader(), context.getProperties(), context);

View File

@ -0,0 +1,64 @@
/*
* 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.platform.plugin;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.AnnotationReader;
import org.teavm.model.CallLocation;
import org.teavm.model.MethodReader;
import org.teavm.model.ValueType;
import org.teavm.platform.metadata.MetadataGenerator;
import org.teavm.platform.metadata.MetadataProvider;
public final class MetadataUtils {
private MetadataUtils() {
}
public static MetadataGenerator createMetadataGenerator(ClassLoader classLoader, MethodReader method,
CallLocation callLocation, Diagnostics diagnostics) {
AnnotationReader annot = method.getAnnotations().get(MetadataProvider.class.getName());
ValueType generatorType = annot.getValue("value").getJavaClass();
String generatorClassName = ((ValueType.Object) generatorType).getClassName();
Class<?> generatorClass;
try {
generatorClass = Class.forName(generatorClassName, true, classLoader);
} catch (ClassNotFoundException e) {
diagnostics.error(callLocation, "Can't find metadata provider class {{c0}}",
generatorClassName);
return null;
}
Constructor<?> cons;
try {
cons = generatorClass.getConstructor();
} catch (NoSuchMethodException e) {
diagnostics.error(callLocation, "Metadata generator {{c0}} does not have "
+ "a public no-arg constructor", generatorClassName);
return null;
}
MetadataGenerator generator;
try {
generator = (MetadataGenerator) cons.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
diagnostics.error(callLocation, "Error instantiating metadata "
+ "generator {{c0}}", generatorClassName);
return null;
}
return generator;
}
}

View File

@ -32,7 +32,8 @@ public class PlatformPlugin implements TeaVMPlugin {
TeaVMWasmHost wasmHost = host.getExtension(TeaVMWasmHost.class);
if (wasmHost != null) {
wasmHost.add(ctx -> new MetadataIntrinsic(ctx.getClassSource(), ctx.getClassLoader(), ctx.getProperties()));
wasmHost.add(ctx -> new MetadataIntrinsic(ctx.getClassSource(), ctx.getClassLoader(), ctx.getServices(),
ctx.getProperties()));
}
host.add(new AsyncMethodProcessor());

View File

@ -0,0 +1,21 @@
/*
* 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.platform.plugin;
public enum ResourceAccessorType {
GETTER,
SETTER
}

View File

@ -0,0 +1,34 @@
/*
* 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.platform.plugin;
public class ResourceMethodDescriptor {
private String propertyName;
private ResourceAccessorType type;
public ResourceMethodDescriptor(String propertyName, ResourceAccessorType type) {
this.propertyName = propertyName;
this.type = type;
}
public String getPropertyName() {
return propertyName;
}
public ResourceAccessorType getType() {
return type;
}
}

View File

@ -0,0 +1,185 @@
/*
* 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.platform.plugin;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceArray;
import org.teavm.platform.metadata.ResourceMap;
public class ResourceTypeDescriptor {
private static Set<Class<?>> allowedPropertyTypes = new HashSet<>(Arrays.asList(
boolean.class, byte.class, short.class, int.class, float.class, double.class,
String.class, ResourceArray.class, ResourceMap.class));
private Class<?> rootIface;
private Map<String, Class<?>> getters = new HashMap<>();
private Map<String, Class<?>> setters = new HashMap<>();
private Map<Method, ResourceMethodDescriptor> methods = new LinkedHashMap<>();
private Map<String, Class<?>> propertyTypes = new LinkedHashMap<>();
public ResourceTypeDescriptor(Class<?> iface) {
this.rootIface = iface;
if (!rootIface.isInterface()) {
throw new IllegalArgumentException("Error creating a new resource of type " + rootIface.getName()
+ " that is not an interface");
}
scanIface(rootIface);
}
public Class<?> getRootInterface() {
return rootIface;
}
public Map<Method, ResourceMethodDescriptor> getMethods() {
return methods;
}
public Map<String, Class<?>> getPropertyTypes() {
return propertyTypes;
}
private void scanIface(Class<?> iface) {
if (!Resource.class.isAssignableFrom(iface)) {
throw new IllegalArgumentException("Error creating a new resource of type " + iface.getName() + "."
+ " This type does not implement the " + Resource.class.getName() + " interface");
}
// Scan methods
getters.clear();
setters.clear();
for (Method method : iface.getDeclaredMethods()) {
if (method.getName().startsWith("get")) {
scanGetter(method);
} else if (method.getName().startsWith("is")) {
scanBooleanGetter(method);
} else if (method.getName().startsWith("set")) {
scanSetter(method);
} else {
throwInvalidMethod(method);
}
}
// Verify consistency of getters and setters
for (Map.Entry<String, Class<?>> property : getters.entrySet()) {
String propertyName = property.getKey();
Class<?> getterType = property.getValue();
Class<?> setterType = setters.get(propertyName);
if (setterType == null) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a getter, but does not have a setter");
}
if (!setterType.equals(getterType)) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a getter and a setter of different types");
}
}
for (String propertyName : setters.keySet()) {
if (!getters.containsKey(propertyName)) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName
+ " has a setter, but does not have a getter");
}
}
// Verify types of properties
for (Map.Entry<String, Class<?>> property : getters.entrySet()) {
String propertyName = property.getKey();
Class<?> propertyType = property.getValue();
if (!allowedPropertyTypes.contains(propertyType)) {
if (!propertyType.isInterface() || !Resource.class.isAssignableFrom(propertyType)) {
throw new IllegalArgumentException("Property " + rootIface.getName() + "." + propertyName
+ " has an illegal type " + propertyType.getName());
}
}
if (!propertyTypes.containsKey(propertyName)) {
propertyTypes.put(propertyName, propertyType);
}
}
// Scan superinterfaces
for (Class<?> superIface : iface.getInterfaces()) {
scanIface(superIface);
}
}
private void throwInvalidMethod(Method method) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is not likely to be either getter or setter");
}
private void scanGetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(3));
if (propertyName == null || method.getReturnType().equals(void.class)
|| method.getParameterTypes().length > 0) {
throwInvalidMethod(method);
}
if (getters.put(propertyName, method.getReturnType()) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate getter for property " + propertyName);
}
methods.put(method, new ResourceMethodDescriptor(propertyName, ResourceAccessorType.GETTER));
}
private void scanBooleanGetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(2));
if (propertyName == null || !method.getReturnType().equals(boolean.class)
|| method.getParameterTypes().length > 0) {
throwInvalidMethod(method);
}
if (getters.put(propertyName, method.getReturnType()) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate getter for property " + propertyName);
}
methods.put(method, new ResourceMethodDescriptor(propertyName, ResourceAccessorType.GETTER));
}
private void scanSetter(Method method) {
String propertyName = extractPropertyName(method.getName().substring(3));
if (propertyName == null || !method.getReturnType().equals(void.class)
|| method.getParameterTypes().length != 1) {
throwInvalidMethod(method);
}
if (setters.put(propertyName, method.getParameterTypes()[0]) != null) {
throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "."
+ method.getName() + " is a duplicate setter for property " + propertyName);
}
methods.put(method, new ResourceMethodDescriptor(propertyName, ResourceAccessorType.SETTER));
}
private String extractPropertyName(String propertyName) {
if (propertyName.isEmpty()) {
return null;
}
char c = propertyName.charAt(0);
if (c != Character.toUpperCase(c)) {
return null;
}
if (propertyName.length() == 1) {
return propertyName.toLowerCase();
}
c = propertyName.charAt(1);
if (c == Character.toUpperCase(c)) {
return propertyName;
} else {
return Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
}
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.platform.plugin;
public interface ResourceTypeDescriptorProvider {
ResourceTypeDescriptor getDescriptor();
Object[] getValues();
int getPropertyIndex(String propertyName);
}

View File

@ -18,10 +18,6 @@ package org.teavm.platform.plugin;
import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
/**
*
* @author Alexey Andreev
*/
public interface ResourceWriter {
void write(SourceWriter writer) throws IOException;
}

View File

@ -18,10 +18,6 @@ package org.teavm.platform.plugin;
import java.io.IOException;
import org.teavm.backend.javascript.codegen.SourceWriter;
/**
*
* @author Alexey Andreev
*/
final class ResourceWriterHelper {
private ResourceWriterHelper() {
}