| /* Copyright 2004 The Apache Software Foundation |
| * |
| * Licensed 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 org.apache.xmlbeans.impl.config; |
| |
| import com.github.javaparser.ast.NodeList; |
| import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; |
| import com.github.javaparser.ast.body.MethodDeclaration; |
| import com.github.javaparser.ast.body.Parameter; |
| import com.github.javaparser.ast.type.ReferenceType; |
| import com.github.javaparser.ast.type.Type; |
| import com.github.javaparser.ast.type.TypeParameter; |
| import com.github.javaparser.resolution.MethodUsage; |
| import com.github.javaparser.resolution.types.ResolvedType; |
| import org.apache.xmlbeans.InterfaceExtension; |
| import org.apache.xmlbeans.XmlObject; |
| import org.apache.xmlbeans.impl.xb.xmlconfig.Extensionconfig; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| public class InterfaceExtensionImpl implements InterfaceExtension { |
| private NameSet _xbeanSet; |
| private String _interfaceClassName; |
| private String _delegateToClassName; |
| private MethodSignatureImpl[] _methods; |
| |
| static InterfaceExtensionImpl newInstance(Parser loader, NameSet xbeanSet, Extensionconfig.Interface intfXO) { |
| InterfaceExtensionImpl result = new InterfaceExtensionImpl(); |
| |
| result._xbeanSet = xbeanSet; |
| |
| ClassOrInterfaceDeclaration interfaceJClass = validateInterface(loader, intfXO.getName(), intfXO); |
| |
| |
| if (interfaceJClass == null) |
| { |
| BindingConfigImpl.error("Interface '" + intfXO.getStaticHandler() + "' not found.", intfXO); |
| return null; |
| } |
| |
| result._interfaceClassName = interfaceJClass.getFullyQualifiedName().get(); |
| |
| result._delegateToClassName = intfXO.getStaticHandler(); |
| ClassOrInterfaceDeclaration delegateJClass = validateClass(loader, result._delegateToClassName, intfXO); |
| |
| if (delegateJClass == null) { |
| // no HandlerClass |
| BindingConfigImpl.warning("Handler class '" + intfXO.getStaticHandler() + "' not found on classpath, skip validation.", intfXO); |
| return result; |
| } |
| |
| if (!result.validateMethods(loader, interfaceJClass, delegateJClass, intfXO)) |
| return null; |
| |
| return result; |
| } |
| |
| private static ClassOrInterfaceDeclaration validateInterface(Parser loader, String intfStr, XmlObject loc) { |
| return validateJava(loader, intfStr, true, loc); |
| } |
| |
| static ClassOrInterfaceDeclaration validateClass(Parser loader, String clsStr, XmlObject loc) { |
| return validateJava(loader, clsStr, false, loc); |
| } |
| |
| static ClassOrInterfaceDeclaration validateJava(Parser loader, String clsStr, boolean isInterface, XmlObject loc) { |
| if (loader==null) { |
| return null; |
| } |
| |
| final String ent = isInterface ? "Interface" : "Class"; |
| ClassOrInterfaceDeclaration cls = loader.loadSource(clsStr); |
| |
| if (cls == null) { |
| BindingConfigImpl.error(ent + " '" + clsStr + "' not found.", loc); |
| return null; |
| } |
| |
| if ( isInterface != cls.isInterface() ) { |
| BindingConfigImpl.error("'" + clsStr + "' must be " + (isInterface ? "an interface" : "a class") + ".", loc); |
| } |
| |
| if (!cls.isPublic()) { |
| BindingConfigImpl.error(ent + " '" + clsStr + "' is not public.", loc); |
| } |
| |
| return cls; |
| } |
| |
| private boolean validateMethods(Parser loader, ClassOrInterfaceDeclaration interfaceJClass, ClassOrInterfaceDeclaration delegateJClass, XmlObject loc) { |
| _methods = interfaceJClass.resolve().getAllMethods().stream() |
| .filter(m -> !Object.class.getName().equals(m.declaringType().getQualifiedName())) |
| .map(m -> validateMethod(interfaceJClass, delegateJClass, m, loc)) |
| .map(m -> m == null ? null : new MethodSignatureImpl(getStaticHandler(), m)) |
| .toArray(MethodSignatureImpl[]::new); |
| |
| return Stream.of(_methods).allMatch(Objects::nonNull); |
| } |
| |
| private MethodDeclaration validateMethod(ClassOrInterfaceDeclaration interfaceJClass, |
| ClassOrInterfaceDeclaration delegateJClass, MethodUsage ifMethod, XmlObject loc) { |
| String methodName = ifMethod.getName(); |
| |
| MethodDeclaration delMethod = delegateJClass.getMethodsByName(methodName).stream() |
| .filter(mDel -> matchParams(ifMethod, mDel)).findFirst().orElse(null); |
| |
| String delegateFQN = delegateJClass.getFullyQualifiedName().orElse(""); |
| String methodFQN = methodName + "(" + ifMethod.getParamTypes().toString() + ")"; |
| String interfaceFQN = interfaceJClass.getFullyQualifiedName().orElse(""); |
| |
| if (delMethod == null) { |
| BindingConfigImpl.error("Handler class '" + delegateFQN + "' does not contain method " + methodFQN, loc); |
| return null; |
| } |
| |
| // check for throws exceptions |
| String[] ifEx = ifMethod.getDeclaration().getSpecifiedExceptions().stream().map(ResolvedType::describe).sorted().toArray(String[]::new); |
| String[] delEx = delMethod.getThrownExceptions().stream().map(Type::resolve).map(ResolvedType::describe).sorted().toArray(String[]::new); |
| |
| if (!Arrays.equals(ifEx, delEx)) { |
| BindingConfigImpl.error("Handler method '" + delegateFQN + "." + methodName + "' must declare the same " + |
| "exceptions as the interface method '" + interfaceFQN + "." + methodFQN, loc); |
| return null; |
| } |
| |
| if (!delMethod.isPublic() || !delMethod.isStatic()) { |
| BindingConfigImpl.error("Method '" + delegateFQN + "." + methodFQN + "' must be declared public and static.", loc); |
| return null; |
| } |
| |
| |
| if (!ifMethod.getDeclaration().getReturnType().equals(delMethod.resolve().getReturnType())) { |
| String returnType = ifMethod.getDeclaration().getReturnType().describe(); |
| BindingConfigImpl.error("Return type for method '" + returnType + " " + delegateFQN + "." + methodName + |
| "(...)' does not match the return type of the interface method :'" + returnType + "'.", loc); |
| return null; |
| } |
| |
| return delMethod; |
| } |
| |
| // private MethodDeclaration validateMethod(ClassOrInterfaceDeclaration interfaceJClass, |
| // ClassOrInterfaceDeclaration delegateJClass, MethodDeclaration method, XmlObject loc) { |
| // |
| // String methodName = method.getName().asString(); |
| // |
| // String[] delegateParams = Stream.concat( |
| // Stream.of("org.apache.xmlbeans.XmlObject"), |
| // Stream.of(paramStrings(method.getParameters())) |
| // ).toArray(String[]::new); |
| // |
| // MethodDeclaration handlerMethod = getMethod(delegateJClass, methodName, delegateParams); |
| // |
| // String delegateFQN = delegateJClass.getFullyQualifiedName().orElse(""); |
| // String methodFQN = methodName + "(" + method.getParameters().toString() + ")"; |
| // String interfaceFQN = interfaceJClass.getFullyQualifiedName().orElse(""); |
| // |
| // if (handlerMethod == null) { |
| // BindingConfigImpl.error("Handler class '" + delegateFQN + "' does not contain method " + methodFQN, loc); |
| // return null; |
| // } |
| // |
| // // check for throws exceptions |
| // if (!Arrays.equals(exceptionStrings(method), exceptionStrings(handlerMethod))) { |
| // BindingConfigImpl.error("Handler method '" + delegateFQN + "." + methodName + "' must declare the same " + |
| // "exceptions as the interface method '" + interfaceFQN + "." + methodFQN, loc); |
| // return null; |
| // } |
| // |
| // if (!handlerMethod.isPublic() || !handlerMethod.isStatic()) { |
| // BindingConfigImpl.error("Method '" + delegateJClass.getFullyQualifiedName() + "." + |
| // methodFQN + "' must be declared public and static.", loc); |
| // return null; |
| // } |
| // |
| // String returnType = method.getTypeAsString(); |
| // if (!returnType.equals(handlerMethod.getTypeAsString())) { |
| // BindingConfigImpl.error("Return type for method '" + returnType + " " + delegateFQN + "." + methodName + |
| // "(...)' does not match the return type of the interface method :'" + returnType + "'.", loc); |
| // return null; |
| // } |
| // |
| // return method; |
| // } |
| |
| static MethodDeclaration getMethod(ClassOrInterfaceDeclaration cls, String name, String[] paramTypes) { |
| // cls.getMethodsBySignature only checks the type name as-is ... i.e. if the type name is imported |
| // only the simple name is checked, otherwise the full qualified name |
| return cls.getMethodsByName(name).stream() |
| .filter(m -> parameterMatches(paramStrings(m.getParameters()), paramTypes)) |
| .findFirst().orElse(null); |
| } |
| |
| private static String[] paramStrings(NodeList<?> params) { |
| return params.stream().map(p -> { |
| if (p instanceof Parameter) { |
| return ((Parameter)p).getType().resolve().describe(); |
| } else if (p instanceof TypeParameter) { |
| return ((TypeParameter)p).getNameAsString(); |
| } else { |
| return "unknown"; |
| } |
| }).toArray(String[]::new); |
| } |
| |
| private static boolean matchParams(MethodUsage mIf, MethodDeclaration mDel) { |
| // the delegate needs to have the XmlObject as first parameter |
| List<ResolvedType> pIf = mIf.getParamTypes(); |
| NodeList<Parameter> pDel = mDel.getParameters(); |
| if (pDel.size() != pIf.size()+1) { |
| return false; |
| } |
| |
| if (!XmlObject.class.getName().equals(pDel.get(0).resolve().describeType())) { |
| return false; |
| } |
| |
| // the other parameters need to match |
| int idx = 1; |
| for (ResolvedType rt : pIf) { |
| if (!rt.describe().equals(pDel.get(idx++).resolve().describeType())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean parameterMatches(String[] params1, String[] params2) { |
| // compare all parameters type strings |
| // a type string can be a simple name (e.g. "XmlObject") or |
| // fully qualified name ("org.apache.xmlbeans.XmlObject") |
| // try to loosely match the names |
| if (params1.length != params2.length) { |
| return false; |
| } |
| for (int i=0; i<params1.length; i++) { |
| String p1 = params1[i]; |
| String p2 = params2[i]; |
| if (p1.contains(".")) { |
| String tmp = p1; |
| p1 = p2; |
| p2 = tmp; |
| } |
| if (!p2.endsWith(p1)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /* public getters */ |
| public boolean contains(String fullJavaName) { |
| return _xbeanSet.contains(fullJavaName); |
| } |
| |
| public String getStaticHandler() { |
| return _delegateToClassName; |
| } |
| |
| public String getInterface() { |
| return _interfaceClassName; |
| } |
| |
| public InterfaceExtension.MethodSignature[] getMethods() { |
| return _methods; |
| } |
| |
| public String toString() { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(" static handler: ").append(_delegateToClassName).append("\n"); |
| buf.append(" interface: ").append(_interfaceClassName).append("\n"); |
| buf.append(" name set: ").append(_xbeanSet).append("\n"); |
| |
| for (int i = 0; i < _methods.length; i++) |
| buf.append(" method[").append(i).append("]=").append(_methods[i]).append("\n"); |
| |
| return buf.toString(); |
| } |
| |
| // this is used only for detecting method colisions of extending interfaces |
| static class MethodSignatureImpl implements InterfaceExtension.MethodSignature { |
| private final String _intfName; |
| private final int NOTINITIALIZED = -1; |
| private int _hashCode = NOTINITIALIZED; |
| private String _signature; |
| |
| private final String _name; |
| private final String _return; |
| private final String[] _params; |
| private final String[] _paramNames; |
| private final String[] _exceptions; |
| |
| MethodSignatureImpl(String intfName, MethodDeclaration method) { |
| if (intfName==null || method==null) { |
| throw new IllegalArgumentException("Interface: " + intfName + " method: " + method); |
| } |
| |
| _intfName = intfName; |
| _signature = null; |
| |
| _name = method.getName().asString(); |
| String typeParams = method.getTypeParameters().stream().map(TypeParameter::toString).collect(Collectors.joining(", ")); |
| _return = ( typeParams.length() == 0 ? "" : ( " <" + typeParams + "> ") ) + |
| replaceInner(method.getType().resolve().describe()); |
| |
| _params = method.getParameters().stream().map(p -> p.getType().resolve().describe()) |
| .map(MethodSignatureImpl::replaceInner).toArray(String[]::new); |
| |
| _exceptions = method.getThrownExceptions().stream().map(e -> e.resolve().describe()) |
| .map(MethodSignatureImpl::replaceInner).toArray(String[]::new); |
| |
| _paramNames = method.getParameters().stream().map(p -> p.getNameAsString()).toArray(String[]::new); |
| } |
| |
| private static String replaceInner(String classname) { |
| return classname.replace('$', '.'); |
| } |
| |
| String getInterfaceName() { |
| return _intfName; |
| } |
| |
| public String getName() { |
| return _name; |
| } |
| |
| public String getReturnType() { |
| return _return; |
| } |
| |
| public String[] getParameterTypes() { |
| return _params; |
| } |
| |
| public String[] getParameterNames() { |
| return _paramNames; |
| } |
| |
| public String[] getExceptionTypes() { |
| return _exceptions; |
| } |
| |
| public boolean equals(Object o) { |
| if (o == this) { |
| return true; |
| } |
| |
| if (!(o instanceof MethodSignatureImpl)) { |
| return false; |
| } |
| MethodSignatureImpl ms = (MethodSignatureImpl)o; |
| |
| return ms.getName().equals(getName()) && |
| _intfName.equals(ms._intfName) && |
| Arrays.equals(getParameterTypes(),ms.getParameterTypes()); |
| } |
| |
| public int hashCode() { |
| return (_hashCode!=NOTINITIALIZED) ? _hashCode : |
| (_hashCode = Objects.hash(getName(), Arrays.hashCode(getParameterTypes()), _intfName)); |
| } |
| |
| String getSignature() { |
| return (_signature!=null) ? _signature : |
| (_signature = _name+"("+String.join(" ,", _params)+")"); |
| } |
| |
| public String toString() { |
| return getReturnType() + " " + getSignature(); |
| } |
| } |
| } |