001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.xbean.finder;
019    
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.net.URL;
023    import java.util.HashSet;
024    import java.util.List;
025    import java.util.Set;
026    
027    import org.apache.xbean.osgi.bundle.util.BundleClassFinder;
028    import org.apache.xbean.osgi.bundle.util.BundleDescription;
029    import org.apache.xbean.osgi.bundle.util.ClassDiscoveryFilter;
030    import org.objectweb.asm.ClassReader;
031    import org.objectweb.asm.Opcodes;
032    import org.osgi.framework.Bundle;
033    import org.osgi.service.packageadmin.ExportedPackage;
034    import org.osgi.service.packageadmin.PackageAdmin;
035    import org.slf4j.Logger;
036    import org.slf4j.LoggerFactory;
037    
038    /**
039     * @version $Rev: 941557 $ $Date: 2010-05-06 03:16:59 +0200 (Thu, 06 May 2010) $
040     */
041    public class BundleAssignableClassFinder extends BundleClassFinder {
042    
043        private static final Logger logger = LoggerFactory.getLogger(BundleAssignableClassFinder.class);
044    
045        private Class<?>[] clses;
046    
047        private Set<String> targetClassNames = new HashSet<String>();
048    
049        private Set<String> targetInterfaceNames = new HashSet<String>();
050    
051        private Set<String> wiredImportedPackageNames = new HashSet<String>();
052    
053        /**
054         * Create a new BundleClassFinder, it will search all the classes based the rule defined by the parameters via ASM tool
055         * @param packageAdmin
056         * @param bundle
057         * @param clses
058         * @param discoveryFilter
059         */
060        public BundleAssignableClassFinder(PackageAdmin packageAdmin, Bundle bundle, Class<?>[] clses, ClassDiscoveryFilter discoveryFilter) {
061            super(packageAdmin, bundle, discoveryFilter);
062            if (clses == null || clses.length == 0) {
063                throw new IllegalArgumentException("At least one class or interface should be specified");
064            }
065            this.clses = clses;
066            for (Class<?> cls : clses) {
067                String asmStyleName = cls.getName().replace('.', '/');
068                if (cls.isInterface()) {
069                    targetInterfaceNames.add(asmStyleName);
070                } else {
071                    targetClassNames.add(asmStyleName);
072                }
073            }
074            initialize();
075        }
076    
077        public BundleAssignableClassFinder(PackageAdmin packageAdmin, Class<?>[] clses, Bundle bundle) {
078            this(packageAdmin, bundle, clses, FULL_CLASS_DISCOVERY_FILTER);
079        }
080    
081        @Override
082        protected BundleClassFinder createSubBundleClassFinder(PackageAdmin packageAdmin, Bundle bundle, ClassDiscoveryFilter classDiscoveryFilter) {
083            return new BundleAssignableClassFinder(packageAdmin, bundle, clses, classDiscoveryFilter);
084        }
085    
086        @Override
087        protected boolean isClassAcceptable(String name, InputStream in) throws IOException {
088            ClassReader classReader = new ClassReader(in);
089            String className = classReader.getClassName();
090            if ((classReader.getAccess() & Opcodes.ACC_INTERFACE) == 0) {
091                if (targetClassNames.contains(className)) {
092                    return true;
093                }
094            } else {
095                if (targetInterfaceNames.contains(className)) {
096                    return true;
097                }
098            }
099            String[] interfaceNames = classReader.getInterfaces();
100            try {
101                for (String interfaceName : interfaceNames) {
102                    if (wiredImportedPackageNames.contains(toASMStylePackageName(interfaceName))) {
103                        return isClassAssignable(bundle.loadClass(toJavaStyleClassName(interfaceName)));
104                    } else {
105                        if (isInterfaceAssignable(interfaceName)) {
106                            return true;
107                        }
108                    }
109                }
110                String superClassName = classReader.getSuperName();
111                if (wiredImportedPackageNames.contains(toASMStylePackageName(superClassName))) {
112                    return isClassAssignable(bundle.loadClass(toJavaStyleClassName(superClassName)));
113                }
114                return isSuperClassAssignable(superClassName);
115            } catch (ClassNotFoundException e) {
116                return false;
117            }
118        }
119    
120        @Override
121        protected boolean isClassAcceptable(URL url) {
122            InputStream in = null;
123            try {
124                in = url.openStream();
125                return isClassAcceptable("", in);
126            } catch (IOException e) {
127                logger.warn("Unable to check the class of url " + url, e);
128                return false;
129            } finally {
130                if (in != null)
131                    try {
132                        in.close();
133                    } catch (Exception e) {
134                    }
135            }
136        }
137    
138        private void initialize() {
139            BundleDescription description = new BundleDescription(bundle.getHeaders());
140            List<BundleDescription.ImportPackage> imports = description.getExternalImports();
141            for (BundleDescription.ImportPackage packageImport : imports) {
142                String packageName = packageImport.getName();
143                ExportedPackage[] exports = packageAdmin.getExportedPackages(packageName);
144                Bundle wiredBundle = isWired(bundle, exports);
145                if (wiredBundle != null) {
146                    wiredImportedPackageNames.add(packageName.replace('.', '/'));
147                    break;
148                }
149            }
150        }
151    
152        private boolean isClassAssignable(Class<?> cls) {
153            for (Class<?> targetClass : clses) {
154                if (targetClass.isAssignableFrom(cls)) {
155                    return true;
156                }
157            }
158            return false;
159        }
160    
161        /**
162         *
163         * @param interfaceName The interface name should be in the format of org/test/SimpleInterface
164         * @return return true if the method parameter interfaceName is assignable to any interface in the expected interfaces
165         */
166        private boolean isInterfaceAssignable(String interfaceName) {
167            //Check each interface in interfaceNames set
168            if (targetInterfaceNames.contains(interfaceName)) {
169                return true;
170            }
171            //Check ancestor intefaces
172            URL url = bundle.getResource("/" + interfaceName + ".class");
173            if (url == null) {
174                //TODO what should we do if we do not find the interface ?
175                return false;
176            }
177            InputStream in = null;
178            try {
179                in = url.openStream();
180                ClassReader classReader = new ClassReader(in);
181                String[] superInterfaceNames = classReader.getInterfaces();
182                for (String superInterfaceName : superInterfaceNames) {
183                    if (isInterfaceAssignable(superInterfaceName)) {
184                        return true;
185                    }
186                }
187                return false;
188            } catch (IOException e) {
189                logger.warn("Unable to check the interface " + interfaceName, e);
190                return false;
191            } finally {
192                if (in != null)
193                    try {
194                        in.close();
195                    } catch (Exception e) {
196                    }
197            }
198        }
199    
200        /**
201         *
202         * @param superClassName The super class name should be in the format of org/test/SimpleClass
203         * @return return true if the method parameter superClassName  is assignable to any interface in the expected interfaces or any class in the expected classes
204         */
205        private boolean isSuperClassAssignable(String superClassName) {
206            if (targetClassNames.contains(superClassName)) {
207                return true;
208            }
209            //Check parent class
210            URL url = bundle.getResource("/" + superClassName + ".class");
211            if (url == null) {
212                //TODO what should we do if we do not find the super class ?
213                return false;
214            }
215            InputStream in = null;
216            try {
217                in = url.openStream();
218                ClassReader classReader = new ClassReader(in);
219                String[] superInterfaceNames = classReader.getInterfaces();
220                //Check interfaces
221                for (String superInterfaceName : superInterfaceNames) {
222                    if (isInterfaceAssignable(superInterfaceName)) {
223                        return true;
224                    }
225                }
226                //Check className
227                if (classReader.getSuperName().equals("java/lang/Object")) {
228                    return targetClassNames.contains("java/lang/Object");
229                } else {
230                    return isSuperClassAssignable(classReader.getSuperName());
231                }
232            } catch (IOException e) {
233                logger.warn("Unable to check the super class  " + superClassName, e);
234                return false;
235            } finally {
236                if (in != null)
237                    try {
238                        in.close();
239                    } catch (Exception e) {
240                    }
241            }
242        }
243    
244        /**
245         * Get the ASM style package name from the parameter className.
246         * If the className is ended with .class extension, e.g.  /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class,
247         *      then org/apache/geronimo is returned
248         * If the className is not ended with .class extension, e.g.  /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass,
249         *      then org/apache/geronimo is returned
250         * @param className
251         * @return ASM style package name, should be in the format of  "org/apache/geronimo"
252         */
253        protected String toASMStylePackageName(String className) {
254            if (className.endsWith(EXT)) {
255                className = className.substring(0, className.length() - EXT.length());
256            }
257            className = className.replace('.', '/');
258            int iLastDotIndex = className.lastIndexOf('/');
259            if (iLastDotIndex != -1) {
260                return className.substring(0, iLastDotIndex);
261            } else {
262                return "";
263            }
264        }
265    }