1 /* Copyright 2004 The Apache Software Foundation 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package org.apache.xmlbeans.impl.config; 17 18 import org.apache.xmlbeans.impl.xb.xmlconfig.Extensionconfig; 19 import org.apache.xmlbeans.InterfaceExtension; 20 import org.apache.xmlbeans.XmlObject; 21 import org.apache.xmlbeans.impl.jam.JMethod; 22 import org.apache.xmlbeans.impl.jam.JClass; 23 import org.apache.xmlbeans.impl.jam.JParameter; 24 import org.apache.xmlbeans.impl.jam.JamClassLoader; 25 26 public class InterfaceExtensionImpl implements InterfaceExtension 27 { 28 private NameSet _xbeanSet; 29 private String _interfaceClassName; 30 private String _delegateToClassName; 31 private MethodSignatureImpl[] _methods; 32 33 static InterfaceExtensionImpl newInstance(JamClassLoader loader, NameSet xbeanSet, Extensionconfig.Interface intfXO) 34 { 35 InterfaceExtensionImpl result = new InterfaceExtensionImpl(); 36 37 result._xbeanSet = xbeanSet; 38 JClass interfaceJClass = validateInterface(loader, intfXO.getName(), intfXO); 39 40 41 if (interfaceJClass == null) 42 { 43 BindingConfigImpl.error("Interface '" + intfXO.getStaticHandler() + "' not found.", intfXO); 44 return null; 45 } 46 47 result._interfaceClassName = interfaceJClass.getQualifiedName(); 48 49 result._delegateToClassName = intfXO.getStaticHandler(); 50 JClass delegateJClass = validateClass(loader, result._delegateToClassName, intfXO); 51 52 if (delegateJClass == null) // no HandlerClass 53 { 54 BindingConfigImpl.warning("Handler class '" + intfXO.getStaticHandler() + "' not found on classpath, skip validation.", intfXO); 55 return result; 56 } 57 58 if (!result.validateMethods(interfaceJClass, delegateJClass, intfXO)) 59 return null; 60 61 return result; 62 } 63 64 private static JClass validateInterface(JamClassLoader loader, String intfStr, XmlObject loc) 65 { 66 return validateJava(loader, intfStr, true, loc); 67 } 68 69 static JClass validateClass(JamClassLoader loader, String clsStr, XmlObject loc) 70 { 71 return validateJava(loader, clsStr, false, loc); 72 } 73 74 static JClass validateJava(JamClassLoader loader, String clsStr, boolean isInterface, XmlObject loc) 75 { 76 if (loader==null) 77 return null; 78 79 final String ent = isInterface ? "Interface" : "Class"; 80 JClass cls = loader.loadClass(clsStr); 81 82 if (cls==null || cls.isUnresolvedType()) 83 { 84 BindingConfigImpl.error(ent + " '" + clsStr + "' not found.", loc); 85 return null; 86 } 87 88 if ( (isInterface && !cls.isInterface()) || 89 (!isInterface && cls.isInterface())) 90 { 91 BindingConfigImpl.error("'" + clsStr + "' must be " + 92 (isInterface ? "an interface" : "a class") + ".", loc); 93 } 94 95 if (!cls.isPublic()) 96 { 97 BindingConfigImpl.error(ent + " '" + clsStr + "' is not public.", loc); 98 } 99 100 return cls; 101 } 102 103 private boolean validateMethods(JClass interfaceJClass, JClass delegateJClass, XmlObject loc) 104 { 105 //assert _delegateToClass != null : "Delegate to class handler expected."; 106 boolean valid = true; 107 108 JMethod[] interfaceMethods = interfaceJClass.getMethods(); 109 _methods = new MethodSignatureImpl[interfaceMethods.length]; 110 111 for (int i = 0; i < interfaceMethods.length; i++) 112 { 113 JMethod method = validateMethod(interfaceJClass, delegateJClass, interfaceMethods[i], loc); 114 if (method != null) 115 _methods[i] = new MethodSignatureImpl(getStaticHandler(), method); 116 else 117 valid = false; 118 } 119 120 121 return valid; 122 } 123 124 private JMethod validateMethod(JClass interfaceJClass, JClass delegateJClass, JMethod method, XmlObject loc) 125 { 126 String methodName = method.getSimpleName(); 127 JParameter[] params = method.getParameters(); 128 JClass returnType = method.getReturnType(); 129 130 JClass[] delegateParams = new JClass[params.length+1]; 131 delegateParams[0] = returnType.forName("org.apache.xmlbeans.XmlObject"); 132 for (int i = 1; i < delegateParams.length; i++) 133 { 134 delegateParams[i] = params[i-1].getType(); 135 } 136 137 JMethod handlerMethod = null; 138 handlerMethod = getMethod(delegateJClass, methodName, delegateParams); 139 if (handlerMethod==null) 140 { 141 BindingConfigImpl.error("Handler class '" + delegateJClass.getQualifiedName() + "' does not contain method " + methodName + "(" + listTypes(delegateParams) + ")", loc); 142 return null; 143 } 144 145 // check for throws exceptions 146 JClass[] intfExceptions = method.getExceptionTypes(); 147 JClass[] delegateExceptions = handlerMethod.getExceptionTypes(); 148 if ( delegateExceptions.length!=intfExceptions.length ) 149 { 150 BindingConfigImpl.error("Handler method '" + delegateJClass.getQualifiedName() + "." + methodName + "(" + listTypes(delegateParams) + 151 ")' must declare the same exceptions as the interface method '" + interfaceJClass.getQualifiedName() + "." + methodName + "(" + listTypes(params), loc); 152 return null; 153 } 154 155 for (int i = 0; i < delegateExceptions.length; i++) 156 { 157 if ( delegateExceptions[i]!=intfExceptions[i] ) 158 { 159 BindingConfigImpl.error("Handler method '" + delegateJClass.getQualifiedName() + "." + methodName + "(" + listTypes(delegateParams) + 160 ")' must declare the same exceptions as the interface method '" + interfaceJClass.getQualifiedName() + "." + methodName + "(" + listTypes(params), loc); 161 return null; 162 } 163 } 164 165 if (!handlerMethod.isPublic() || !handlerMethod.isStatic()) 166 { 167 BindingConfigImpl.error("Method '" + delegateJClass.getQualifiedName() + "." + methodName + "(" + listTypes(delegateParams) + ")' must be declared public and static.", loc); 168 return null; 169 } 170 171 if (!returnType.equals(handlerMethod.getReturnType())) 172 { 173 BindingConfigImpl.error("Return type for method '" + handlerMethod.getReturnType() + " " + delegateJClass.getQualifiedName() + 174 "." + methodName + "(" + listTypes(delegateParams) + ")' does not match the return type of the interface method :'" + returnType + "'.", loc); 175 return null; 176 } 177 178 return method; 179 } 180 181 static JMethod getMethod(JClass cls, String name, JClass[] paramTypes) 182 { 183 JMethod[] methods = cls.getMethods(); 184 for (int i = 0; i < methods.length; i++) 185 { 186 JMethod method = methods[i]; 187 if (!name.equals(method.getSimpleName())) 188 continue; 189 190 JParameter[] mParams = method.getParameters(); 191 192 // can have methods with same name but different # of params 193 if (mParams.length != paramTypes.length) 194 continue; 195 196 for (int j = 0; j < mParams.length; j++) 197 { 198 JParameter mParam = mParams[j]; 199 if (!mParam.getType().equals(paramTypes[j])) 200 continue; 201 } 202 203 return method; 204 } 205 return null; 206 } 207 208 private static String listTypes(JClass[] types) 209 { 210 StringBuffer result = new StringBuffer(); 211 for (int i = 0; i < types.length; i++) 212 { 213 JClass type = types[i]; 214 if (i>0) 215 result.append(", "); 216 result.append(emitType(type)); 217 } 218 return result.toString(); 219 } 220 221 private static String listTypes(JParameter[] params) 222 { 223 StringBuffer result = new StringBuffer(); 224 for (int i = 0; i < params.length; i++) 225 { 226 JClass type = params[i].getType(); 227 if (i>0) 228 result.append(", "); 229 result.append(emitType(type)); 230 } 231 return result.toString(); 232 } 233 234 public static String emitType(JClass cls) 235 { 236 if (cls.isArrayType()) 237 return emitType(cls.getArrayComponentType()) + "[]"; 238 else 239 return cls.getQualifiedName().replace('$', '.'); 240 } 241 242 /* public getters */ 243 public boolean contains(String fullJavaName) 244 { 245 return _xbeanSet.contains(fullJavaName); 246 } 247 248 public String getStaticHandler() 249 { 250 return _delegateToClassName; 251 } 252 253 public String getInterface() 254 { 255 return _interfaceClassName; 256 } 257 258 public InterfaceExtension.MethodSignature[] getMethods() 259 { 260 return _methods; 261 } 262 263 public String toString() 264 { 265 StringBuffer buf = new StringBuffer(); 266 buf.append(" static handler: ").append(_delegateToClassName).append("\n"); 267 buf.append(" interface: ").append(_interfaceClassName).append("\n"); 268 buf.append(" name set: ").append(_xbeanSet).append("\n"); 269 270 for (int i = 0; i < _methods.length; i++) 271 buf.append(" method[").append(i).append("]=").append(_methods[i]).append("\n"); 272 273 return buf.toString(); 274 } 275 276 // this is used only for detecting method colisions of extending interfaces 277 static class MethodSignatureImpl implements InterfaceExtension.MethodSignature 278 { 279 private String _intfName; 280 private final int NOTINITIALIZED = -1; 281 private int _hashCode = NOTINITIALIZED; 282 private String _signature; 283 284 private String _name; 285 private String _return; 286 private String[] _params; 287 private String[] _exceptions; 288 289 MethodSignatureImpl(String intfName, JMethod method) 290 { 291 if (intfName==null || method==null) 292 throw new IllegalArgumentException("Interface: " + intfName + " method: " + method); 293 294 _intfName = intfName; 295 _hashCode = NOTINITIALIZED; 296 _signature = null; 297 298 _name = method.getSimpleName(); 299 _return = method.getReturnType().getQualifiedName().replace('$', '.'); 300 301 JParameter[] paramTypes = method.getParameters(); 302 _params = new String[paramTypes.length]; 303 for (int i = 0; i < paramTypes.length; i++) 304 _params[i] = paramTypes[i].getType().getQualifiedName().replace('$', '.');; 305 306 JClass[] exceptionTypes = method.getExceptionTypes(); 307 _exceptions = new String[exceptionTypes.length]; 308 for (int i = 0; i < exceptionTypes.length; i++) 309 _exceptions[i] = exceptionTypes[i].getQualifiedName().replace('$', '.'); 310 } 311 312 String getInterfaceName() 313 { 314 return _intfName; 315 } 316 317 public String getName() 318 { 319 return _name; 320 } 321 322 public String getReturnType() 323 { 324 return _return; 325 } 326 327 public String[] getParameterTypes() 328 { 329 return _params; 330 } 331 332 public String[] getExceptionTypes() 333 { 334 return _exceptions; 335 } 336 337 public boolean equals(Object o) 338 { 339 if ( !(o instanceof MethodSignatureImpl)) 340 return false; 341 342 MethodSignatureImpl ms = (MethodSignatureImpl)o; 343 344 if (!ms.getName().equals(getName()) ) 345 return false; 346 347 String[] params = getParameterTypes(); 348 String[] msParams = ms.getParameterTypes(); 349 350 if (msParams.length != params.length ) 351 return false; 352 353 for (int i = 0; i < params.length; i++) 354 { 355 if (!msParams[i].equals(params[i])) 356 return false; 357 } 358 359 if (!_intfName.equals(ms._intfName)) 360 return false; 361 362 return true; 363 } 364 365 public int hashCode() 366 { 367 if (_hashCode!=NOTINITIALIZED) 368 return _hashCode; 369 370 int hash = getName().hashCode(); 371 372 String[] params = getParameterTypes(); 373 374 for (int i = 0; i < params.length; i++) 375 { 376 hash *= 19; 377 hash += params[i].hashCode(); 378 } 379 380 hash += 21 * _intfName.hashCode(); 381 382 _hashCode = hash; 383 return _hashCode; 384 } 385 386 String getSignature() 387 { 388 if (_signature!=null) 389 return _signature; 390 391 StringBuffer sb = new StringBuffer(60); 392 sb.append(_name).append("("); 393 for (int i = 0; i < _params.length; i++) 394 sb.append((i == 0 ? "" : " ,")).append(_params[i]); 395 sb.append(")"); 396 397 _signature = sb.toString(); 398 399 return _signature; 400 } 401 402 public String toString() 403 { 404 StringBuffer buf = new StringBuffer(); 405 406 buf.append(getReturnType()).append(" ").append(getSignature()); 407 408 return buf.toString(); 409 } 410 } 411 }