<commit_before>package rt.plugin.service.an

import java.lang.annotation.Target
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.CodeGenerationContext
import rt.plugin.config.PluginConfig
import rt.plugin.config.PluginConfigFactory
import rt.plugin.config.PluginEntry
import java.util.List
import rt.pipeline.IComponent
import rt.pipeline.pipe.PipeContext
import rt.pipeline.IMessageBus.Message
import org.eclipse.xtend.lib.macro.ValidationContext
import rt.plugin.service.IServiceClientFactory
import rt.plugin.service.ServiceClient
import java.util.LinkedList
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference
import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration
import org.eclipse.xtend.lib.macro.declaration.MethodDeclaration

@Target(TYPE)
@Active(ServiceProcessor)
annotation Service {
	Class<?> value = Void
}

class ServiceProcessor extends AbstractClassProcessor {
	
	override doValidate(ClassDeclaration clazz, extension ValidationContext ctx) {
		//TODO: verify if methods have return types, inferred not working!
	}
	
	def methSignature(MethodDeclaration meth) {
		'''«meth.returnType» «meth.simpleName»(«FOR param: meth.parameters SEPARATOR ','»«param.type»«ENDFOR»)'''.toString
	}
	
	override doTransform(MutableClassDeclaration clazz, extension TransformationContext ctx) {
		clazz.extendedClass = IComponent.newTypeReference
		
		//validate interface implementation before changes...
		val annoRef = clazz.findAnnotation(Service.findTypeGlobally)
		val srvInterface = annoRef.getClassValue('value')
		if (srvInterface != Void.newTypeReference) {
			val srvInterfaceMethods = srvInterface.declaredResolvedMethods.map[ declaration ]
			srvInterfaceMethods.forEach[ interMeth |
				if (!clazz.declaredMethods.exists[ methSignature == interMeth.methSignature ]) {
					clazz.addError('No implementation for method: ' + interMeth.methSignature)
				}
			]
		}
		
		
		val switchCases = new LinkedList<String>
		for (MutableMethodDeclaration meth: clazz.declaredMethods) {
			val annoPublicRef = meth.findAnnotation(Public.findTypeGlobally)
			if (annoPublicRef != null) {
				val isNotification = annoPublicRef.getBooleanValue('notif')
				
				//make a copy because the parameters will be changed
				val methParameters = meth.parameters.map[it as MutableParameterDeclaration].toList
				
				val ctxArgs = meth.getContextArgs(ctx)
				ctxArgs.forEach[
					meth.addParameter(getStringValue('name'), getClassValue('proxy'))
				]
				
				val retType = meth.returnType.simpleName
				val hasIntervalSimbol = methParameters.size != 0 && ctxArgs.size != 0
				switchCases.add('''
					case "«meth.simpleName»":
						«IF methParameters.size != 0»
						args = msg.args(«FOR param: methParameters SEPARATOR ','» «param.type.simpleName.replaceFirst('<.*>', '')».class«ENDFOR» );
						«ENDIF»
						«IF retType != 'void'»ret = «ENDIF»«meth.simpleName»(«methParameters.addMessageArgs»«IF hasIntervalSimbol»,«ENDIF»«meth.addContextArgs(ctx, ctxArgs)»);
						«IF !isNotification»
							«IF retType != 'void'»
								ctx.replyOK(ret);
							«ELSE»
								ctx.replyOK();
							«ENDIF»
						«ENDIF»
						break;
				''')
			}
		}
		
		clazz.addMethod('apply')[
			addParameter('ctx', PipeContext.newTypeReference)
			
			body = '''
				«ServiceClient» client = null;
				
				final IServiceClientFactory clientFactory = ctx.object(«IServiceClientFactory».class);
				if (clientFactory != null)
					client = clientFactory.getServiceClient();
				
				final «Message» msg = ctx.getMessage();
				«List»<Object> args = null;
				Object ret = null;
				
				try {
					switch(msg.cmd) {
						«FOR cas: switchCases»
							«cas»
						«ENDFOR»
						default:
							ctx.replyError(new RuntimeException("No public method: " + msg.cmd));
							break;
					}
				} catch(«Exception.name» ex) {
					ex.printStackTrace();
					ctx.replyError(ex);
				}
			'''
		]
	}
	
	override doGenerateCode(ClassDeclaration clazz, extension CodeGenerationContext context) {
		val filePath = clazz.compilationUnit.filePath
		val file = filePath.projectFolder.append('/src/main/resources/plugin-config.xml')
		val factory = new PluginConfigFactory
		
		// read plugin-config.xml
		val config = if (file.exists) factory.readFrom(file.contentsAsStream) else new PluginConfig
		config.cleanVoidEntries
		
		// change config
		config.addEntry(new PluginEntry => [
			type = 'srv'
			ref = clazz.qualifiedName
		])
		
		// write plugin-config.xml
		file.contents = factory.transform(config)
	}
	
	def getContextArgs(MutableMethodDeclaration meth, extension TransformationContext ctx) {
		val ctxArgs = new LinkedList<AnnotationReference>
		
		val annoProxyRef = meth.findAnnotation(Proxy.findTypeGlobally)
		if (annoProxyRef != null)
			ctxArgs.add(annoProxyRef)
		
		val annoProxysRef = meth.findAnnotation(Proxies.findTypeGlobally)<commit_msg>support for multiple Proxy not inside Proxies<commit_after>package rt.plugin.service.an

import java.lang.annotation.Target
import java.util.LinkedList
import java.util.List
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.CodeGenerationContext
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.ValidationContext
import org.eclipse.xtend.lib.macro.declaration.AnnotationReference
import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MethodDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration
import org.eclipse.xtend.lib.macro.declaration.MutableParameterDeclaration
import rt.pipeline.IComponent
import rt.pipeline.IMessageBus.Message
import rt.pipeline.pipe.PipeContext
import rt.plugin.config.PluginConfig
import rt.plugin.config.PluginConfigFactory
import rt.plugin.config.PluginEntry
import rt.plugin.service.IServiceClientFactory
import rt.plugin.service.ServiceClient

@Target(TYPE)
@Active(ServiceProcessor)
annotation Service {
	Class<?> value = Void
}

class ServiceProcessor extends AbstractClassProcessor {
	
	override doValidate(ClassDeclaration clazz, extension ValidationContext ctx) {
		//TODO: verify if methods have return types, inferred not working!
	}
	
	def methSignature(MethodDeclaration meth) {
		'''«meth.returnType» «meth.simpleName»(«FOR param: meth.parameters SEPARATOR ','»«param.type»«ENDFOR»)'''.toString
	}
	
	override doTransform(MutableClassDeclaration clazz, extension TransformationContext ctx) {
		clazz.extendedClass = IComponent.newTypeReference
		
		//validate interface implementation before changes...
		val annoRef = clazz.findAnnotation(Service.findTypeGlobally)
		val srvInterface = annoRef.getClassValue('value')
		if (srvInterface != Void.newTypeReference) {
			val srvInterfaceMethods = srvInterface.declaredResolvedMethods.map[ declaration ]
			srvInterfaceMethods.forEach[ interMeth |
				if (!clazz.declaredMethods.exists[ methSignature == interMeth.methSignature ]) {
					clazz.addError('No implementation for method: ' + interMeth.methSignature)
				}
			]
		}
		
		
		val switchCases = new LinkedList<String>
		for (MutableMethodDeclaration meth: clazz.declaredMethods) {
			val annoPublicRef = meth.findAnnotation(Public.findTypeGlobally)
			if (annoPublicRef != null) {
				val isNotification = annoPublicRef.getBooleanValue('notif')
				
				//make a copy because the parameters will be changed
				val methParameters = meth.parameters.map[it as MutableParameterDeclaration].toList
				
				val ctxArgs = meth.getContextArgs(ctx)
				ctxArgs.forEach[
					meth.addParameter(getStringValue('name'), getClassValue('proxy'))
				]
				
				val retType = meth.returnType.simpleName
				val hasIntervalSimbol = methParameters.size != 0 && ctxArgs.size != 0
				switchCases.add('''
					case "«meth.simpleName»":
						«IF methParameters.size != 0»
						args = msg.args(«FOR param: methParameters SEPARATOR ','» «param.type.simpleName.replaceFirst('<.*>', '')».class«ENDFOR» );
						«ENDIF»
						«IF retType != 'void'»ret = «ENDIF»«meth.simpleName»(«methParameters.addMessageArgs»«IF hasIntervalSimbol»,«ENDIF»«meth.addContextArgs(ctx, ctxArgs)»);
						«IF !isNotification»
							«IF retType != 'void'»
								ctx.replyOK(ret);
							«ELSE»
								ctx.replyOK();
							«ENDIF»
						«ENDIF»
						break;
				''')
			}
		}
		
		clazz.addMethod('apply')[
			addParameter('ctx', PipeContext.newTypeReference)
			
			body = '''
				«ServiceClient» client = null;
				
				final IServiceClientFactory clientFactory = ctx.object(«IServiceClientFactory».class);
				if (clientFactory != null)
					client = clientFactory.getServiceClient();
				
				final «Message» msg = ctx.getMessage();
				«List»<Object> args = null;
				Object ret = null;
				
				try {
					switch(msg.cmd) {
						«FOR cas: switchCases»
							«cas»
						«ENDFOR»
						default:
							ctx.replyError(new RuntimeException("No public method: " + msg.cmd));
							break;
					}
				} catch(«Exception.name» ex) {
					ex.printStackTrace();
					ctx.replyError(ex);
				}
			'''
		]
	}
	
	override doGenerateCode(ClassDeclaration clazz, extension CodeGenerationContext context) {
		val filePath = clazz.compilationUnit.filePath
		val file = filePath.projectFolder.append('/src/main/resources/plugin-config.xml')
		val factory = new PluginConfigFactory
		
		// read plugin-config.xml
		val config = if (file.exists) factory.readFrom(file.contentsAsStream) else new PluginConfig
		config.cleanVoidEntries
		
		// change config
		config.addEntry(new PluginEntry => [
			type = 'srv'
			ref = clazz.qualifiedName
		])
		
		// write plugin-config.xml
		file.contents = factory.transform(config)
	}
	
	def getContextArgs(MutableMethodDeclaration meth, extension TransformationContext ctx) {
		val ctxArgs = new LinkedList<AnnotationReference>
		
		val annoProxyRef = meth.annotations.filter[ annotationTypeDeclaration ==  Proxy.findTypeGlobally ]
		ctxArgs.addAll(annoProxyRef)
		
		val annoProxysRef = meth.findAnnotation(Proxies.findTypeGlobally)
{'fraction_non_alphanumeric': 0.10700649102710959, 'fraction_numerical': 0.000572737686139748, 'mean_word_length': 4.2228315054835495, 'pattern_counts': {'":': 2, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6541559', 'n_tokens_mistral': 3743, 'n_tokens_neox': 3057, 'n_words': 756}