KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > aop > framework > ProxyFactoryBean


1 /*
2  * Copyright 2002-2007 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.aop.framework;
18
19 import java.util.ArrayList JavaDoc;
20 import java.util.Collections JavaDoc;
21 import java.util.HashMap JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24 import java.util.Map JavaDoc;
25
26 import org.aopalliance.aop.Advice;
27 import org.aopalliance.intercept.Interceptor;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 import org.springframework.aop.Advisor;
32 import org.springframework.aop.TargetSource;
33 import org.springframework.aop.framework.adapter.AdvisorAdapterRegistry;
34 import org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
35 import org.springframework.aop.framework.adapter.UnknownAdviceTypeException;
36 import org.springframework.aop.target.SingletonTargetSource;
37 import org.springframework.beans.BeansException;
38 import org.springframework.beans.factory.BeanClassLoaderAware;
39 import org.springframework.beans.factory.BeanFactory;
40 import org.springframework.beans.factory.BeanFactoryAware;
41 import org.springframework.beans.factory.BeanFactoryUtils;
42 import org.springframework.beans.factory.FactoryBean;
43 import org.springframework.beans.factory.ListableBeanFactory;
44 import org.springframework.core.OrderComparator;
45 import org.springframework.util.ClassUtils;
46 import org.springframework.util.ObjectUtils;
47
48 /**
49  * {@link org.springframework.beans.factory.FactoryBean} implementation that builds an
50  * AOP proxy based on beans in Spring {@link org.springframework.beans.factory.BeanFactory}.
51  *
52  * <p>{@link org.aopalliance.intercept.MethodInterceptor MethodInterceptors} and
53  * {@link org.springframework.aop.Advisor Advisors} are identified by a list of bean
54  * names in the current bean factory, specified through the "interceptorNames" property.
55  * The last entry in the list can be the name of a target bean or a
56  * {@link org.springframework.aop.TargetSource}; however, it is normally preferable
57  * to use the "targetName"/"target"/"targetSource" properties instead.
58  *
59  * <p>Global interceptors and advisors can be added at the factory level. The specified
60  * ones are expanded in an interceptor list where an "xxx*" entry is included in the
61  * list, matching the given prefix with the bean names (e.g. "global*" would match
62  * both "globalBean1" and "globalBean2", "*" all defined interceptors). The matching
63  * interceptors get applied according to their returned order value, if they implement
64  * the {@link org.springframework.core.Ordered} interface.
65  *
66  * <p>Creates a JDK proxy when proxy interfaces are given, and a CGLIB proxy for the
67  * actual target class if not. Note that the latter will only work if the target class
68  * does not have final methods, as a dynamic subclass will be created at runtime.
69  *
70  * <p>It's possible to cast a proxy obtained from this factory to {@link Advised},
71  * or to obtain the ProxyFactoryBean reference and programmatically manipulate it.
72  * This won't work for existing prototype references, which are independent. However,
73  * it will work for prototypes subsequently obtained from the factory. Changes to
74  * interception will work immediately on singletons (including existing references).
75  * However, to change interfaces or target it's necessary to obtain a new instance
76  * from the factory. This means that singleton instances obtained from the factory
77  * do not have the same object identity. However, they do have the same interceptors
78  * and target, and changing any reference will change all objects.
79  *
80  * @author Rod Johnson
81  * @author Juergen Hoeller
82  * @see #setInterceptorNames
83  * @see #setProxyInterfaces
84  * @see org.aopalliance.intercept.MethodInterceptor
85  * @see org.springframework.aop.Advisor
86  * @see Advised
87  */

88 public class ProxyFactoryBean extends ProxyCreatorSupport
89         implements FactoryBean, BeanClassLoaderAware, BeanFactoryAware {
90
91     /**
92      * This suffix in a value in an interceptor list indicates to expand globals.
93      */

94     public static final String JavaDoc GLOBAL_SUFFIX = "*";
95
96
97     protected final Log logger = LogFactory.getLog(getClass());
98
99     private String JavaDoc[] interceptorNames;
100     
101     private String JavaDoc targetName;
102
103     private boolean autodetectInterfaces = true;
104
105     private boolean singleton = true;
106
107     private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
108
109     /**
110      * Indicates whether the proxy should be frozen before creation.
111      */

112     private boolean freezeProxy = false;
113
114     private ClassLoader JavaDoc beanClassLoader = ClassUtils.getDefaultClassLoader();
115
116     /**
117      * Owning bean factory, which cannot be changed after this
118      * object is initialized.
119      */

120     private BeanFactory beanFactory;
121
122     /** Whether the advisor chain has already been initialized */
123     private boolean advisorChainInitialized = false;
124
125     /** If this is a singleton, the cached singleton proxy instance */
126     private Object JavaDoc singletonInstance;
127
128
129     /**
130      * Set the names of the interfaces we're proxying. If no interface
131      * is given, a CGLIB for the actual class will be created.
132      * <p>This is essentially equivalent to the "setInterfaces" method,
133      * but mirrors TransactionProxyFactoryBean's "setProxyInterfaces".
134      * @see #setInterfaces
135      * @see AbstractSingletonProxyFactoryBean#setProxyInterfaces
136      */

137     public void setProxyInterfaces(Class JavaDoc[] proxyInterfaces) throws ClassNotFoundException JavaDoc {
138         setInterfaces(proxyInterfaces);
139     }
140
141     /**
142      * Set the list of Advice/Advisor bean names. This must always be set
143      * to use this factory bean in a bean factory.
144      * <p>The referenced beans should be of type Interceptor, Advisor or Advice
145      * The last entry in the list can be the name of any bean in the factory.
146      * If it's neither an Advice nor an Advisor, a new SingletonTargetSource
147      * is added to wrap it. Such a target bean cannot be used if the "target"
148      * or "targetSource" or "targetName" property is set, in which case the
149      * "interceptorNames" array must contain only Advice/Advisor bean names.
150      * @see org.aopalliance.intercept.MethodInterceptor
151      * @see org.springframework.aop.Advisor
152      * @see org.aopalliance.aop.Advice
153      * @see org.springframework.aop.target.SingletonTargetSource
154      */

155     public void setInterceptorNames(String JavaDoc[] interceptorNames) {
156         this.interceptorNames = interceptorNames;
157     }
158
159     /**
160      * Set the name of the target bean. This is an alternative to specifying
161      * the target name at the end of the "interceptorNames" array.
162      * <p>You can also specify a target object or a TargetSource object
163      * directly, via the "target"/"targetSource" property, respectively.
164      * @see #setInterceptorNames(String[])
165      * @see #setTarget(Object)
166      * @see #setTargetSource(org.springframework.aop.TargetSource)
167      */

168     public void setTargetName(String JavaDoc targetName) {
169         this.targetName = targetName;
170     }
171
172     /**
173      * Set whether to autodetect proxy interfaces if none specified.
174      * <p>Default is "true". Turn this flag off to create a CGLIB
175      * proxy for the full target class if no interfaces specified.
176      * @see #setProxyTargetClass
177      */

178     public void setAutodetectInterfaces(boolean autodetectInterfaces) {
179         this.autodetectInterfaces = autodetectInterfaces;
180     }
181
182     /**
183      * Set the value of the singleton property. Governs whether this factory
184      * should always return the same proxy instance (which implies the same target)
185      * or whether it should return a new prototype instance, which implies that
186      * the target and interceptors may be new instances also, if they are obtained
187      * from prototype bean definitions. This allows for fine control of
188      * independence/uniqueness in the object graph.
189      */

190     public void setSingleton(boolean singleton) {
191         this.singleton = singleton;
192     }
193
194     /**
195      * Specify the AdvisorAdapterRegistry to use.
196      * Default is the global AdvisorAdapterRegistry.
197      * @see org.springframework.aop.framework.adapter.GlobalAdvisorAdapterRegistry
198      */

199     public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) {
200         this.advisorAdapterRegistry = advisorAdapterRegistry;
201     }
202
203     public void setFrozen(boolean frozen) {
204         this.freezeProxy = frozen;
205     }
206
207     public void setBeanClassLoader(ClassLoader JavaDoc classLoader) {
208         this.beanClassLoader = classLoader;
209     }
210
211     public void setBeanFactory(BeanFactory beanFactory) {
212         this.beanFactory = beanFactory;
213         checkInterceptorNames();
214     }
215
216
217     /**
218      * Return a proxy. Invoked when clients obtain beans from this factory bean.
219      * Create an instance of the AOP proxy to be returned by this factory.
220      * The instance will be cached for a singleton, and create on each call to
221      * <code>getObject()</code> for a proxy.
222      * @return a fresh AOP proxy reflecting the current state of this factory
223      */

224     public Object JavaDoc getObject() throws BeansException {
225         initializeAdvisorChain();
226         if (isSingleton()) {
227             return getSingletonInstance();
228         }
229         else {
230             if (this.targetName == null) {
231                 logger.warn("Using non-singleton proxies with singleton targets is often undesirable." +
232                         "Enable prototype proxies by setting the 'targetName' property.");
233             }
234             return newPrototypeInstance();
235         }
236     }
237
238     /**
239      * Return the type of the proxy. Will check the singleton instance if
240      * already created, else fall back to the proxy interface (in case of just
241      * a single one), the target bean type, or the TargetSource's target class.
242      * @see org.springframework.aop.TargetSource#getTargetClass
243      */

244     public Class JavaDoc getObjectType() {
245         synchronized (this) {
246             if (this.singletonInstance != null) {
247                 return this.singletonInstance.getClass();
248             }
249         }
250         Class JavaDoc[] ifcs = getProxiedInterfaces();
251         if (ifcs.length == 1) {
252             return ifcs[0];
253         }
254         else if (ifcs.length > 1) {
255             return createCompositeInterface(ifcs);
256         }
257         else if (this.targetName != null && this.beanFactory != null) {
258             return this.beanFactory.getType(this.targetName);
259         }
260         else {
261             return getTargetClass();
262         }
263     }
264
265     public boolean isSingleton() {
266         return this.singleton;
267     }
268
269
270     /**
271      * Create a composite interface Class for the given interfaces,
272      * implementing the given interfaces in one single Class.
273      * <p>The default implementation builds a JDK proxy class for the
274      * given interfaces.
275      * @param interfaces the interfaces to merge
276      * @return the merged interface as Class
277      * @see java.lang.reflect.Proxy#getProxyClass
278      */

279     protected Class JavaDoc createCompositeInterface(Class JavaDoc[] interfaces) {
280         return ClassUtils.createCompositeInterface(interfaces, this.beanClassLoader);
281     }
282
283     /**
284      * Return the singleton instance of this class's proxy object,
285      * lazily creating it if it hasn't been created already.
286      * @return the shared singleton proxy
287      */

288     private synchronized Object JavaDoc getSingletonInstance() {
289         if (this.singletonInstance == null) {
290             this.targetSource = freshTargetSource();
291             if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
292                 // Rely on AOP infrastructure to tell us what interfaces to proxy.
293
setInterfaces(ClassUtils.getAllInterfacesForClass(this.targetSource.getTargetClass()));
294             }
295             // Initialize the shared singleton instance.
296
super.setFrozen(this.freezeProxy);
297             this.singletonInstance = getProxy(createAopProxy());
298         }
299         return this.singletonInstance;
300     }
301
302     /**
303      * Create a new prototype instance of this class's created proxy object,
304      * backed by an independent AdvisedSupport configuration.
305      * @return a totally independent proxy, whose advice we may manipulate in isolation
306      */

307     private synchronized Object JavaDoc newPrototypeInstance() {
308         // In the case of a prototype, we need to give the proxy
309
// an independent instance of the configuration.
310
// In this case, no proxy will have an instance of this object's configuration,
311
// but will have an independent copy.
312
if (logger.isTraceEnabled()) {
313             logger.trace("Creating copy of prototype ProxyFactoryBean config: " + this);
314         }
315
316         ProxyCreatorSupport copy = new ProxyCreatorSupport(getAopProxyFactory());
317         // The copy needs a fresh advisor chain, and a fresh TargetSource.
318
TargetSource targetSource = freshTargetSource();
319         copy.copyConfigurationFrom(this, targetSource, freshAdvisorChain());
320         if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
321             // Rely on AOP infrastructure to tell us what interfaces to proxy.
322
copy.setInterfaces(ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass()));
323         }
324         copy.setFrozen(this.freezeProxy);
325
326         if (logger.isTraceEnabled()) {
327             logger.trace("Using ProxyCreatorSupport copy: " + copy);
328         }
329         return getProxy(copy.createAopProxy());
330     }
331
332     /**
333      * Return the proxy object to expose.
334      * <p>The default implementation uses a <code>getProxy</code> call with
335      * the factory's bean class loader. Can be overridden to specify a
336      * custom class loader.
337      * @param aopProxy the prepared AopProxy instance to get the proxy from
338      * @return the proxy object to expose
339      * @see AopProxy#getProxy(ClassLoader)
340      */

341     protected Object JavaDoc getProxy(AopProxy aopProxy) {
342         return aopProxy.getProxy(this.beanClassLoader);
343     }
344
345     /**
346      * Check the interceptorNames list whether it contains a target name as final element.
347      * If found, remove the final name from the list and set it as targetName.
348      */

349     private void checkInterceptorNames() {
350         if (!ObjectUtils.isEmpty(this.interceptorNames)) {
351             String JavaDoc finalName = this.interceptorNames[this.interceptorNames.length - 1];
352             if (this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
353                 // The last name in the chain may be an Advisor/Advice or a target/TargetSource.
354
// Unfortunately we don't know; we must look at type of the bean.
355
if (!finalName.endsWith(GLOBAL_SUFFIX) && !isNamedBeanAnAdvisorOrAdvice(finalName)) {
356                     // Must be an interceptor.
357
this.targetName = finalName;
358                     if (logger.isDebugEnabled()) {
359                         logger.debug("Bean with name '" + finalName + "' concluding interceptor chain " +
360                                 "is not an advisor class: treating it as a target or TargetSource");
361                     }
362                     String JavaDoc[] newNames = new String JavaDoc[this.interceptorNames.length - 1];
363                     System.arraycopy(this.interceptorNames, 0, newNames, 0, newNames.length);
364                     this.interceptorNames = newNames;
365                 }
366             }
367         }
368     }
369
370     /**
371      * Look at bean factory metadata to work out whether this bean name,
372      * which concludes the interceptorNames list, is an Advisor or Advice,
373      * or may be a target.
374      * @param beanName bean name to check
375      * @return true if it's an Advisor or Advice
376      */

377     private boolean isNamedBeanAnAdvisorOrAdvice(String JavaDoc beanName) {
378         Class JavaDoc namedBeanClass = this.beanFactory.getType(beanName);
379         if (namedBeanClass != null) {
380             return Advisor.class.isAssignableFrom(namedBeanClass) ||
381                     Advice.class.isAssignableFrom(namedBeanClass);
382         }
383         // Treat it as an Advisor if we can't tell.
384
return true;
385     }
386
387     /**
388      * Create the advisor (interceptor) chain. Aadvisors that are sourced
389      * from a BeanFactory will be refreshed each time a new prototype instance
390      * is added. Interceptors added programmatically through the factory API
391      * are unaffected by such changes.
392      */

393     private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
394         if (this.advisorChainInitialized) {
395             return;
396         }
397
398         if (!ObjectUtils.isEmpty(this.interceptorNames)) {
399
400             // Globals can't be last unless we specified a targetSource using the property...
401
if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) &&
402                     this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) {
403                 throw new AopConfigException("Target required after globals");
404             }
405
406             // Materialize interceptor chain from bean names.
407
for (int i = 0; i < this.interceptorNames.length; i++) {
408                 String JavaDoc name = this.interceptorNames[i];
409                 if (logger.isTraceEnabled()) {
410                     logger.trace("Configuring advisor or advice '" + name + "'");
411                 }
412
413                 if (name.endsWith(GLOBAL_SUFFIX)) {
414                     if (!(this.beanFactory instanceof ListableBeanFactory)) {
415                         throw new AopConfigException(
416                                 "Can only use global advisors or interceptors with a ListableBeanFactory");
417                     }
418                     addGlobalAdvisor((ListableBeanFactory) this.beanFactory,
419                             name.substring(0, name.length() - GLOBAL_SUFFIX.length()));
420                 }
421
422                 else {
423                     // If we get here, we need to add a named interceptor.
424
// We must check if it's a singleton or prototype.
425
Object JavaDoc advice = null;
426                     if (this.singleton || this.beanFactory.isSingleton(this.interceptorNames[i])) {
427                         // Add the real Advisor/Advice to the chain.
428
advice = this.beanFactory.getBean(this.interceptorNames[i]);
429                     }
430                     else {
431                         // It's a prototype Advice or Advisor: replace with a prototype.
432
// Avoid unnecessary creation of prototype bean just for advisor chain initialization.
433
advice = new PrototypePlaceholderAdvisor(interceptorNames[i]);
434                     }
435                     addAdvisorOnChainCreation(advice, this.interceptorNames[i]);
436                 }
437             }
438         }
439
440         this.advisorChainInitialized = true;
441     }
442
443
444     /**
445      * Return an independent advisor chain.
446      * We need to do this every time a new prototype instance is returned,
447      * to return distinct instances of prototype Advisors and Advices.
448      */

449     private List JavaDoc freshAdvisorChain() {
450         Advisor[] advisors = getAdvisors();
451         List JavaDoc freshAdvisors = new ArrayList JavaDoc(advisors.length);
452
453         for (int i = 0; i < advisors.length; i++) {
454             if (advisors[i] instanceof PrototypePlaceholderAdvisor) {
455                 PrototypePlaceholderAdvisor pa = (PrototypePlaceholderAdvisor) advisors[i];
456                 if (logger.isDebugEnabled()) {
457                     logger.debug("Refreshing bean named '" + pa.getBeanName() + "'");
458                 }
459                 // Replace the placeholder with a fresh prototype instance resulting
460
// from a getBean() lookup
461
Object JavaDoc bean = this.beanFactory.getBean(pa.getBeanName());
462                 Advisor refreshedAdvisor = namedBeanToAdvisor(bean);
463                 freshAdvisors.add(refreshedAdvisor);
464             }
465             else {
466                 // Add the shared instance.
467
freshAdvisors.add(advisors[i]);
468             }
469         }
470         return freshAdvisors;
471     }
472
473     /**
474      * Add all global interceptors and pointcuts.
475      */

476     private void addGlobalAdvisor(ListableBeanFactory beanFactory, String JavaDoc prefix) {
477         String JavaDoc[] globalAdvisorNames =
478                 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Advisor.class);
479         String JavaDoc[] globalInterceptorNames =
480                 BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Interceptor.class);
481         List JavaDoc beans = new ArrayList JavaDoc(globalAdvisorNames.length + globalInterceptorNames.length);
482         Map JavaDoc names = new HashMap JavaDoc();
483         for (int i = 0; i < globalAdvisorNames.length; i++) {
484             String JavaDoc name = globalAdvisorNames[i];
485             Object JavaDoc bean = beanFactory.getBean(name);
486             beans.add(bean);
487             names.put(bean, name);
488         }
489         for (int i = 0; i < globalInterceptorNames.length; i++) {
490             String JavaDoc name = globalInterceptorNames[i];
491             Object JavaDoc bean = beanFactory.getBean(name);
492             beans.add(bean);
493             names.put(bean, name);
494         }
495         Collections.sort(beans, new OrderComparator());
496         for (Iterator JavaDoc it = beans.iterator(); it.hasNext();) {
497             Object JavaDoc bean = it.next();
498             String JavaDoc name = (String JavaDoc) names.get(bean);
499             if (name.startsWith(prefix)) {
500                 addAdvisorOnChainCreation(bean, name);
501             }
502         }
503     }
504
505     /**
506      * Invoked when advice chain is created.
507      * <p>Add the given advice, advisor or object to the interceptor list.
508      * Because of these three possibilities, we can't type the signature
509      * more strongly.
510      * @param next advice, advisor or target object
511      * @param name bean name from which we obtained this object in our owning
512      * bean factory
513      */

514     private void addAdvisorOnChainCreation(Object JavaDoc next, String JavaDoc name) {
515         // We need to convert to an Advisor if necessary so that our source reference
516
// matches what we find from superclass interceptors.
517
Advisor advisor = namedBeanToAdvisor(next);
518         if (logger.isTraceEnabled()) {
519             logger.trace("Adding advisor with name '" + name + "'");
520         }
521         addAdvisor((Advisor) advisor);
522     }
523     
524     /**
525      * Return a TargetSource to use when creating a proxy. If the target was not
526      * specified at the end of the interceptorNames list, the TargetSource will be
527      * this class's TargetSource member. Otherwise, we get the target bean and wrap
528      * it in a TargetSource if necessary.
529      */

530     private TargetSource freshTargetSource() {
531         if (this.targetName == null) {
532             if (logger.isTraceEnabled()) {
533                 logger.trace("Not refreshing target: Bean name not specified in 'interceptorNames'.");
534             }
535             return this.targetSource;
536         }
537         else {
538             if (logger.isDebugEnabled()) {
539                 logger.debug("Refreshing target with name '" + this.targetName + "'");
540             }
541             Object JavaDoc target = this.beanFactory.getBean(this.targetName);
542             return (target instanceof TargetSource ? (TargetSource) target : new SingletonTargetSource(target));
543         }
544     }
545
546     /**
547      * Convert the following object sourced from calling getBean() on a name in the
548      * interceptorNames array to an Advisor or TargetSource.
549      */

550     private Advisor namedBeanToAdvisor(Object JavaDoc next) {
551         try {
552             return this.advisorAdapterRegistry.wrap(next);
553         }
554         catch (UnknownAdviceTypeException ex) {
555             // We expected this to be an Advisor or Advice,
556
// but it wasn't. This is a configuration error.
557
throw new AopConfigException("Unknown advisor type " + next.getClass() +
558                     "; Can only include Advisor or Advice type beans in interceptorNames chain except for last entry," +
559                     "which may also be target or TargetSource", ex);
560         }
561     }
562
563     /**
564      * Blow away and recache singleton on an advice change.
565      */

566     protected void adviceChanged() {
567         super.adviceChanged();
568         if (this.singleton) {
569             logger.debug("Advice has changed; recaching singleton instance");
570             synchronized (this) {
571                 this.singletonInstance = null;
572             }
573         }
574     }
575
576
577     /**
578      * Used in the interceptor chain where we need to replace a bean with a prototype
579      * on creating a proxy.
580      */

581     private static class PrototypePlaceholderAdvisor implements Advisor {
582
583         private final String JavaDoc beanName;
584
585         private final String JavaDoc message;
586         
587         public PrototypePlaceholderAdvisor(String JavaDoc beanName) {
588             this.beanName = beanName;
589             this.message = "Placeholder for prototype Advisor/Advice with bean name '" + beanName + "'";
590         }
591         
592         public String JavaDoc getBeanName() {
593             return beanName;
594         }
595         
596         public Advice getAdvice() {
597             throw new UnsupportedOperationException JavaDoc("Cannot invoke methods: " + this.message);
598         }
599         
600         public boolean isPerInstance() {
601             throw new UnsupportedOperationException JavaDoc("Cannot invoke methods: " + this.message);
602         }
603         
604         public String JavaDoc toString() {
605             return this.message;
606         }
607     }
608
609 }
610
Popular Tags