net.jakubholy.jeeutils.jsfelcheck.validator.jsf12.MethodFakingFunctionMapper Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jakubholy.jeeutils.jsfelcheck.validator.jsf12;
import org.apache.el.parser.AstFunction;
import org.apache.el.parser.ELParser;
import org.apache.el.parser.Node;
import org.apache.el.parser.NodeVisitor;
import javax.el.FunctionMapper;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* Instead of resolving to the correct Method based on prefix, name, taglib URI and
* information in a taglib info file, this mapper just returns a suitable
* generic method taking the expected number of parameters.
*
* In the future I may implement/reuse a true function mapper.
*/
public class MethodFakingFunctionMapper extends FunctionMapper {
/**
* For each function node found in an EL, remember the number if its
* children (arity).
*/
private final class FunctionArityExtractingVisitor implements NodeVisitor {
private final Map functionArity = new HashMap();
@Override
public void visit(Node n) throws Exception {
if (n instanceof AstFunction) {
visitFunction((AstFunction) n);
}
}
private void visitFunction(AstFunction function) throws Exception {
String nodeFunctionQName = function.getPrefix() + ":" + function.getLocalName();
functionArity.put(nodeFunctionQName, function.jjtGetNumChildren());
}
public Map getFunctionArities() {
return functionArity;
}
}
private static final String DEFAULT_RESULT = "";
// CHECKSTYLE:OFF
static final Method[] FAKE_METHODS = new Method[] {
createFakeMethod(0)
, createFakeMethod(1)
, createFakeMethod(2)
, createFakeMethod(3)
, createFakeMethod(4)
, createFakeMethod(5)
};
// CHECKSTYLE:ON
private final Map functionToAritiesCache = new HashMap();
private final Set currentExpressionsFunctions = new TreeSet();
private String currentExpression;
private static Method createFakeMethod(int arity) {
Class>[] argumentTypes = new Class[arity];
Arrays.fill(argumentTypes, Object.class);
try {
return MethodFakingFunctionMapper.class.getDeclaredMethod(
"fakeMethod" + arity
, argumentTypes);
} catch (SecurityException e) {
throw new IllegalStateException(e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
}
// CHECKSTYLE:OFF
public static String fakeMethod0() { return DEFAULT_RESULT; }
public static String fakeMethod1(Object p1) { return DEFAULT_RESULT; }
public static String fakeMethod2(Object p1, Object p2) { return DEFAULT_RESULT; }
public static String fakeMethod3(Object p1, Object p2, Object p3) { return DEFAULT_RESULT; }
public static String fakeMethod4(Object p1, Object p2, Object p3, Object p4) { return DEFAULT_RESULT; }
public static String fakeMethod5(Object p1, Object p2, Object p3, Object p4, Object p5) { return DEFAULT_RESULT; }
// hopefully nobody uses method of more than 5 parameters
// CHECKSTYLE:ON
public Collection getLastExpressionsFunctionQNames() {
return currentExpressionsFunctions;
}
@Override
public Method resolveFunction(String prefix, String name) {
final String resolvedFunctionQName = prefix + ":" + name;
// FIXME The code below allows for a function name to have just one number of parameters, i.e. no f(1), f(1,2)
// What does the specification say?
Integer arity = functionToAritiesCache.get(resolvedFunctionQName);
if (arity == null) {
Map functionsAndAritiesInCurrentEL = extractFunctionArities();
arity = functionsAndAritiesInCurrentEL.get(resolvedFunctionQName);
functionToAritiesCache.putAll(functionsAndAritiesInCurrentEL);
}
if (arity == null) {
throw new IllegalStateException("Couldn't determine the arity of the function " + resolvedFunctionQName
+ " from the EL '" + getCurrentExpressionOrFail() + "' - not found in it.");
} else if (arity >= FAKE_METHODS.length) {
throw new IllegalArgumentException("Currently we only can fake methods with up to 5 parameters but "
+ resolvedFunctionQName + " has " + arity + ". This is really a bad practice anyway.");
} else {
currentExpressionsFunctions.add(resolvedFunctionQName);
return FAKE_METHODS[arity];
}
}
/**
* Extract all arities of all functions in the current EL expression.
*/
private Map extractFunctionArities() {
Node parsedEl = ELParser.parse(getCurrentExpressionOrFail());
try {
FunctionArityExtractingVisitor arityExtractingVisitor = new FunctionArityExtractingVisitor();
parsedEl.accept(arityExtractingVisitor);
return arityExtractingVisitor.getFunctionArities();
} catch (Exception e) {
throw new IllegalStateException("Unexpected failure parsing the EL " + getCurrentExpressionOrFail(), e);
}
}
public void setCurrentExpression(String currentExpression) {
this.currentExpression = currentExpression;
currentExpressionsFunctions.clear();
}
private String getCurrentExpressionOrFail() {
if (currentExpression == null) {
throw new IllegalStateException("Current EL expression not set!");
}
return currentExpression;
}
}