001/*******************************************************************************
002 * Copyright (C) PicoContainer Organization. All rights reserved. 
003 * ---------------------------------------------------------------------------
004 * The software in this package is published under the terms of the BSD style
005 * license a copy of which has been included with this distribution in the
006 * LICENSE.txt file. 
007 ******************************************************************************/
008package org.picocontainer.classname;
009
010import org.picocontainer.ComponentAdapter;
011
012import org.picocontainer.*;
013import org.picocontainer.security.CustomPermissionsURLClassLoader;
014import org.picocontainer.lifecycle.LifecycleState;
015import org.picocontainer.behaviors.Caching;
016import org.picocontainer.containers.AbstractDelegatingMutablePicoContainer;
017
018import java.io.File;
019import java.io.IOException;
020import java.lang.annotation.Annotation;
021import java.lang.reflect.Type;
022import java.net.URL;
023import java.security.AccessController;
024import java.security.CodeSource;
025import java.security.PrivilegedAction;
026import java.security.Permissions;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Enumeration;
030import java.util.HashMap;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Properties;
035import java.util.regex.Pattern;
036import java.util.zip.ZipEntry;
037import java.util.zip.ZipFile;
038
039/**
040 * Default implementation of ClassLoadingPicoContainer.
041 *
042 * @author Paul Hammant
043 * @author Mauro Talevi
044 * @author Michael Rimov
045 */
046@SuppressWarnings("serial")
047public class DefaultClassLoadingPicoContainer extends AbstractDelegatingMutablePicoContainer implements
048        ClassLoadingPicoContainer, ComponentMonitorStrategy {
049
050    /**
051     * Converting Map to allow for primitives to be boxed to Object types.
052     */
053    private static final transient Map<String, String> primitiveNameToBoxedName = new HashMap<String, String>();
054
055    static {
056        primitiveNameToBoxedName.put("int", Integer.class.getName());
057        primitiveNameToBoxedName.put("byte", Byte.class.getName());
058        primitiveNameToBoxedName.put("short", Short.class.getName());
059        primitiveNameToBoxedName.put("long", Long.class.getName());
060        primitiveNameToBoxedName.put("float", Float.class.getName());
061        primitiveNameToBoxedName.put("double", Double.class.getName());
062        primitiveNameToBoxedName.put("boolean", Boolean.class.getName());
063    }
064
065    private final transient List<ClassPathElement> classPathElements = new ArrayList<ClassPathElement>();
066    private final transient ClassLoader parentClassLoader;
067
068    private transient ClassLoader componentClassLoader;
069    private transient boolean componentClassLoaderLocked;
070
071    protected final Map<String, PicoContainer> namedChildContainers = new HashMap<String, PicoContainer>();
072
073    public DefaultClassLoadingPicoContainer(ClassLoader classLoader, ComponentFactory componentFactory, PicoContainer parent) {
074        super(new DefaultPicoContainer(componentFactory, parent));
075        parentClassLoader = classLoader;
076    }
077
078    public DefaultClassLoadingPicoContainer(ClassLoader classLoader, MutablePicoContainer delegate) {
079        super(delegate);
080        parentClassLoader = classLoader;
081
082    }
083
084    public DefaultClassLoadingPicoContainer(ClassLoader classLoader, PicoContainer parent, ComponentMonitor componentMonitor) {
085        super(new DefaultPicoContainer(new Caching(), parent));
086        parentClassLoader = classLoader;
087        ((ComponentMonitorStrategy) getDelegate()).changeMonitor(componentMonitor);
088    }
089
090    public DefaultClassLoadingPicoContainer(ComponentFactory componentFactory) {
091        super(new DefaultPicoContainer(componentFactory, null));
092        parentClassLoader = DefaultClassLoadingPicoContainer.class.getClassLoader();
093    }
094
095    
096    public DefaultClassLoadingPicoContainer(PicoContainer parent) {
097        super(new DefaultPicoContainer(parent));
098        parentClassLoader = DefaultClassLoadingPicoContainer.class.getClassLoader();
099    }
100
101    public DefaultClassLoadingPicoContainer(MutablePicoContainer delegate) {
102        super(delegate);
103        parentClassLoader = DefaultClassLoadingPicoContainer.class.getClassLoader();
104    }
105
106    public DefaultClassLoadingPicoContainer(ClassLoader classLoader) {
107        super(new DefaultPicoContainer());
108        parentClassLoader = classLoader;
109    }
110
111    public DefaultClassLoadingPicoContainer() {
112        super(new DefaultPicoContainer());
113        parentClassLoader = DefaultClassLoadingPicoContainer.class.getClassLoader();
114    }
115
116    public DefaultClassLoadingPicoContainer(ComponentFactory componentFactory, LifecycleStrategy lifecycleStrategy,
117            PicoContainer parent, ClassLoader cl, ComponentMonitor componentMonitor) {
118
119        super(new DefaultPicoContainer(componentFactory, lifecycleStrategy, parent, componentMonitor));
120        parentClassLoader = (cl != null) ? cl : DefaultClassLoadingPicoContainer.class.getClassLoader();
121    }
122
123    protected DefaultClassLoadingPicoContainer createChildContainer() {
124        MutablePicoContainer child = getDelegate().makeChildContainer();
125        DefaultClassLoadingPicoContainer container = new DefaultClassLoadingPicoContainer(getComponentClassLoader(), child);
126        container.changeMonitor(currentMonitor());
127        return container;
128    }
129
130    /**
131     * Propagates the monitor change down the delegate chain if a delegate that implements ComponentMonitorStrategy
132     * exists.  Because of the ComponentMonitorStrategy API, not all delegates can have their API changed.  If
133     * a delegate implementing ComponentMonitorStrategy cannot be found, an exception is thrown.
134     * @throws IllegalStateException if no delegate can be found that implements ComponentMonitorStrategy.
135     * @param monitor the monitor to swap.
136     */
137    public void changeMonitor(ComponentMonitor monitor) {
138        
139        MutablePicoContainer picoDelegate = getDelegate();
140        while (picoDelegate != null) {
141                if (picoDelegate instanceof ComponentMonitorStrategy) {
142                        ((ComponentMonitorStrategy)picoDelegate).changeMonitor(monitor);
143                        return;
144                }
145                
146                if (picoDelegate instanceof AbstractDelegatingMutablePicoContainer) {
147                        picoDelegate = ((AbstractDelegatingMutablePicoContainer)picoDelegate).getDelegate();
148                } else {
149                        break;
150                }
151        }
152        
153        throw new IllegalStateException("Could not find delegate picocontainer that implemented ComponentMonitorStrategy");
154        
155        
156    }
157
158    public ComponentMonitor currentMonitor() {
159        MutablePicoContainer picoDelegate = getDelegate();
160        while (picoDelegate != null) {
161                if (picoDelegate instanceof ComponentMonitorStrategy) {
162                        return ((ComponentMonitorStrategy)picoDelegate).currentMonitor();
163                }
164                
165                if (picoDelegate instanceof AbstractDelegatingMutablePicoContainer) {
166                        picoDelegate = ((AbstractDelegatingMutablePicoContainer)picoDelegate).getDelegate();
167                } else {
168                        break;
169                }
170        }
171        
172        throw new IllegalStateException("Could not find delegate picocontainer that implemented ComponentMonitorStrategy");
173    }
174
175    public final Object getComponent(Object componentKeyOrType) throws PicoException {
176
177        if (componentKeyOrType instanceof ClassName) {
178            componentKeyOrType = loadClass((ClassName) componentKeyOrType);
179        }
180
181        Object instance = getDelegate().getComponent(componentKeyOrType);
182
183        if (instance != null) {
184            return instance;
185        }
186
187        ComponentAdapter<?> componentAdapter = null;
188        if (componentKeyOrType.toString().startsWith("*")) {
189            String candidateClassName = componentKeyOrType.toString().substring(1);
190            Collection<ComponentAdapter<?>> cas = getComponentAdapters();
191            for (ComponentAdapter<?> ca : cas) {
192                Object key = ca.getComponentKey();
193                if (key instanceof Class && candidateClassName.equals(((Class<?>) key).getName())) {
194                    componentAdapter = ca;
195                    break;
196                }
197            }
198        }
199        if (componentAdapter != null) {
200            return componentAdapter.getComponentInstance(this, ComponentAdapter.NOTHING.class);
201        } else {
202            return getComponentInstanceFromChildren(componentKeyOrType);
203        }
204    }
205
206    private Object getComponentInstanceFromChildren(Object componentKey) {
207        String componentKeyPath = componentKey.toString();
208        int ix = componentKeyPath.indexOf('/');
209        if (ix != -1) {
210            String firstElement = componentKeyPath.substring(0, ix);
211            String remainder = componentKeyPath.substring(ix + 1, componentKeyPath.length());
212            Object o = getNamedContainers().get(firstElement);
213            if (o != null) {
214                MutablePicoContainer child = (MutablePicoContainer) o;
215                return child.getComponent(remainder);
216            }
217        }
218        return null;
219    }
220
221    public final MutablePicoContainer makeChildContainer() {
222        return makeChildContainer("containers" + namedChildContainers.size());
223    }
224
225    /**
226     * Makes a child container with the same basic characteristics of
227     * <tt>this</tt> object (ComponentFactory, PicoContainer type, Behavior,
228     * etc)
229     *
230     * @param name the name of the child container
231     * @return The child MutablePicoContainer
232     */
233    public ClassLoadingPicoContainer makeChildContainer(String name) {
234        DefaultClassLoadingPicoContainer child = createChildContainer();
235        MutablePicoContainer parentDelegate = getDelegate();
236        parentDelegate.removeChildContainer(child.getDelegate());
237        parentDelegate.addChildContainer(child);
238        namedChildContainers.put(name, child);
239        return child;
240    }
241
242    public boolean removeChildContainer(PicoContainer child) {
243        boolean result = getDelegate().removeChildContainer(child);
244        Iterator<Map.Entry<String, PicoContainer>> children = namedChildContainers.entrySet().iterator();
245        while (children.hasNext()) {
246            Map.Entry<String, PicoContainer> e = children.next();
247            PicoContainer pc = e.getValue();
248            if (pc == child) {
249                children.remove();
250            }
251        }
252        return result;
253    }
254
255    protected final Map<String, PicoContainer> getNamedContainers() {
256        return namedChildContainers;
257    }
258
259    public ClassPathElement addClassLoaderURL(URL url) {
260        if (componentClassLoaderLocked) {
261            throw new IllegalStateException("ClassLoader URLs cannot be added once this instance is locked");
262        }
263
264        ClassPathElement classPathElement = new ClassPathElement(url);
265        classPathElements.add(classPathElement);
266        return classPathElement;
267    }
268
269    public MutablePicoContainer addComponent(Object implOrInstance) {
270        if (implOrInstance instanceof ClassName) {
271            super.addComponent(loadClass((ClassName) implOrInstance));
272        } else {
273            super.addComponent(implOrInstance);
274        }
275        return this;
276    }
277
278    public MutablePicoContainer addComponent(Object key, Object componentImplementationOrInstance,
279            Parameter... parameters) {
280        super.addComponent(classNameToClassIfApplicable(key),
281                classNameToClassIfApplicable(componentImplementationOrInstance), parameters);
282        return this;
283    }
284
285    private Object classNameToClassIfApplicable(Object key) {
286        if (key instanceof ClassName) {
287            key = loadClass((ClassName) key);
288        }
289        return key;
290    }
291
292    public MutablePicoContainer addAdapter(ComponentAdapter<?> componentAdapter) throws PicoCompositionException {
293        super.addAdapter(componentAdapter);
294        return this;
295    }
296
297    public ClassLoader getComponentClassLoader() {
298        if (componentClassLoader == null) {
299            componentClassLoaderLocked = true;
300            componentClassLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
301                public ClassLoader run() {
302                    return new CustomPermissionsURLClassLoader(getURLs(classPathElements), makePermissions(),
303                            parentClassLoader);
304                }
305            });
306        }
307        return componentClassLoader;
308    }
309
310    public MutablePicoContainer addChildContainer(PicoContainer child) {
311        getDelegate().addChildContainer(child);
312        namedChildContainers.put("containers" + namedChildContainers.size(), child);
313        return this;
314    }
315
316    public ClassLoadingPicoContainer addChildContainer(String name, PicoContainer child) {
317
318        super.addChildContainer(child);
319
320        namedChildContainers.put(name, child);
321        return this;
322    }
323
324    private Class<?> loadClass(final ClassName className) {
325        ClassLoader classLoader = getComponentClassLoader();
326        // this is deliberately not a doPrivileged operation.
327        String cn = getClassName(className.toString());
328        try {
329            return classLoader.loadClass(cn);
330        } catch (ClassNotFoundException e) {
331            throw new PicoClassNotFoundException(cn, e);
332        }
333    }
334
335    private Map<URL, Permissions> makePermissions() {
336        Map<URL, Permissions> permissionsMap = new HashMap<URL, Permissions>();
337        for (ClassPathElement cpe : classPathElements) {
338            Permissions permissionCollection = cpe.getPermissionCollection();
339            permissionsMap.put(cpe.getUrl(), permissionCollection);
340        }
341        return permissionsMap;
342    }
343
344    private URL[] getURLs(List<ClassPathElement> classPathElemelements) {
345        final URL[] urls = new URL[classPathElemelements.size()];
346        for (int i = 0; i < urls.length; i++) {
347            urls[i] = (classPathElemelements.get(i)).getUrl();
348        }
349        return urls;
350    }
351
352    private static String getClassName(String primitiveOrClass) {
353        String fromMap = primitiveNameToBoxedName.get(primitiveOrClass);
354        return fromMap != null ? fromMap : primitiveOrClass;
355    }
356
357    public ComponentAdapter<?> getComponentAdapter(Object componentKey) {
358        Object componentKey2 = componentKey;
359        if (componentKey instanceof ClassName) {
360            componentKey2 = loadClass((ClassName) componentKey);
361        }
362        return super.getComponentAdapter(componentKey2);
363    }
364
365    public MutablePicoContainer change(Properties... properties) {
366        super.change(properties);
367        return this;
368    }
369
370    public MutablePicoContainer as(Properties... properties) {
371        return new AsPropertiesPicoContainer(properties);
372    }
373
374    private class AsPropertiesPicoContainer implements ClassLoadingPicoContainer {
375        private MutablePicoContainer delegate;
376
377        public AsPropertiesPicoContainer(Properties... props) {
378            delegate = DefaultClassLoadingPicoContainer.this.getDelegate().as(props);
379        }
380
381        public ClassPathElement addClassLoaderURL(URL url) {
382            return DefaultClassLoadingPicoContainer.this.addClassLoaderURL(url);
383        }
384
385        public ClassLoader getComponentClassLoader() {
386            return DefaultClassLoadingPicoContainer.this.getComponentClassLoader();
387        }
388
389        public ClassLoadingPicoContainer makeChildContainer(String name) {
390            return DefaultClassLoadingPicoContainer.this.makeChildContainer(name);
391        }
392
393        public ClassLoadingPicoContainer addChildContainer(String name, PicoContainer child) {
394            return (ClassLoadingPicoContainer) DefaultClassLoadingPicoContainer.this.addChildContainer(child);
395        }
396
397        public MutablePicoContainer addComponent(Object componentKey, Object componentImplementationOrInstance,
398                Parameter... parameters) {
399            delegate.addComponent(classNameToClassIfApplicable(componentKey),
400                    classNameToClassIfApplicable(componentImplementationOrInstance), parameters);
401            return DefaultClassLoadingPicoContainer.this;
402        }
403
404        public MutablePicoContainer addComponent(Object implOrInstance) {
405            delegate.addComponent(classNameToClassIfApplicable(implOrInstance));
406            return DefaultClassLoadingPicoContainer.this;
407        }
408
409        public MutablePicoContainer addConfig(String name, Object val) {
410            delegate.addConfig(name, val);
411            return DefaultClassLoadingPicoContainer.this;
412        }
413
414        public MutablePicoContainer addAdapter(ComponentAdapter<?> componentAdapter) {
415            delegate.addAdapter(componentAdapter);
416            return DefaultClassLoadingPicoContainer.this;
417        }
418
419        public ComponentAdapter removeComponent(Object componentKey) {
420            return delegate.removeComponent(componentKey);
421        }
422
423        public ComponentAdapter removeComponentByInstance(Object componentInstance) {
424            return delegate.removeComponentByInstance(componentInstance);
425        }
426
427        public MutablePicoContainer makeChildContainer() {
428            return DefaultClassLoadingPicoContainer.this.makeChildContainer();
429        }
430
431        public MutablePicoContainer addChildContainer(PicoContainer child) {
432            return DefaultClassLoadingPicoContainer.this.addChildContainer(child);
433        }
434
435        public boolean removeChildContainer(PicoContainer child) {
436            return DefaultClassLoadingPicoContainer.this.removeChildContainer(child);
437        }
438
439        public MutablePicoContainer change(Properties... properties) {
440            return DefaultClassLoadingPicoContainer.this.change(properties);
441        }
442
443        public MutablePicoContainer as(Properties... properties) {
444            return new AsPropertiesPicoContainer(properties);
445        }
446
447        public Object getComponent(Object componentKeyOrType) {
448            return DefaultClassLoadingPicoContainer.this.getComponent(componentKeyOrType);
449        }
450
451        public Object getComponent(Object componentKeyOrType, Type into) {
452            return DefaultClassLoadingPicoContainer.this.getComponent(componentKeyOrType, into);
453        }
454
455        public <T> T getComponent(Class<T> componentType) {
456            return DefaultClassLoadingPicoContainer.this.getComponent(componentType);
457        }
458
459        public <T> T getComponent(Class<T> componentType, Class<? extends Annotation> binding) {
460            return DefaultClassLoadingPicoContainer.this.getComponent(componentType, binding);
461        }
462
463        public List<Object> getComponents() {
464            return DefaultClassLoadingPicoContainer.this.getComponents();
465        }
466
467        public PicoContainer getParent() {
468            return DefaultClassLoadingPicoContainer.this.getParent();
469        }
470
471        public ComponentAdapter<?> getComponentAdapter(Object componentKey) {
472            return DefaultClassLoadingPicoContainer.this.getComponentAdapter(componentKey);
473        }
474
475        public <T> ComponentAdapter<T> getComponentAdapter(Class<T> componentType, NameBinding componentNameBinding) {
476            return DefaultClassLoadingPicoContainer.this.getComponentAdapter(componentType, componentNameBinding);
477        }
478
479        public <T> ComponentAdapter<T> getComponentAdapter(Class<T> componentType, Class<? extends Annotation> binding) {
480            return DefaultClassLoadingPicoContainer.this.getComponentAdapter(componentType, binding);
481        }
482
483        public Collection<ComponentAdapter<?>> getComponentAdapters() {
484            return DefaultClassLoadingPicoContainer.this.getComponentAdapters();
485        }
486
487        public <T> List<ComponentAdapter<T>> getComponentAdapters(Class<T> componentType) {
488            return DefaultClassLoadingPicoContainer.this.getComponentAdapters(componentType);
489        }
490
491        public <T> List<ComponentAdapter<T>> getComponentAdapters(Class<T> componentType,
492                Class<? extends Annotation> binding) {
493            return DefaultClassLoadingPicoContainer.this.getComponentAdapters(componentType, binding);
494        }
495
496        public <T> List<T> getComponents(Class<T> componentType) {
497            return DefaultClassLoadingPicoContainer.this.getComponents(componentType);
498        }
499
500        public void accept(PicoVisitor visitor) {
501            DefaultClassLoadingPicoContainer.this.accept(visitor);
502        }
503
504        public void start() {
505            //This implementation does nothing on lifecycle triggers.          
506        }
507
508        public void stop() {
509            //This implementation does nothing on lifecycle triggers.          
510        }
511
512        public void dispose() {
513            //This implementation does nothing on lifecycle triggers.          
514        }
515
516        public void setName(String name) {
517            DefaultClassLoadingPicoContainer.this.setName(name);
518        }
519
520        public void setLifecycleState(LifecycleState lifecycleState) {
521            DefaultClassLoadingPicoContainer.this.setLifecycleState(lifecycleState);
522        }
523        
524        public Converters getConverter() {
525            return DefaultClassLoadingPicoContainer.this.getConverters();
526        }
527
528        /**
529         * {@inheritDoc}
530         * @see org.picocontainer.MutablePicoContainer#getLifecycleState()
531         */
532        public LifecycleState getLifecycleState() {
533            return DefaultClassLoadingPicoContainer.this.getLifecycleState();
534        }
535
536        /**
537         * {@inheritDoc}
538         * @see org.picocontainer.MutablePicoContainer#getName()
539         */
540        public String getName() {
541            return DefaultClassLoadingPicoContainer.this.getName();
542        }
543
544    }
545
546    public int visit(ClassName thisClassesPackage, String regex, boolean recursive, ClassVisitor classNameVisitor) {
547        Class clazz = loadClass(thisClassesPackage);
548        /* File Seperator of '\\' can cause bogus results in Windows -- So we keep it to forward slash since Windows
549         * can handle it.  
550         * -MR
551         */
552        String pkgName = clazz.getPackage().getName().replace(".", "/");  
553        CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
554        if(codeSource == null) {
555            throw new PicoCompositionException("no codesource for " + thisClassesPackage);
556        }
557        String codeSourceRoot = codeSource.getLocation().getFile();
558        String fileName = codeSourceRoot + File.separator + pkgName;
559        File file = new File(fileName);
560        Pattern compiledPattern = Pattern.compile(regex);
561        if (file.exists()) {
562            if (file.isFile()) {
563                file = file.getParentFile();
564            }
565            return visit(file, pkgName, compiledPattern, recursive, classNameVisitor);
566        } else {
567            return visit(pkgName, codeSourceRoot, compiledPattern, recursive, classNameVisitor);
568        }
569    }
570
571    public int visit(String pkgName, String codeSourceRoot, Pattern compiledPattern, boolean recursive, ClassVisitor classNameVisitor) {
572        int found = 0;
573        try {
574            ZipFile zip = new ZipFile(new File(codeSourceRoot));
575            for (Enumeration e = zip.entries(); e.hasMoreElements();) {
576                ZipEntry entry = (ZipEntry) e.nextElement();
577                String entryName = entry.getName();
578                if (entryName.startsWith(pkgName) && entryName.endsWith(".class")) {
579                    String name =  entryName.substring(pkgName.length()+1);
580                    if (name.endsWith("XStream.class")) {
581                        System.out.println();
582                    }
583                    int length = name.split("/").length;
584                    if (length == 1 || recursive) {
585                        found = visit(pkgName, compiledPattern, classNameVisitor, found, entryName.replace("/","."), null);
586                    }
587                }
588            }
589        } catch (IOException e) {
590            e.printStackTrace();
591        }
592        return found;
593    }
594
595    public int visit(File pkgDir, String pkgName, Pattern pattern, boolean recursive, ClassVisitor classNameVisitor) {
596        int found = 0;
597        File files[] = pkgDir.listFiles();
598        if(files != null) {
599            for (File file : files) {
600                if (file.isDirectory()) {
601                    if (recursive) {
602                        found = found + visit(file, pkgName, pattern, recursive, classNameVisitor);
603                    }
604                } else {
605                    found = visit(pkgName, pattern, classNameVisitor, found, file.getName(), 
606                        file.getAbsolutePath().replace(File.separatorChar, '/') );
607                }
608            }
609        }
610        return found;
611    }
612
613    private int visit(String pkgName, Pattern pattern, ClassVisitor classNameVisitor, int foundSoFar, String fileName, String absolutePath) {
614        boolean matches = pattern.matcher(fileName).matches();
615        if (matches) {
616            if (absolutePath != null) {
617                String fqn = absolutePath.substring(absolutePath.indexOf(pkgName));                
618                fileName = fqn.substring(0, fqn.indexOf(".class")).replace('/', '.');;
619            } else {
620                fileName = fileName.substring(0, fileName.indexOf(".class"));
621            }
622            classNameVisitor.classFound(loadClass(new ClassName(fileName)));
623            foundSoFar++;
624        }
625        return foundSoFar;
626    }
627
628    public interface ClassVisitor {
629         void classFound(Class clazz);
630    }
631
632}