/* * <copyright> * * Copyright (c) 2005-2006 Sven Efftinge and others. * 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: * Sven Efftinge - Initial API and implementation * * </copyright> */ package org.eclipse.gmf.internal.xpand.xtend.ast; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.gmf.internal.xpand.expression.AnalysationIssue; import org.eclipse.gmf.internal.xpand.expression.EvaluationException; import org.eclipse.gmf.internal.xpand.expression.ExecutionContext; import org.eclipse.gmf.internal.xpand.expression.Variable; import org.eclipse.gmf.internal.xpand.expression.ast.DeclaredParameter; import org.eclipse.gmf.internal.xpand.expression.ast.Identifier; public class JavaExtensionStatement extends Extension { protected final Identifier javaType; protected final Identifier javaMethod; protected final List<Identifier> javaParamTypes; protected final Identifier instanceSlot; public JavaExtensionStatement(final int start, final int end, final int line, final int startOffset, final int endOffset, final Identifier name, final List<DeclaredParameter> formalParameters, final Identifier returnType, final Identifier defaultImplementation, final Identifier javaMethod, final List<Identifier> javaParamTypes, final boolean cached, final boolean isPrivate, final Identifier instanceSlot) { super(start, end, line, startOffset, endOffset, name, returnType, formalParameters, cached, isPrivate); javaType = defaultImplementation; this.javaMethod = javaMethod; this.javaParamTypes = javaParamTypes; this.instanceSlot = instanceSlot; } public Identifier getJavaType() { return javaType; } public Identifier getJavaMethod() { return javaMethod; } public List<Identifier> getJavaParameterTypes() { return javaParamTypes; } @Override public Object evaluateInternal(final Object[] parameters, final ExecutionContext ctx) { final HashSet<AnalysationIssue> issues = new HashSet<AnalysationIssue>(); try { final Method method = getJavaMethod(ctx, issues); if (method == null) { final StringBuffer b = new StringBuffer(); for (AnalysationIssue element : issues) { b.append(element.toString()).append("\n"); } throw new EvaluationException(javaMethodToString() + " not found, problems were: \n" + b, this); } transformParameters(method.getParameterTypes(), parameters); if (Modifier.isStatic(method.getModifiers())) { return method.invoke(null, parameters); } else { if (instanceSlot == null) { throw new EvaluationException("Non-static method may be invoked only when slot with instance object is specified", this); } Variable variable = ctx.getGlobalVariable(instanceSlot.getValue()); if (variable == null || variable.getValue() == null) { throw new EvaluationException("The method '" + javaMethodToString() + "' is not static in " + javaType.getValue() + ", and there's no global variable '" + instanceSlot + "' to obtain instance from", this); } if (!method.getDeclaringClass().isInstance(variable.getValue())) { throw new EvaluationException("Instance available in global vars as '" + instanceSlot + "' is not compatible with " + javaType.getValue(), this); } return method.invoke(variable.getValue(), parameters); } } catch (final InvocationTargetException ite) { throw new RuntimeException(ite.getCause()); } catch (final EvaluationException e) { throw e; } catch (final Exception e) { throw new EvaluationException(e, this); } } private void transformParameters(Class[] paramTypes, Object[] parameters) { assert paramTypes.length == parameters.length; for (int i = 0; i < parameters.length; i++) { // XXX no support for arrays of arrays if (parameters[i] instanceof List && paramTypes[i].isArray()) { List<?> list = (List<?>) parameters[i]; parameters[i] = list.toArray((Object[]) Array.newInstance(paramTypes[i].getComponentType(), list.size())); } } } private String javaMethodToString() { final StringBuffer buff = new StringBuffer(); for (final Iterator<Identifier> iter = javaParamTypes.iterator(); iter.hasNext();) { buff.append(iter.next()); if (iter.hasNext()) { buff.append(","); } } return javaType + "." + javaMethod + "(" + buff + ")"; } private Method getJavaMethod(final ExecutionContext ctx, final Set<AnalysationIssue> issues) { try { Class clazz = null; clazz = ctx.loadClass(javaType.getValue()); if (clazz == null) { issues.add(new AnalysationIssue(AnalysationIssue.Type.JAVA_TYPE_NOT_FOUND, javaType.getValue(), javaType, true)); return null; } final Class[] paramTypes = new Class[javaParamTypes.size()]; for (int i = 0, x = javaParamTypes.size(); i < x; i++) { final Identifier javaParamType = javaParamTypes.get(i); String value = javaParamType.getValue(); boolean isList = value.endsWith(".List"); if (isList) { value = value.substring(0, value.length() - 5); } paramTypes[i] = ctx.loadClass(value); if (paramTypes[i] == null) { issues.add(new AnalysationIssue(AnalysationIssue.Type.JAVA_TYPE_NOT_FOUND, value, javaParamType, true)); return null; } if (isList) { // XXX unless I find out Class.forName alternative that is // capable of resolving "String[]" paramTypes[i] = Array.newInstance(paramTypes[i], 0).getClass(); } } final Method m = clazz.getMethod(javaMethod.getValue(), paramTypes); if (instanceSlot == null && !Modifier.isStatic(m.getModifiers())) { issues.add(new AnalysationIssue(AnalysationIssue.Type.FEATURE_NOT_FOUND, javaMethod.getValue() + " must be static (unless slot to get instance from is specified)!", javaMethod)); } if (!Modifier.isPublic(m.getModifiers())) { issues.add(new AnalysationIssue(AnalysationIssue.Type.FEATURE_NOT_FOUND, javaMethod.getValue() + " must be public!", javaMethod)); } return m; } catch (final NoSuchMethodException e) { issues.add(new AnalysationIssue(AnalysationIssue.Type.FEATURE_NOT_FOUND, javaMethod.getValue(), javaMethod)); } return null; } @Override public void analyzeInternal(final ExecutionContext ctx, final Set<AnalysationIssue> issues) { if (returnType == null) { issues.add(new AnalysationIssue(AnalysationIssue.Type.SYNTAX_ERROR, "A return type must be specified for java extensions!", this)); } getJavaMethod(ctx, issues); } @Override protected EClassifier internalGetReturnType(final EClassifier[] parameters, final ExecutionContext ctx, final Set<AnalysationIssue> issues) { if (returnType == null) { issues.add(new AnalysationIssue(AnalysationIssue.Type.SYNTAX_ERROR, "A return type must be specified for java extensions!", this)); return null; } else { return ctx.getTypeForName(returnType.getValue()); } } }