/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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
*
* Contributor:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.jsf.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.jboss.tools.common.el.core.ELCorePlugin;
import org.jboss.tools.common.el.core.model.ELExpression;
import org.jboss.tools.common.el.core.model.ELInvocationExpression;
import org.jboss.tools.common.el.core.resolver.ELContext;
import org.jboss.tools.common.el.core.resolver.ELResolution;
import org.jboss.tools.common.el.core.resolver.ELResolutionImpl;
import org.jboss.tools.common.el.core.resolver.TypeInfoCollector;
import org.jboss.tools.common.el.core.resolver.TypeInfoCollector.ArtificialTypeInfo;
import org.jboss.tools.common.model.util.EclipseJavaUtil;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.jsf.JSFModelPlugin;
import org.jboss.tools.jst.web.kb.PageContextFactory;
import org.jboss.tools.jst.web.kb.internal.XmlContextImpl;
import org.jboss.tools.jst.web.kb.taglib.IELFunction;
import org.jboss.tools.jst.web.kb.taglib.IFunctionLibrary;
import org.jboss.tools.jst.web.kb.taglib.INameSpace;
import org.jboss.tools.jst.web.kb.taglib.ITagLibrary;
import org.jboss.tools.jst.web.kb.taglib.TagLibraryManager;
public class JSFFuncsELCompletionEngine extends JSFELCompletionEngine {
public JSFFuncsELCompletionEngine() {}
/*
* (non-Javadoc)
* @see org.jboss.tools.jst.web.kb.el.AbstractELCompletionEngine#getMemberInfoByVariable(org.jboss.tools.jst.web.kb.el.AbstractELCompletionEngine.IVariable, boolean)
*/
@Override
protected TypeInfoCollector.MemberInfo getMemberInfoByVariable(IJSFVariable var, ELContext context, boolean onlyEqualNames, int offset) {
// Need to create artificial member info based on the Source Member type, but having only method named after the func's name
if (!(var instanceof Variable))
return null;
Variable variable = (Variable)var;
IType sourceMember = (IType)variable.getSourceMember();
if (variable.funcResolvedMethod == null)
return null;
TypeInfoCollector.MemberInfo result = null;
try {
result = new ArtificialTypeInfo(sourceMember,
variable.funcResolvedMethod,
variable.funcName);
} catch (JavaModelException e) {
ELCorePlugin.getPluginLog().logError(e);
}
return result;
}
@Override
public ELResolution resolve(ELContext context, ELExpression operand, int offset) {
if(operand.getText().indexOf(':')>0) {
return super.resolve(context, operand, offset);
} else {
return new ELResolutionImpl(operand);
}
}
/*
* (non-Javadoc)
* @see org.jboss.tools.common.el.core.ca.AbstractELCompletionEngine#resolveVariables(org.eclipse.core.resources.IFile, org.jboss.tools.common.el.core.model.ELInvocationExpression, boolean, boolean)
*/
@Override
public List<IJSFVariable> resolveVariables(IFile file, ELContext context,
ELInvocationExpression expr, boolean isFinal, boolean onlyEqualNames, int offset) {
return resolveVariablesInternal(file, expr, isFinal, onlyEqualNames, offset);
}
private List<IJSFVariable> resolveVariablesInternal(IFile file, ELInvocationExpression expr, boolean isFinal, boolean onlyEqualNames, int offset) {
ELContext context = PageContextFactory.createPageContext(file);
if (!(context instanceof XmlContextImpl)) {
return Collections.emptyList();
}
ITagLibrary[] libraries = TagLibraryManager.getLibraries(file.getProject());
if (libraries.length==0)
return Collections.emptyList();;
List<IJSFVariable> result = new ArrayList<IJSFVariable>();
String varName = expr.toString();
// AbstractELCompletionEngine sets up a current offset in beginning of each ELResolver method (if it has appropriate parameter)
// but if resolve variables is called (because it is public) from somewhere outside, then we'll use all the namespaces we could find
Map<String, List<INameSpace>> namespacesByOffset = ((XmlContextImpl)context).getNameSpaces(offset);
for (ITagLibrary l : libraries) {
if (l instanceof IFunctionLibrary) {
String uri = l.getURI();
Collection<INameSpace> namespaces = namespacesByOffset.get(uri);
if (namespaces != null) {
for (INameSpace ns : namespaces) {
String name = ns.getPrefix();
if(!isFinal || onlyEqualNames) {
if(!name.equals(varName)) continue;
}
if(!name.startsWith(varName)) continue;
if(varName.lastIndexOf('.') > name.length()) continue; //It is the java variable case
IELFunction[] functions = ((IFunctionLibrary)l).getFunctions();
if (functions == null) continue;
for (IELFunction f : functions) {
String funcClass = f.getFunctionClass();
String funcSignature = f.getFunctionSignature();
String funcName = f.getName();
Variable v = new Variable(name, file,funcName, funcClass, funcSignature);
result.add(v);
}
}
}
}
}
return result;
}
static class Variable implements IJSFVariable {
IFile f;
String name;
String funcName;
String funcClass;
String funcSignature;
IMethod funcResolvedMethod;
IType funcSourceMember;
public Variable(String name, IFile f, String funcName, String funcClass, String funcSignature) {
this.name = name;
this.f = f;
this.funcName = funcName;
this.funcClass = funcClass;
this.funcSignature = funcSignature;
this.funcResolvedMethod = null;
}
@Override
public String getName() {
return name;
}
public Collection<String> getKeys() {
List<String> result = new ArrayList<String>();
if (funcResolvedMethod != null)
return result;
if (f == null || f.getProject() == null)
return result;
int startParamIndex = funcSignature.indexOf('(');
int endParamIndex = funcSignature.indexOf(')');
if (startParamIndex == -1 || endParamIndex == -1 || startParamIndex > endParamIndex) {
return result;
}
String[] s1 = funcSignature.substring(0, startParamIndex).trim().split("\\s");
if(s1.length != 2) {
return result;
}
String funcRetType = s1[0];
String funcMethodName = s1[1];
if(funcMethodName.indexOf("<") > 0) {
funcMethodName = funcMethodName.substring(0, funcMethodName.indexOf("<"));
}
String paramsString = funcSignature.substring(startParamIndex + 1, endParamIndex);
String[] params = paramsString.length() == 0 ? new String[0] : paramsString.split(",");
funcSourceMember = EclipseResourceUtil.getValidType(f.getProject(), funcClass);
if (funcSourceMember == null) {
return result;
}
IType currentType = funcSourceMember;
try {
while (currentType != null) {
IMethod[] binMethods = currentType.getMethods();
if (binMethods != null) {
for (IMethod method : binMethods) {
if (method.isConstructor() || (method.getFlags() & Flags.AccStatic) == 0) {
continue;
}
String methodName = method.getElementName();
if(!funcMethodName.equals(methodName)) {
continue;
}
String methodReturnType = method.getReturnType();
String methodReturnTypeSimple = methodReturnType;
if (Signature.getTypeSignatureKind(methodReturnType) == Signature.BASE_TYPE_SIGNATURE) {
methodReturnType = Signature.toString(methodReturnType);
methodReturnTypeSimple = methodReturnType;
} else {
methodReturnType = EclipseJavaUtil.resolveTypeAsString(currentType, methodReturnType);
methodReturnTypeSimple = Signature.getSimpleName(methodReturnType);
}
if (!areTypesEqual(funcRetType, methodReturnType) && !areTypesEqual(funcRetType, methodReturnTypeSimple)) {
continue;
}
String[] methodParamTypes = method.getParameterTypes();
boolean paramsAreEqual = areParametersEqual(methodParamTypes, params, currentType, methodReturnTypeSimple);
if (!paramsAreEqual)
continue;
funcResolvedMethod = method;
result.add(funcName);
break;
}
}
currentType = TypeInfoCollector.getSuperclass(currentType);
}
} catch (JavaModelException e) {
JSFModelPlugin.getDefault().logError("An error occurred while retrieving methods for type '" + funcClass + "'", e);
}
return result;
}
@Override
public IMember getSourceMember() {
getKeys(); // Initialize source member
return funcSourceMember;
}
private boolean areParametersEqual(String[] methodParamTypes, String[] params,
IType currentType, String methodReturnTypeSimple) {
int paramTypesCount = methodParamTypes == null ? 0 : methodParamTypes.length;
int paramsCount = params == null ? 0 : params.length;
if (paramTypesCount != paramsCount) {
return false;
}
for (int i = 0; methodParamTypes != null && i < methodParamTypes.length; i++) {
String methodParamType = methodParamTypes[i];
String methodParamTypeSimple = methodParamType;
if (Signature.getTypeSignatureKind(methodParamType) == Signature.BASE_TYPE_SIGNATURE) {
methodParamType = Signature.toString(methodParamType);
methodReturnTypeSimple = methodParamType;
} else {
methodParamType = EclipseJavaUtil.resolveTypeAsString(currentType, methodParamType);
methodParamTypeSimple = Signature.getSimpleName(methodParamType);
}
if(params[i] != null) {
if(areTypesEqual(params[i], methodParamType)
|| areTypesEqual(params[i], methodParamTypeSimple)) {
continue;
}
}
return false;
}
return true;
}
private boolean areTypesEqual(String type1, String type2) {
if(type1 == null || type2 == null) {
return false;
}
type1 = toPrimitiveType(stripTypeName(type1.trim()));
type2 = toPrimitiveType(stripTypeName(type2.trim()));
return (type1.equals(type2));
}
private String stripTypeName(String type) {
if(type.endsWith(";")) {
return type.substring(1, type.length() - 1);
}
return type;
}
static Map<String, String> WRAPPER_TYPES = new HashMap<String, String>();
static {
WRAPPER_TYPES.put("java.lang.Boolean", "boolean");
WRAPPER_TYPES.put("java.lang.Byte", "byte");
WRAPPER_TYPES.put("java.lang.Character", "char");
WRAPPER_TYPES.put("java.lang.Double", "double");
WRAPPER_TYPES.put("java.lang.Float", "float");
WRAPPER_TYPES.put("java.lang.Integer", "int");
WRAPPER_TYPES.put("java.lang.Long", "long");
WRAPPER_TYPES.put("java.lang.Short", "short");
WRAPPER_TYPES.put("Boolean", "boolean");
WRAPPER_TYPES.put("Byte", "byte");
WRAPPER_TYPES.put("Character", "char");
WRAPPER_TYPES.put("Double", "double");
WRAPPER_TYPES.put("Float", "float");
WRAPPER_TYPES.put("Integer", "int");
WRAPPER_TYPES.put("Long", "long");
WRAPPER_TYPES.put("Short", "short");
}
private String toPrimitiveType(String type) {
return WRAPPER_TYPES.containsKey(type) ? WRAPPER_TYPES.get(type) : type;
}
}
}