Added JavaMethodReceiverService.

Change-Id: If692b4e05e89f6957a318b37daa3f8d08a444e86
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/ServiceUtils.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/ServiceUtils.java
index e78df92..606d0f0 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/ServiceUtils.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/ServiceUtils.java
@@ -18,6 +18,7 @@
 import java.util.Set;
 
 import org.eclipse.acceleo.query.runtime.impl.EOperationService;
+import org.eclipse.acceleo.query.runtime.impl.JavaMethodReceiverService;
 import org.eclipse.acceleo.query.runtime.impl.JavaMethodService;
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EClassifier;
@@ -76,6 +77,28 @@
 	}
 
 	/**
+	 * Gets the {@link Set} of {@link IService} for the given {@link Class} with receiver as first parameter.
+	 * 
+	 * @param queryEnvironment
+	 *            the {@link IReadOnlyQueryEnvironment}
+	 * @param cls
+	 *            the {@link Class}
+	 * @return the {@link Set} of {@link IService} for the given {@link Class} with receiver as first
+	 *         parameter
+	 */
+	public static Set<IService> getReceiverServices(IReadOnlyQueryEnvironment queryEnvironment, Class<?> cls) {
+		final Set<IService> result = new LinkedHashSet<IService>();
+
+		for (Method method : cls.getMethods()) {
+			if (isReveiverServiceMethod(method)) {
+				result.add(new JavaMethodReceiverService(method));
+			}
+		}
+
+		return result;
+	}
+
+	/**
 	 * Gets the {@link Set} of {@link IService} for the given {@link Object instance}.
 	 * 
 	 * @param queryEnvironment
@@ -150,6 +173,22 @@
 	}
 
 	/**
+	 * Tells if a given {@link Method} is considered as a {@link IService} with receiver as first parameter.
+	 * {@link Object} methods are not considered.
+	 * 
+	 * @param method
+	 *            the {@link Method} to check
+	 * @return <code>true</code> if a given {@link Method} is considered as a {@link IService} with receiver
+	 *         as first parameter, <code>false</code> otherwise
+	 */
+	public static boolean isReveiverServiceMethod(Method method) {
+		// We do not register java.lang.Object method as
+		// having an expression calling the 'wait' or the notify service
+		// could yield problems that are difficult to track down.
+		return method.getDeclaringClass() != Object.class;
+	}
+
+	/**
 	 * Registers a {@link Set} of {@link IService} to the given {@link IQueryEnvironment}.
 	 * 
 	 * @param queryEnvironment
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodReceiverService.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodReceiverService.java
new file mode 100644
index 0000000..faad4a9
--- /dev/null
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodReceiverService.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Obeo.
+ * 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:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.acceleo.query.runtime.impl;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment;
+import org.eclipse.acceleo.query.validation.type.IType;
+
+/**
+ * Implementation of an {@link org.eclipse.acceleo.query.runtime.IService IService} for {@link Method} with
+ * receiver as first parameter.
+ * 
+ * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
+ */
+public class JavaMethodReceiverService extends JavaMethodService {
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param method
+	 *            the method that realizes the service
+	 */
+	public JavaMethodReceiverService(Method method) {
+		super(method, null);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.acceleo.query.runtime.IService#getShortSignature()
+	 */
+	@Override
+	public String getShortSignature() {
+		final Class<?>[] parameters = getParameterTypes();
+		return serviceShortSignature(parameters);
+	}
+
+	/**
+	 * Gets the parameter types including the receiver type.
+	 * 
+	 * @return the parameter types including the receiver type
+	 */
+	private Class<?>[] getParameterTypes() {
+		final Class<?>[] parameters = new Class<?>[getMethod().getParameterTypes().length + 1];
+		int i = 0;
+		parameters[i++] = getMethod().getDeclaringClass();
+		for (Class<?> cls : getMethod().getParameterTypes()) {
+			parameters[i++] = cls;
+		}
+		return parameters;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.acceleo.query.runtime.IService#getLongSignature()
+	 */
+	@Override
+	public String getLongSignature() {
+		return super.getLongSignature() + " (receiver as first parameter)";
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.acceleo.query.runtime.IService#getParameterTypes(org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment)
+	 */
+	@Override
+	public List<IType> getParameterTypes(IReadOnlyQueryEnvironment queryEnvironment) {
+		final List<IType> result = new ArrayList<IType>();
+
+		for (Class<?> cls : getParameterTypes()) {
+			result.add(getClassType(queryEnvironment, cls));
+		}
+
+		return result;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.acceleo.query.runtime.IService#getNumberOfParameters()
+	 */
+	@Override
+	public int getNumberOfParameters() {
+		return super.getNumberOfParameters() + 1;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.acceleo.query.runtime.impl.AbstractService#internalInvoke(java.lang.Object[])
+	 */
+	@Override
+	protected Object internalInvoke(Object[] arguments) throws Exception {
+		final Object receiver = arguments[0];
+		final Object[] newArguments = new Object[arguments.length - 1];
+		for (int i = 0; i < newArguments.length; i++) {
+			newArguments[i] = arguments[i + 1];
+		}
+
+		return getMethod().invoke(receiver, newArguments);
+	}
+
+}
diff --git a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodService.java b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodService.java
index 5b11d50..460ecdd 100644
--- a/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodService.java
+++ b/query/plugins/org.eclipse.acceleo.query/src/org/eclipse/acceleo/query/runtime/impl/JavaMethodService.java
@@ -35,8 +35,7 @@
 import org.eclipse.emf.ecore.EObject;
 
 /**
- * Abstract implementation of an {@link org.eclipse.acceleo.query.runtime.IService IService} for
- * {@link Method}.
+ * Implementation of an {@link org.eclipse.acceleo.query.runtime.IService IService} for {@link Method}.
  * 
  * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
  */
diff --git a/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/EvaluationTest.java b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/EvaluationTest.java
index cfcf9a3..4dbbc8c 100644
--- a/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/EvaluationTest.java
+++ b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/EvaluationTest.java
@@ -38,6 +38,7 @@
 import org.eclipse.acceleo.query.tests.nestedpackages.root.child.grand_child.Grand_childFactory;
 import org.eclipse.acceleo.query.tests.nestedpackages.root.child.grand_child.Grand_childPackage;
 import org.eclipse.acceleo.query.tests.services.EObjectServices;
+import org.eclipse.acceleo.query.tests.services.ReceiverServices;
 import org.eclipse.emf.common.util.Diagnostic;
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EPackage;
@@ -641,4 +642,27 @@
 
 		assertEquals("\u1F61C \u1F62D \u1F63D \u1F1EB\u1F1F7", result.getResult());
 	}
+
+	@Test
+	public void javaMethodReceiverServiceNoArg() {
+		Map<String, Object> variables = new HashMap<String, Object>();
+		variables.put("self", new ReceiverServices());
+		ServiceUtils.registerServices(queryEnvironment, ServiceUtils.getReceiverServices(queryEnvironment,
+				ReceiverServices.class));
+		EvaluationResult result = engine.eval(builder.build("self.noArg()"), variables);
+
+		assertEquals("noArgResult", result.getResult());
+	}
+
+	@Test
+	public void javaMethodReceiverServiceArg() {
+		Map<String, Object> variables = new HashMap<String, Object>();
+		variables.put("self", new ReceiverServices());
+		ServiceUtils.registerServices(queryEnvironment, ServiceUtils.getReceiverServices(queryEnvironment,
+				ReceiverServices.class));
+		EvaluationResult result = engine.eval(builder.build("self.arg('arg')"), variables);
+
+		assertEquals("argResultarg", result.getResult());
+	}
+
 }
diff --git a/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/ValidationTest.java b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/ValidationTest.java
index 7418577..edbcc7c 100644
--- a/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/ValidationTest.java
+++ b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/parser/tests/ValidationTest.java
@@ -33,6 +33,7 @@
 import org.eclipse.acceleo.query.runtime.impl.QueryValidationEngine;
 import org.eclipse.acceleo.query.tests.anydsl.AnydslPackage;
 import org.eclipse.acceleo.query.tests.services.EObjectServices;
+import org.eclipse.acceleo.query.tests.services.ReceiverServices;
 import org.eclipse.acceleo.query.validation.type.ClassType;
 import org.eclipse.acceleo.query.validation.type.EClassifierLiteralType;
 import org.eclipse.acceleo.query.validation.type.EClassifierSetLiteralType;
@@ -1400,6 +1401,46 @@
 		assertEquals(0, validationResult.getMessages().size());
 	}
 
+	@Test
+	public void javaMethodReceiverServiceNoArg() {
+		Set<IType> selfTypes = new LinkedHashSet<IType>();
+		selfTypes.add(new ClassType(queryEnvironment, ReceiverServices.class));
+		variableTypes.put("self", selfTypes);
+		ServiceUtils.registerServices(queryEnvironment, ServiceUtils.getReceiverServices(queryEnvironment,
+				ReceiverServices.class));
+
+		final IValidationResult validationResult = engine.validate("self.noArg()", variableTypes);
+		final Expression ast = validationResult.getAstResult().getAst();
+		final Set<IType> possibleTypes = validationResult.getPossibleTypes(ast);
+
+		assertEquals(1, possibleTypes.size());
+		final Iterator<IType> it = possibleTypes.iterator();
+		IType possibleType = it.next();
+		assertTrue(possibleType instanceof ClassType);
+		assertEquals(String.class, possibleType.getType());
+		assertEquals(0, validationResult.getMessages().size());
+	}
+
+	@Test
+	public void javaMethodReceiverServiceArg() {
+		Set<IType> selfTypes = new LinkedHashSet<IType>();
+		selfTypes.add(new ClassType(queryEnvironment, ReceiverServices.class));
+		variableTypes.put("self", selfTypes);
+		ServiceUtils.registerServices(queryEnvironment, ServiceUtils.getReceiverServices(queryEnvironment,
+				ReceiverServices.class));
+
+		final IValidationResult validationResult = engine.validate("self.arg('arg')", variableTypes);
+		final Expression ast = validationResult.getAstResult().getAst();
+		final Set<IType> possibleTypes = validationResult.getPossibleTypes(ast);
+
+		assertEquals(1, possibleTypes.size());
+		final Iterator<IType> it = possibleTypes.iterator();
+		IType possibleType = it.next();
+		assertTrue(possibleType instanceof ClassType);
+		assertEquals(String.class, possibleType.getType());
+		assertEquals(0, validationResult.getMessages().size());
+	}
+
 	/**
 	 * Asserts the given {@link IValidationMessage} against expected values.
 	 * 
diff --git a/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/tests/services/ReceiverServices.java b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/tests/services/ReceiverServices.java
new file mode 100644
index 0000000..e4d9b28
--- /dev/null
+++ b/query/tests/org.eclipse.acceleo.query.tests/src/org/eclipse/acceleo/query/tests/services/ReceiverServices.java
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Obeo.
+ * 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:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.acceleo.query.tests.services;
+
+/**
+ * @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
+ */
+public class ReceiverServices {
+
+	private final String noArgResult = "noArgResult";
+
+	private final String argResult = "argResult";
+
+	public String noArg() {
+		return noArgResult;
+	}
+
+	public String arg(String arg) {
+		return argResult + arg;
+	}
+
+}