blob: 0255167a027945bd2382eebbd2f2a31f140b4200 [file] [log] [blame]
/**
* 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 org.apache.xbean.finder;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.xbean.osgi.bundle.util.BundleClassFinder;
import org.apache.xbean.osgi.bundle.util.BundleDescription;
import org.apache.xbean.osgi.bundle.util.ClassDiscoveryFilter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.osgi.framework.Bundle;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @version $Rev$ $Date$
*/
public class BundleAssignableClassFinder extends BundleClassFinder {
private static final Logger logger = LoggerFactory.getLogger(BundleAssignableClassFinder.class);
private Class<?>[] clses;
private Set<String> targetClassNames = new HashSet<String>();
private Set<String> targetInterfaceNames = new HashSet<String>();
private Set<String> wiredImportedPackageNames = new HashSet<String>();
/**
* Create a new BundleClassFinder, it will search all the classes based the rule defined by the parameters via ASM tool
* @param packageAdmin
* @param bundle
* @param clses
* @param discoveryFilter
*/
public BundleAssignableClassFinder(PackageAdmin packageAdmin, Bundle bundle, Class<?>[] clses, ClassDiscoveryFilter discoveryFilter) {
super(packageAdmin, bundle, discoveryFilter);
if (clses == null || clses.length == 0) {
throw new IllegalArgumentException("At least one class or interface should be specified");
}
this.clses = clses;
for (Class<?> cls : clses) {
String asmStyleName = cls.getName().replace('.', '/');
if (cls.isInterface()) {
targetInterfaceNames.add(asmStyleName);
} else {
targetClassNames.add(asmStyleName);
}
}
initialize();
}
public BundleAssignableClassFinder(PackageAdmin packageAdmin, Class<?>[] clses, Bundle bundle) {
this(packageAdmin, bundle, clses, FULL_CLASS_DISCOVERY_FILTER);
}
@Override
protected BundleClassFinder createSubBundleClassFinder(PackageAdmin packageAdmin, Bundle bundle, ClassDiscoveryFilter classDiscoveryFilter) {
return new BundleAssignableClassFinder(packageAdmin, bundle, clses, classDiscoveryFilter);
}
@Override
protected boolean isClassAcceptable(String name, InputStream in) throws IOException {
ClassReader classReader = new ClassReader(in);
String className = classReader.getClassName();
if ((classReader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
if (targetClassNames.contains(className)) {
return true;
}
} else {
if (targetInterfaceNames.contains(className)) {
return true;
}
}
String[] interfaceNames = classReader.getInterfaces();
try {
for (String interfaceName : interfaceNames) {
if (wiredImportedPackageNames.contains(toASMStylePackageName(interfaceName))) {
return isClassAssignable(bundle.loadClass(toJavaStyleClassName(interfaceName)));
} else {
if (isInterfaceAssignable(interfaceName)) {
return true;
}
}
}
String superClassName = classReader.getSuperName();
if (superClassName == null) {
return false;
}
if (wiredImportedPackageNames.contains(toASMStylePackageName(superClassName))) {
return isClassAssignable(bundle.loadClass(toJavaStyleClassName(superClassName)));
}
return isSuperClassAssignable(superClassName);
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
protected boolean isClassAcceptable(URL url) {
InputStream in = null;
try {
in = url.openStream();
return isClassAcceptable("", in);
} catch (IOException e) {
logger.warn("Unable to check the class of url " + url, e);
return false;
} finally {
if (in != null)
try {
in.close();
} catch (Exception e) {
}
}
}
private void initialize() {
BundleDescription description = new BundleDescription(bundle.getHeaders());
List<BundleDescription.ImportPackage> imports = description.getExternalImports();
for (BundleDescription.ImportPackage packageImport : imports) {
String packageName = packageImport.getName();
ExportedPackage[] exports = packageAdmin.getExportedPackages(packageName);
Bundle wiredBundle = isWired(bundle, exports);
if (wiredBundle != null) {
wiredImportedPackageNames.add(packageName.replace('.', '/'));
break;
}
}
}
private boolean isClassAssignable(Class<?> cls) {
for (Class<?> targetClass : clses) {
if (targetClass.isAssignableFrom(cls)) {
return true;
}
}
return false;
}
/**
*
* @param interfaceName The interface name should be in the format of org/test/SimpleInterface
* @return return true if the method parameter interfaceName is assignable to any interface in the expected interfaces
*/
private boolean isInterfaceAssignable(String interfaceName) {
//Check each interface in interfaceNames set
if (targetInterfaceNames.contains(interfaceName)) {
return true;
}
//Check ancestor intefaces
URL url = bundle.getResource(interfaceName + ".class");
if (url == null) {
//TODO what should we do if we do not find the interface ?
return false;
}
InputStream in = null;
try {
in = url.openStream();
ClassReader classReader = new ClassReader(in);
String[] superInterfaceNames = classReader.getInterfaces();
for (String superInterfaceName : superInterfaceNames) {
if (isInterfaceAssignable(superInterfaceName)) {
return true;
}
}
return false;
} catch (IOException e) {
logger.warn("Unable to check the interface " + interfaceName, e);
return false;
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
}
}
}
}
/**
*
* @param superClassName The super class name should be in the format of org/test/SimpleClass
* @return return true if the method parameter superClassName is assignable to any interface in the expected interfaces or any class in the expected classes
*/
private boolean isSuperClassAssignable(String superClassName) {
if (targetClassNames.contains(superClassName)) {
return true;
} else if (superClassName.equals("java/lang/Object")) {
return false;
}
//Check parent class
URL url = bundle.getResource(superClassName + ".class");
if (url == null) {
//TODO what should we do if we do not find the super class ?
return false;
}
InputStream in = null;
try {
in = url.openStream();
ClassReader classReader = new ClassReader(in);
//Check interfaces
String[] superInterfaceNames = classReader.getInterfaces();
for (String superInterfaceName : superInterfaceNames) {
if (isInterfaceAssignable(superInterfaceName)) {
return true;
}
}
//Check className
return isSuperClassAssignable(classReader.getSuperName());
} catch (IOException e) {
logger.warn("Unable to check the super class " + superClassName, e);
return false;
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
}
}
}
}
/**
* Get the ASM style package name from the parameter className.
* If the className is ended with .class extension, e.g. /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class,
* then org/apache/geronimo is returned
* If the className is not ended with .class extension, e.g. /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass,
* then org/apache/geronimo is returned
* @param className
* @return ASM style package name, should be in the format of "org/apache/geronimo"
*/
protected String toASMStylePackageName(String className) {
if (className.endsWith(EXT)) {
className = className.substring(0, className.length() - EXT.length());
}
className = className.replace('.', '/');
int iLastDotIndex = className.lastIndexOf('/');
if (iLastDotIndex != -1) {
return className.substring(0, iLastDotIndex);
} else {
return "";
}
}
}