Adds incomplete implementation of build-time resource proxy

This commit is contained in:
konsoletyper 2014-06-03 17:06:56 +04:00
parent a533e6f112
commit c23f62ced7
17 changed files with 652 additions and 7 deletions

View File

@ -15,5 +15,10 @@
<artifactId>teavm-core</artifactId> <artifactId>teavm-core</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -23,7 +23,7 @@ import java.lang.annotation.Target;
/** /**
* <p>Marks a valid <b>resource interface</b>. Resource interface is an interface, that has get* and set* methods, * <p>Marks a valid <b>resource interface</b>. Resource interface is an interface, that has get* and set* methods,
* according the default convention for JavaBeans. Each property must have both getter and setter. * according the default convention for JavaBeans. Each property must have both getter and setter.
* Also each property's must be either primitive value, except for <code>long</code> or valid resource.</p> * Also each property's must be either primitive value (except for <code>long</code>) or a valid resource.</p>
* *
* @see MetadataGenerator * @see MetadataGenerator
* @see ResourceArray * @see ResourceArray

View File

@ -20,6 +20,8 @@ package org.teavm.platform.metadata;
* @author Alexey Andreev <konsoletyper@gmail.com> * @author Alexey Andreev <konsoletyper@gmail.com>
*/ */
public interface ResourceMap<T> { public interface ResourceMap<T> {
boolean has(String key);
T get(String key); T get(String key);
void put(String key, T value); void put(String key, T value);

View File

@ -0,0 +1,43 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.util.ArrayList;
import java.util.List;
import org.teavm.platform.metadata.ResourceArray;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceArray<T> implements ResourceArray<T> {
private List<T> data = new ArrayList<>();
@Override
public int size() {
return data.size();
}
@Override
public T get(int i) {
return data.get(i);
}
@Override
public void add(T elem) {
data.add(elem);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceGetter implements BuildTimeResourceMethod {
private int index;
public BuildTimeResourceGetter(int index) {
this.index = index;
}
@Override
public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable {
return proxy.data[index];
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.util.HashMap;
import java.util.Map;
import org.teavm.platform.metadata.ResourceMap;
/**
*
* @author Alexey Andreev
*/
class BuildTimeResourceMap<T> implements ResourceMap<T> {
private Map<String, T> data = new HashMap<>();
@Override
public boolean has(String key) {
return data.containsKey(key);
}
@Override
public T get(String key) {
return data.get(key);
}
@Override
public void put(String key, T value) {
data.put(key, value);
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
/**
*
* @author Alexey Andreev
*/
interface BuildTimeResourceMethod {
Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable;
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
class BuildTimeResourceProxy implements InvocationHandler {
private Map<Method, BuildTimeResourceMethod> methods;
Object[] data;
public BuildTimeResourceProxy(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData) {
this.methods = methods;
data = Arrays.copyOf(initialData, initialData.length);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return methods.get(method).invoke(this, args);
}
}

View File

@ -0,0 +1,203 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.lang.reflect.Method;
import java.util.*;
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.<Class<?>>asList(
boolean.class, Boolean.class, byte.class, Byte.class, short.class, Short.class,
int.class, Integer.class, float.class, Float.class, double.class, Double.class,
String.class, ResourceArray.class, ResourceMap.class));
public BuildTimeResourceProxy buildProxy(Class<?> iface) {
BuildTimeResourceProxyFactory factory = factories.get(iface);
if (factory == null) {
factory = createFactory(iface);
factories.put(iface, factory);
}
return factory.create();
}
private BuildTimeResourceProxyFactory createFactory(Class<?> iface) {
return new ProxyFactoryCreation(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 List<Object> initialData = new ArrayList<>();
private Map<String, Integer> propertyIndexes = new HashMap<>();
public ProxyFactoryCreation(Class<?> iface) {
this.rootIface = iface;
}
BuildTimeResourceProxyFactory create() {
if (!rootIface.isInterface()) {
throw new IllegalArgumentException("Error creating a new resource of type " + rootIface.getName() +
" that is not an interface");
}
scanIface(rootIface);
return new BuildTimeResourceProxyFactory(methods, initialData.toArray(new Object[initialData.size()]));
}
private void scanIface(Class<?> iface) {
if (iface.isAnnotationPresent(Resource.class)) {
throw new IllegalArgumentException("Error creating a new resource of type " + iface.getName() +
". This type is not marked with the " + Resource.class.getName() + " annotation");
}
// 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 and fill default values
for (Map.Entry<String, Class<?>> property : getters.entrySet()) {
String propertyName = property.getKey();
Class<?> propertyType = property.getValue();
if (allowedPropertyTypes.contains(propertyType)) {
continue;
}
if (!propertyType.isInterface() || !propertyType.isAnnotationPresent(Resource.class)) {
throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName +
" has an illegal type " + propertyType.getName());
}
}
// TODO: fill default values
// 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(2));
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;
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
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;
public BuildTimeResourceProxyFactory(Map<Method, BuildTimeResourceMethod> methods, Object[] initialData) {
this.methods = methods;
this.initialData = initialData;
}
BuildTimeResourceProxy create() {
return new BuildTimeResourceProxy(methods, initialData);
}
}

View File

@ -15,16 +15,20 @@
*/ */
package org.teavm.platform.plugin; package org.teavm.platform.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/** /**
* *
* @author Alexey Andreev <konsoletyper@gmail.com> * @author Alexey Andreev
*/ */
class ResourceProxy implements InvocationHandler { class BuildTimeResourceSetter implements BuildTimeResourceMethod {
private int index;
public BuildTimeResourceSetter(int index) {
this.index = index;
}
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable {
proxy.data[index] = args[0];
return null; return null;
} }
} }

View File

@ -0,0 +1,72 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.lang.reflect.Proxy;
import java.util.Properties;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.platform.metadata.MetadataGeneratorContext;
import org.teavm.platform.metadata.ResourceArray;
import org.teavm.platform.metadata.ResourceMap;
/**
*
* @author Alexey Andreev
*/
class DefaultMetadataGeneratorContext implements MetadataGeneratorContext {
private ListableClassReaderSource classSource;
private ClassLoader classLoader;
private Properties properties;
private BuildTimeResourceProxyBuilder proxyBuilder = new BuildTimeResourceProxyBuilder();
private DefaultMetadataGeneratorContext(ListableClassReaderSource classSource, ClassLoader classLoader,
Properties properties) {
this.classSource = classSource;
this.classLoader = classLoader;
this.properties = properties;
}
@Override
public ListableClassReaderSource getClassSource() {
return classSource;
}
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public Properties getProperties() {
return new Properties(properties);
}
@Override
public <T> T createResource(Class<T> resourceType) {
return resourceType.cast(Proxy.newProxyInstance(classLoader, new Class<?>[] { resourceType },
proxyBuilder.buildProxy(resourceType)));
}
@Override
public <T> ResourceArray<T> createResourceArray() {
return new BuildTimeResourceArray<>();
}
@Override
public <T> ResourceMap<T> createResourceMap() {
return new BuildTimeResourceMap<>();
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import java.io.IOException;
import org.teavm.codegen.SourceWriter;
import org.teavm.javascript.ni.Generator;
import org.teavm.javascript.ni.GeneratorContext;
import org.teavm.model.*;
import org.teavm.platform.metadata.MetadataProvider;
/**
*
* @author Alexey Andreev
*/
class MetadataProviderNativeGenerator implements Generator {
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
MethodReader method = cls.getMethod(methodRef.getDescriptor());
AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName());
if (providerAnnot == null) {
return;
}
if (!method.hasModifier(ElementModifier.NATIVE)) {
throw new IllegalStateException("Method " + method.getReference() + " was marked with " +
MetadataProvider.class.getName() + " but it is not native");
}
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.plugin;
import org.teavm.javascript.ni.GeneratedBy;
import org.teavm.model.*;
import org.teavm.platform.metadata.MetadataProvider;
/**
*
* @author Alexey Andreev
*/
class MetadataProviderTransformer implements ClassHolderTransformer {
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource) {
for (MethodHolder method : cls.getMethods()) {
AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName());
if (providerAnnot == null) {
return;
}
AnnotationHolder genAnnot = new AnnotationHolder(GeneratedBy.class.getName());
genAnnot.getValues().put("value", new AnnotationValue(ValueType.object(null)));
method.getAnnotations().add(genAnnot);
}
}
}

View File

@ -25,5 +25,6 @@ import org.teavm.vm.spi.TeaVMPlugin;
public class PlatformPlugin implements TeaVMPlugin { public class PlatformPlugin implements TeaVMPlugin {
@Override @Override
public void install(TeaVMHost host) { public void install(TeaVMHost host) {
host.add(new MetadataProviderTransformer());
} }
} }

View File

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

View File

@ -0,0 +1,31 @@
/*
* Copyright 2014 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.platform.metadata;
/**
*
* @author Alexey Andreev
*/
@Resource
public interface TestResource {
int getInt();
void setInt(int value);
String getString();
void setString(String string);
}