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: 1052987 $ $Date: 2010-12-27 09:27:28 +0800 (Mon, 27 Dec 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         *
203         * @param superClassName The super class name should be in the format of org/test/SimpleClass
204         * @return return true if the method parameter superClassName  is assignable to any interface in the expected interfaces or any class in the expected classes
205         */
206        private boolean isSuperClassAssignable(String superClassName) {
207            if (targetClassNames.contains(superClassName)) {
208                return true;
209            } else if (superClassName.equals("java/lang/Object")) {
210                return false;
211            }
212            
213            //Check parent class
214            URL url = bundle.getResource(superClassName + ".class");
215            if (url == null) {
216                //TODO what should we do if we do not find the super class ?
217                return false;
218            }
219            InputStream in = null;
220            try {
221                in = url.openStream();
222                ClassReader classReader = new ClassReader(in);
223                
224                //Check interfaces
225                String[] superInterfaceNames = classReader.getInterfaces();            
226                for (String superInterfaceName : superInterfaceNames) {
227                    if (isInterfaceAssignable(superInterfaceName)) {
228                        return true;
229                    }                
230                }
231                
232                //Check className
233                return isSuperClassAssignable(classReader.getSuperName());            
234            } catch (IOException e) {
235                logger.warn("Unable to check the super class  " + superClassName, e);
236                return false;
237            } finally {
238                if (in != null) {
239                    try {
240                        in.close();
241                    } catch (Exception e) {
242                    }
243                }
244            }
245        }
246    
247        /**
248         * Get the ASM style package name from the parameter className.
249         * If the className is ended with .class extension, e.g.  /org/apache/geronimo/TestCass.class or org.apache.geronimo.TestClass.class,
250         *      then org/apache/geronimo is returned
251         * If the className is not ended with .class extension, e.g.  /org/apache/geronimo/TestCass or org.apache.geronimo.TestClass,
252         *      then org/apache/geronimo is returned
253         * @param className
254         * @return ASM style package name, should be in the format of  "org/apache/geronimo"
255         */
256        protected String toASMStylePackageName(String className) {
257            if (className.endsWith(EXT)) {
258                className = className.substring(0, className.length() - EXT.length());
259            }
260            className = className.replace('.', '/');
261            int iLastDotIndex = className.lastIndexOf('/');
262            if (iLastDotIndex != -1) {
263                return className.substring(0, iLastDotIndex);
264            } else {
265                return "";
266            }
267        }
268    }