001    /*
002     * Copyright 2004 Stephen J. McConnell.
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
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.util;
020    
021    import java.util.Map;
022    import java.beans.Introspector;
023    import java.beans.Expression;
024    import java.lang.reflect.InvocationHandler;
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Proxy;
027    
028    /**
029     * Invoication handler utility for a Context inner-class.
030     *
031     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
032     * @version 1.0.3
033     */
034    public final class ContextInvocationHandler implements InvocationHandler
035    {
036        //-------------------------------------------------------------------
037        // static
038        //-------------------------------------------------------------------
039        
040       /**
041        * Construct a new context instance implementing the supplied class
042        * and backed by entries in the supplied map.
043        *
044        * @param clazz the context inner class
045        * @param map a map of context entry keys to values
046        * @return the proxied context instance
047        */
048        public static Object getProxiedInstance( Class clazz, Map map )
049        {
050            ClassLoader classloader = clazz.getClassLoader();
051            ContextInvocationHandler handler = new ContextInvocationHandler( map );
052            return Proxy.newProxyInstance( classloader, new Class[]{clazz}, handler );
053        }
054        
055        //-------------------------------------------------------------------
056        // state
057        //-------------------------------------------------------------------
058    
059       /**
060        * A map containing key values.
061        */
062        private final Map m_map;
063    
064        //-------------------------------------------------------------------
065        // constructor
066        //-------------------------------------------------------------------
067    
068       /**
069        * Create a context invocation handler.
070        *
071        * @param provider the provider
072        */
073        private ContextInvocationHandler( Map map )
074        {
075            m_map = map;
076        }
077    
078        //-------------------------------------------------------------------
079        // implementation
080        //-------------------------------------------------------------------
081    
082       /**
083        * Invoke the specified method on underlying object.
084        * This is called by the proxy object.
085        *
086        * @param proxy the proxy object
087        * @param method the method invoked on proxy object
088        * @param args the arguments supplied to method
089        * @return the return value of method
090        * @throws Throwable if an error occurs
091        */
092        public Object invoke( final Object proxy, final Method method, final Object[] args ) throws Throwable
093        {
094            Class source = method.getDeclaringClass();
095            if( Object.class == source )
096            {
097                return method.invoke( this, args );
098            }
099            else
100            {
101                String name = method.getName();
102                if( name.startsWith( "get" ) )
103                {
104                    String key = Introspector.decapitalize( name.substring( 3 ) );
105                    Object value = m_map.get( key );
106                    if( null != value )
107                    {
108                        Class clazz = method.getReturnType();
109                        if( isAssignableFrom( clazz, value.getClass() ) )
110                        {
111                            return value;
112                        }
113                        else
114                        {
115                            Expression expression = new Expression( clazz, "new", new Object[]{value} );
116                            return expression.getValue();
117                        }
118                    }
119                    else if( ( null != args ) && args.length > 0 )
120                    {
121                        return args[0];
122                    }
123                    else
124                    {
125                        final String error = 
126                          "Unable to resolve a context entry value for the key [" + key + "].";
127                        throw new IllegalStateException( error );
128                    }
129                }
130                throw new UnsupportedOperationException( name );
131            }
132        }
133        
134        private static boolean isAssignableFrom( Class clazz, Class c )
135        {
136            if( clazz.isPrimitive() )
137            {
138                if( Integer.TYPE == clazz )
139                {
140                    return Integer.class.isAssignableFrom( c );
141                }
142                else if( Boolean.TYPE == clazz )
143                {
144                    return Boolean.class.isAssignableFrom( c );
145                }
146                else if( Byte.TYPE == clazz )
147                {
148                    return Byte.class.isAssignableFrom( c );
149                }
150                else if( Short.TYPE == clazz )
151                {
152                    return Short.class.isAssignableFrom( c );
153                }
154                else if( Long.TYPE == clazz )
155                {
156                    return Long.class.isAssignableFrom( c );
157                }
158                else if( Float.TYPE == clazz )
159                {
160                    return Float.class.isAssignableFrom( c );
161                }
162                else if( Double.TYPE == clazz )
163                {
164                    return Double.class.isAssignableFrom( c );
165                }
166                else
167                {
168                    final String error =
169                      "Primitive type ["
170                      + c.getName()
171                      + "] not supported.";
172                    throw new RuntimeException( error );
173                }
174            }
175            else
176            {
177                return clazz.isAssignableFrom( c );
178            }
179        }
180    }