001    /*
002     * Copyright 2002-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.clazz;
017    
018    import java.lang.ref.WeakReference;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.clazz.common.ClazzElementSupport;
026    import org.apache.commons.clazz.common.ExtendedClazzLoaderFactory;
027    import org.apache.commons.clazz.common.StandardClazzLoaderFactory;
028    
029    /**
030     * 
031     * 
032     * @author <a href="mailto:scolebourne@apache.org">Stephen Colebourne</a>
033     * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
034     * @version $Id: Clazz.java 155436 2005-02-26 13:17:48Z dirkv $
035     */
036    public abstract class Clazz extends ClazzElementSupport
037            implements ClazzElement
038    {
039        private ClazzLoader loader;
040        private String name;
041        private List listeners;
042    
043        /**
044         * The name of the standard clazz model. The value of the constant is
045         * "Standard".
046         */
047        public static final String STANDARD_CLAZZ_MODEL =
048            StandardClazzLoaderFactory.MODEL;
049    
050        /**
051         * The name of the extended clazz model. The value of the constant is
052         * "Extended".
053         */
054        public static final String EXTENDED_CLAZZ_MODEL =
055            ExtendedClazzLoaderFactory.MODEL;
056            
057        private static String defaultClazzModel = EXTENDED_CLAZZ_MODEL;
058        private static ClazzLoader defaultClazzLoader;
059        private static Map clazzLoaderFactories = new HashMap();
060        
061        static {
062            addClazzLoaderFactory(StandardClazzLoaderFactory.FACTORY);
063            addClazzLoaderFactory(ExtendedClazzLoaderFactory.FACTORY);
064        }
065            
066        /**
067         * Register a clazz loader factory, which manages ClazzLoaders, which manage
068         * Clazzes.
069         * 
070         * @param clazzLoaderFactory
071         */
072        public static void addClazzLoaderFactory(ClazzLoaderFactory factory) {
073            clazzLoaderFactories.put(factory.getClazzModel(), factory);
074        }
075        
076        /**
077         * Returns a ClazzLoaderFactory registered for the supplied model. We can
078         * have multiple clazz loader factories implementing different models (e.g.
079         * Standard JavaBeans, Extended JavaBeans etc).
080         * 
081         * @param model is the type of the model we need
082         * @return ClazzLoaderFactory
083         */
084        public static ClazzLoaderFactory getClazzLoaderFactory(String model) {
085            return (ClazzLoaderFactory) clazzLoaderFactories.get(model);
086        }
087        
088        /**
089         * Select the default clazz model.
090         * 
091         * @param defaultClazzModel
092         */
093        public static void setDefaultClazzModel(String defaultClazzModel) {
094            Clazz.defaultClazzModel = defaultClazzModel;
095        }
096    
097        /**
098         * Returns the name of the default clazz model.
099         */
100        public static String getDefaultClazzModel() {
101            return defaultClazzModel;
102        }
103    
104        /**
105         * Returns a clazz loader for the specified model.  The provided ClassLoader
106         * can be used by the ClazzLoader for reflection.
107         * 
108         * @param model
109         * @param classLoader
110         * @return ClazzLoader
111         */
112        public static ClazzLoader getClazzLoader(
113            String model,
114            ClassLoader classLoader) 
115        {
116            if (classLoader == null) {
117                classLoader = Clazz.class.getClassLoader();
118            }
119            return getClazzLoaderFactory(model).getClazzLoader(classLoader);
120        }
121    
122        /**
123         * Returns the ClazzLoader for the default clazz model.
124         */    
125        public static ClazzLoader getDefaultClazzLoader(ClassLoader classLoader) {
126            return getClazzLoader(getDefaultClazzModel(), classLoader);
127        }
128        
129        /**
130         * Uses the clazz loader for the specified model to obtain the Clazz for the
131         * supplied object.
132         */
133        public static Clazz getClazz(Object instance, String model) {
134            if (instance == null) {
135                throw new NullPointerException();
136            }
137    
138            ClassLoader classLoader = instance.getClass().getClassLoader();
139            Clazz clazz = getClazzLoader(model, classLoader).getClazz(instance);
140            if (clazz == null) {
141                throw new ClazzNotFoundException(instance.getClass().getName());
142            }
143            return clazz;
144        }
145        
146        /**
147         * Uses the default clazz loader to obtain the Clazz for the supplied
148         * object.
149         */
150        public static Clazz getClazz(Object instance) {
151            return getClazz(instance, getDefaultClazzModel());
152        }    
153            
154        /**
155         * Keep the constructor protected or private, we want Clazzes to 
156         * be created by the ClazzLoaders only.
157         *  
158         * @param loader is the owning model clazz loader
159         * @param name must be unique within the model
160         */
161        protected Clazz(ClazzLoader modelClazzLoader, String name) {
162            this.loader = modelClazzLoader;
163            this.name = name;
164        }
165        
166        /**
167         * Every Clazz belongs to one and only one ClazzLoader. Its name is unique
168         * within that loader.
169         */
170        public ClazzLoader getClazzLoader() {
171            return loader;
172        }
173        
174        /**
175         * Returns the name of the Clazz. It is ok for the same name to be present
176         * in different ClazzLoaders, except CachingClazzLoaders.
177         */
178        public String getName() {
179            return name;
180        }
181        
182        /**
183         * Gets the package name.
184         * 
185         * @return the package name
186         */
187        public String getPackageName() {
188            if (name == null) {
189                return null;
190            }
191            int index = name.lastIndexOf('.');
192            if (index == -1) {
193                return "";
194            }
195            
196            return name.substring(0, index);
197        }
198    
199        /**
200         * Gets the class name (without the package).
201         * 
202         * @return the class name (without the package)
203         */
204        public String getShortClassName() {
205            if (name == null) {
206                return null;
207            }
208            int index = name.lastIndexOf('.');
209            if (index == -1) {
210                return name;
211            }
212            
213            return name.substring(index + 1);
214        }
215    
216        /**
217         * Returns the class of instances created by the <code>newInstance()</code>
218         * method.
219         */    
220        public abstract Class getInstanceClass();
221        
222        /**
223         * Returns the superclazz for this Clazz, or null if there is none.
224         */
225        public abstract Clazz getSuperclazz();
226        
227        /**
228         * Returns true if the supplied clazz is either the same or a subclazz of
229         * this clazz.
230         */
231        public boolean isAssignableFrom(Clazz clazz) {
232            if (clazz == this) {
233                return true;
234            }
235            Clazz superclazz = clazz.getSuperclazz();
236            if (superclazz != null) {
237                return isAssignableFrom(superclazz);
238            }
239            return false;
240        }
241        
242        /**
243         * Returns properties declared by this Clazz, not its superclazzes
244         */
245        public abstract List getDeclaredProperties();
246    
247        /**
248         * Returns all properties for this clazz, including those declared
249         * by superclazzes.
250         */
251        public abstract List getProperties();
252    
253        /**
254         * Returns the properties that match the Predicate.
255         */
256    //    public abstract List getProperties(Predicate predicate);
257    
258        /**
259         * Returns a ClazzProperty for the given name
260         */
261        public abstract ClazzProperty getProperty(String name);
262    
263        /**
264         * Returns all Operations for this clazz.
265         */
266        public abstract List getOperations();
267    
268        /**
269         * Returns all Operations declared by this clazz, not its superclazzes.
270         */
271        public abstract List getDeclaredOperations();
272    
273        /**
274         * Returns the Operations that match the Predicate.
275         */
276    //    public abstract List getOperations(Predicate predicate);
277    
278        /**
279         * Returns the Operation for the given signature. The signature should be
280         * formatted as follows: <code>"name(paramClazzName1,...)"</code>
281         */
282        public abstract ClazzOperation getOperation(String signature);
283    
284        /**
285         * Returns all InstanceFactories for this clazz.
286         */
287        public abstract List getInstanceFactories();
288    
289        /**
290         * Returns the InstanceFactories that match the Predicate.
291         */
292    //    public abstract List getInstanceFactories(Predicate predicate);
293    
294        /**
295         * Returns ClazzInstanceFactory for the given signature. The signature
296         * should be formatted as follows: <code>"(paramClazzName1,...)"</code>. You
297         * can pass <code>null</code> in place of <code>"()"</code>. 
298         */
299        public abstract ClazzInstanceFactory getInstanceFactory(String signature);
300        
301        /**
302         * Creates a new instance of this Clazz using the InstanceFactory that takes
303         * no parameters.
304         */
305        public Object newInstance() {
306            ClazzInstanceFactory factory = getInstanceFactory("()");
307            if (factory == null) {
308                // @todo: define exception
309                throw new RuntimeException(
310                    "No such instance factory " + getName() + " ()");
311            }
312            return factory.newInstance(null);
313        }
314    
315        /**
316         * Creates a new instance of this Clazz using the InstanceFactory
317         * with the specified signature.
318         */
319        public Object newInstance(String signature, Object[] parameters) {
320            ClazzInstanceFactory factory = getInstanceFactory(signature);
321            if (factory == null) {
322                // @todo: define exception
323                throw new RuntimeException(
324                    "No such instance factory "
325                        + getName()
326                        + (signature == null ? "" : " " + signature));
327            }
328            return factory.newInstance(parameters);
329        }
330        
331        
332        //--- Notification mechanism - verbose, but what are you gonna do?
333        
334        public void addClazzChangeListener(ClazzChangeListener listener) {
335            if (listeners == null) {
336                listeners = new ArrayList();
337            }
338            
339            // We don't want to prevent the subclasses from being garbage
340            // collection just because they are listening to their superclasses. 
341            WeakReference reference = new WeakReference(listener);
342            listeners.add(reference);
343        }
344        
345        public void removeClazzChangeListener(ClazzChangeListener listener) {
346            if (listeners == null) {
347                return;
348            }
349            for (Iterator iter = listeners.iterator(); iter.hasNext();) {
350                WeakReference reference = (WeakReference) iter.next();
351                if (reference.get() == listener) {
352                    iter.remove();
353                    break;
354                }
355            }
356        }
357            
358        private static final WeakReference[] CLAZZ_CHANGE_LISTENER_ARRAY =
359                new WeakReference[0];
360    
361        private abstract static class Notifier {
362            void fire(Clazz clazz, Object parameter) {
363                if (clazz.listeners != null && clazz.listeners.size() != 0) {
364                    WeakReference listenerArray[] =
365                        (WeakReference[]) clazz.listeners.toArray(
366                            CLAZZ_CHANGE_LISTENER_ARRAY);
367                    for (int i = 0; i < listenerArray.length; i++) {
368                        fire(
369                            clazz,
370                            (ClazzChangeListener) listenerArray[i].get(),
371                            parameter);
372                    }
373                }
374            }
375            
376            abstract void fire(
377                Clazz clazz, ClazzChangeListener listener, Object parameter);
378        }
379        
380        private static final Notifier PROPERTY_ADDED_NOTIFIER = new Notifier() {
381            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
382                listener.propertyAdded(clazz, (ClazzProperty) parameter);
383            }
384        };
385                        
386        protected void firePropertyAdded(ClazzProperty property) {
387            PROPERTY_ADDED_NOTIFIER.fire(this, property);
388        }
389    
390        private static final Notifier PROPERTY_REMOVED_NOTIFIER = new Notifier() {
391            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
392                listener.propertyRemoved(clazz, (ClazzProperty) parameter);
393            }
394        };
395    
396        protected void firePropertyRemoved(ClazzProperty property) {
397            PROPERTY_REMOVED_NOTIFIER.fire(this, property);
398        }
399    
400        private static final Notifier OPERATION_ADDED_NOTIFIER = new Notifier() {
401            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
402                listener.operationAdded(clazz, (ClazzOperation) parameter);
403            }
404        };
405    
406        protected void fireOperationAdded(ClazzOperation operation) {
407            OPERATION_ADDED_NOTIFIER.fire(this, operation);
408        }
409    
410        private static final Notifier OPERATION_REMOVED_NOTIFIER = new Notifier() {
411            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
412                listener.operationRemoved(clazz, (ClazzOperation) parameter);
413            }
414        };
415    
416        protected void fireOperationRemoved(ClazzOperation operation) {
417            OPERATION_REMOVED_NOTIFIER.fire(this, operation);
418        }
419    
420    
421        private static final Notifier FACTORY_ADDED_NOTIFIER = new Notifier() {
422            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
423                listener.instanceFactoryAdded(
424                    clazz,
425                    (ClazzInstanceFactory) parameter);
426            }
427        };
428    
429        protected void fireInstanceFactoryAdded(ClazzInstanceFactory factory) {
430            FACTORY_ADDED_NOTIFIER.fire(this, factory);
431        }
432    
433        private static final Notifier FACTORY_REMOVED_NOTIFIER = new Notifier() {
434            void fire(Clazz clazz, ClazzChangeListener listener, Object parameter) {
435                listener.instanceFactoryRemoved(
436                    clazz,
437                    (ClazzInstanceFactory) parameter);
438            }
439        };
440    
441        protected void fireInstanceFactoryRemoved(ClazzInstanceFactory factory) {
442            FACTORY_REMOVED_NOTIFIER.fire(this, factory);
443        }
444    
445        //--- End notification mechanism
446        
447        
448        /**
449         * Creates a signature string out of an operation or instance factory name
450         * and parameter types.
451         */
452        public static String constructSignature(String name, Class[] arguments) {
453            StringBuffer buffer = new StringBuffer();
454            if (name != null) {
455                buffer.append(name);
456            }
457            buffer.append('(');
458            if (arguments != null) {
459                for (int i = 0; i < arguments.length; i++) {
460                    if (i != 0) {
461                        buffer.append(',');
462                    }
463                    buffer.append(getCanonicalClassName(arguments[i]));
464                }
465            }
466            buffer.append(')');
467            return buffer.toString();
468        }
469    
470        /**
471         * Creates a signature string out of an operation or instance factory name
472         * and parameter types.
473         */
474        public static String constructSignature(String name, Clazz[] arguments) {
475            StringBuffer buffer = new StringBuffer();
476            if (name != null) {
477                buffer.append(name);
478            }
479            buffer.append('(');
480            if (arguments != null) {
481                for (int i = 0; i < arguments.length; i++) {
482                    if (i != 0) {
483                        buffer.append(',');
484                    }
485                    buffer.append(arguments[i].getName());
486                }
487            }
488            buffer.append(')');
489            return buffer.toString();
490        }
491        
492        /**
493         * Produces a nice type name for a classes representing an array, e.g. "[I"
494         * is shown as "int[]". For non-array types returns the regular type name.
495         */
496        public static String getCanonicalClassName(Class javaClass) {
497            /*
498             * This is basically a remake of java.lang.reflect.Field.getTypeName
499             * (Class), which cannot be used directly as it is declared with the
500             * default scope.
501             */
502            if (javaClass.isArray()) {
503                return getCanonicalClassName(javaClass.getComponentType()) + "[]";
504            }
505            return javaClass.getName();
506        }
507    }