/****************************************************************************** * Copyright (c) 2016 Oracle * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire.internal; import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ATHROW; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.GETSTATIC; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.IRETURN; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.V1_5; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; import org.eclipse.sapphire.Disposable; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementHandle; import org.eclipse.sapphire.ElementImpl; import org.eclipse.sapphire.ElementList; import org.eclipse.sapphire.ElementProperty; import org.eclipse.sapphire.ElementType; import org.eclipse.sapphire.ImpliedElementProperty; import org.eclipse.sapphire.ListProperty; import org.eclipse.sapphire.Observable; import org.eclipse.sapphire.Property; import org.eclipse.sapphire.PropertyDef; import org.eclipse.sapphire.ReferenceValue; import org.eclipse.sapphire.Resource; import org.eclipse.sapphire.Transient; import org.eclipse.sapphire.TransientProperty; import org.eclipse.sapphire.Value; import org.eclipse.sapphire.ValueProperty; import org.eclipse.sapphire.modeling.annotations.DelegateImplementation; import org.eclipse.sapphire.modeling.annotations.Reference; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class ElementCompiler { private final ElementType type; private final Class<?> typeInterfaceClass; private final String typeInterfaceClassInternalName; private final String typeImplClassInternalName; private final Set<Method> implementedMethods; public ElementCompiler( final ElementType type ) { this.type = type; this.typeInterfaceClass = this.type.getModelElementClass(); this.typeInterfaceClassInternalName = Type.getInternalName( this.typeInterfaceClass ); this.typeImplClassInternalName = this.typeInterfaceClassInternalName + "$Impl"; this.implementedMethods = new HashSet<Method>(); } public byte[] compile() { ClassWriter cw = new ClassWriter( COMPUTE_MAXS ); cw.visit( V1_5, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, this.typeImplClassInternalName, null, Type.getInternalName( ElementImpl.class ), new String[] { this.typeInterfaceClassInternalName } ); processConstructor( cw ); for( PropertyDef property : this.type.properties() ) { if( property instanceof ValueProperty ) { processValueProperty( cw, (ValueProperty) property ); } else if( property instanceof TransientProperty ) { processTransientProperty( cw, (TransientProperty) property ); } else if( property instanceof ListProperty ) { processListProperty( cw, (ListProperty) property ); } else if( property instanceof ImpliedElementProperty ) { processImpliedElementProperty( cw, (ImpliedElementProperty) property ); } else if( property instanceof ElementProperty ) { processElementProperty( cw, (ElementProperty) property ); } else { throw new IllegalStateException( property.getClass().getName() ); } } processDelegatedMethods( cw ); processUnimplementedMethods( cw ); return cw.toByteArray(); } private void processConstructor( final ClassWriter cw ) { final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, "<init>", Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( Property.class ), Type.getType( Resource.class ) } ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, "TYPE", Type.getDescriptor( ElementType.class ) ); mv.visitVarInsn( ALOAD, 1 ); mv.visitVarInsn( ALOAD, 2 ); mv.visitMethodInsn ( INVOKESPECIAL, Type.getInternalName( ElementImpl.class ), "<init>", Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( ElementType.class ), Type.getType( Property.class ), Type.getType( Resource.class ) } ) ); mv.visitInsn( RETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } private void processValueProperty( final ClassWriter cw, final ValueProperty property ) { final String propertyFieldName = findPropertyField( property ).getName(); final Reference referenceAnnotation = property.getAnnotation( Reference.class ); final boolean reference = ( referenceAnnotation != null ); Method getter = findMethod( "get" + property.name() ); if( getter == null ) { getter = findMethod( "is" + property.name() ); } if( getter != null ) { this.implementedMethods.add( getter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, getter.getName(), Type.getMethodDescriptor( Type.getType( reference ? ReferenceValue.class : Value.class ), new Type[ 0 ] ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( ValueProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( Value.class ), new Type[] { Type.getType( ValueProperty.class ) } ) ); if( reference ) { mv.visitTypeInsn( CHECKCAST, Type.getInternalName( ReferenceValue.class ) ); } mv.visitInsn( ARETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } implementSetterMethod( cw, property, String.class ); final Class<?> propertyTypeClass = property.getTypeClass(); if( propertyTypeClass != String.class ) { implementSetterMethod( cw, property, propertyTypeClass ); } if( reference ) { implementSetterMethod( cw, property, referenceAnnotation.target() ); } } private void implementSetterMethod( final ClassWriter cw, final ValueProperty property, final Class<?> type ) { final String propertyFieldName = findPropertyField( property ).getName(); Method setter = findMethod( "set" + property.name(), type ); if( setter != null ) { this.implementedMethods.add( setter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, setter.getName(), Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( type ) } ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( ValueProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( Value.class ), new Type[] { Type.getType( ValueProperty.class ) } ) ); mv.visitVarInsn( ALOAD, 1 ); mv.visitMethodInsn ( INVOKEVIRTUAL, Type.getInternalName( Value.class ), "write", Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( Object.class ) } ) ); mv.visitInsn( RETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } private void processTransientProperty( final ClassWriter cw, final TransientProperty property ) { final String propertyFieldName = findPropertyField( property ).getName(); final Class<?> propertyTypeClass = property.getTypeClass(); final Method getter = findMethod( "get" + property.name() ); if( getter != null ) { this.implementedMethods.add( getter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, getter.getName(), Type.getMethodDescriptor( Type.getType( Transient.class ), new Type[ 0 ] ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( TransientProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( Transient.class ), new Type[] { Type.getType( TransientProperty.class ) } ) ); mv.visitInsn( ARETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } final Method setter = findMethod( "set" + property.name(), propertyTypeClass ); if( setter != null ) { this.implementedMethods.add( setter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, setter.getName(), Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( propertyTypeClass ) } ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( TransientProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( Transient.class ), new Type[] { Type.getType( TransientProperty.class ) } ) ); mv.visitVarInsn( ALOAD, 1 ); mv.visitMethodInsn ( INVOKEVIRTUAL, Type.getInternalName( Transient.class ), "write", Type.getMethodDescriptor( Type.VOID_TYPE, new Type[] { Type.getType( Object.class ) } ) ); mv.visitInsn( RETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } private void processListProperty( final ClassWriter cw, final ListProperty property ) { final String propertyFieldName = findPropertyField( property ).getName(); final Method getter = findMethod( "get" + property.name() ); if( getter != null ) { this.implementedMethods.add( getter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, getter.getName(), Type.getMethodDescriptor( Type.getType( ElementList.class ), new Type[ 0 ] ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( ListProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( ElementList.class ), new Type[] { Type.getType( ListProperty.class ) } ) ); mv.visitInsn( ARETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } private void processElementProperty( final ClassWriter cw, final ElementProperty property ) { final String propertyFieldName = findPropertyField( property ).getName(); final Method getter = findMethod( "get" + property.name() ); if( getter != null ) { this.implementedMethods.add( getter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, getter.getName(), Type.getMethodDescriptor( Type.getType( ElementHandle.class ), new Type[ 0 ] ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( ElementProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( ElementHandle.class ), new Type[] { Type.getType( ElementProperty.class ) } ) ); mv.visitInsn( ARETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } private void processImpliedElementProperty( final ClassWriter cw, final ImpliedElementProperty property ) { final String propertyFieldName = findPropertyField( property ).getName(); final Class<?> propertyTypeClass = property.getTypeClass(); final Method getter = findMethod( "get" + property.name() ); if( getter != null ) { this.implementedMethods.add( getter ); final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, getter.getName(), Type.getMethodDescriptor( Type.getType( propertyTypeClass ), new Type[ 0 ] ), null, null ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitFieldInsn ( GETSTATIC, this.typeInterfaceClassInternalName, propertyFieldName, Type.getDescriptor( ImpliedElementProperty.class ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "property", Type.getMethodDescriptor( Type.getType( ElementHandle.class ), new Type[] { Type.getType( ElementProperty.class ) } ) ); mv.visitMethodInsn ( INVOKEVIRTUAL, Type.getInternalName( ElementHandle.class ), "content", Type.getMethodDescriptor( Type.getType( Element.class ), new Type[ 0 ] ) ); mv.visitTypeInsn( CHECKCAST, Type.getInternalName( propertyTypeClass ) ); mv.visitInsn( ARETURN ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } private void processDelegatedMethods( final ClassWriter cw ) { for( Method method : this.typeInterfaceClass.getMethods() ) { final DelegateImplementation delegateImplementationAnnotation = method.getAnnotation( DelegateImplementation.class ); if( ! this.implementedMethods.contains( method ) && delegateImplementationAnnotation != null ) { this.implementedMethods.add( method ); final Class<?>[] exceptionClasses = method.getExceptionTypes(); final String[] exceptionTypeNames = new String[ exceptionClasses.length ]; for( int i = 0, n = exceptionClasses.length; i < n; i++ ) { exceptionTypeNames[ i ] = Type.getInternalName( exceptionClasses[ i ] ); } final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, method.getName(), Type.getMethodDescriptor( method ), null, exceptionTypeNames ); mv.visitCode(); mv.visitVarInsn( ALOAD, 0 ); mv.visitMethodInsn ( INVOKEVIRTUAL, this.typeImplClassInternalName, "assertNotDisposed", "()V" ); final Type[] methodParameterTypes = Type.getArgumentTypes( method ); final Type[] delegateParameterTypes = new Type[ methodParameterTypes.length + 1 ]; mv.visitVarInsn( ALOAD, 0 ); delegateParameterTypes[ 0 ] = Type.getType( method.getDeclaringClass() ); for( int i = 0, j = 1, n = methodParameterTypes.length; i < n; i++, j++ ) { final Type methodParameterType = methodParameterTypes[ i ]; mv.visitVarInsn( methodParameterType.getOpcode( ILOAD ), j ); delegateParameterTypes[ j ] = methodParameterType; } mv.visitMethodInsn ( INVOKESTATIC, Type.getInternalName( delegateImplementationAnnotation.value() ), method.getName(), Type.getMethodDescriptor( Type.getReturnType( method ), delegateParameterTypes ) ); mv.visitInsn( Type.getReturnType( method ).getOpcode( IRETURN ) ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } } private void processUnimplementedMethods( final ClassWriter cw ) { for( Method method : this.typeInterfaceClass.getMethods() ) { final Class<?> cl = method.getDeclaringClass(); if( ! this.implementedMethods.contains( method ) && cl != Element.class && cl != Observable.class && cl != Disposable.class && cl != Object.class ) { final MethodVisitor mv = cw.visitMethod ( ACC_PUBLIC, method.getName(), Type.getMethodDescriptor( method ), null, null ); mv.visitCode(); mv.visitTypeInsn( NEW, Type.getInternalName( UnsupportedOperationException.class ) ); mv.visitInsn( DUP ); mv.visitMethodInsn( INVOKESPECIAL, Type.getInternalName( UnsupportedOperationException.class ), "<init>", "()V"); mv.visitInsn( ATHROW ); mv.visitMaxs( 0, 0 ); mv.visitEnd(); } } } private Method findMethod( final String methodName, final Class<?>... paramTypes ) { for( Method method : this.typeInterfaceClass.getMethods() ) { if( method.getName().equalsIgnoreCase( methodName ) ) { final Class<?>[] methodParamTypes = method.getParameterTypes(); if( methodParamTypes.length == paramTypes.length ) { boolean paramsMatch = true; for( int i = 0, n = paramTypes.length; i < n; i++ ) { if( methodParamTypes[ i ] != paramTypes[ i ] ) { paramsMatch = false; break; } } if( paramsMatch ) { return method; } } } } return null; } private Field findPropertyField( final PropertyDef property ) { for( Field field : this.typeInterfaceClass.getFields() ) { try { if( field.get( null ) == property ) { return field; } } catch( IllegalAccessException e ) { throw new IllegalStateException( e ); } } return null; } }