001    /*
002     * Copyright 2004-2006 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.metro.info;
020    
021    import java.io.Serializable;
022    import java.beans.IntrospectionException;
023    import java.util.ArrayList;
024    import java.lang.reflect.Method;
025    import java.net.URI;
026    import java.net.URL;
027    
028    import net.dpml.lang.AbstractDirective;
029    
030    import net.dpml.state.State;
031    import net.dpml.state.StateDecoder;
032    import net.dpml.state.StateBuilderRuntimeException;
033    
034    /**
035     * This class contains the meta information about a particular
036     * component type. It describes;
037     *
038     * <ul>
039     *   <li>Human presentable meta data such as name, version, description etc
040     *   useful when assembling a system.</li>
041     *   <li>the context that this component requires</li>
042     *   <li>the services that this component type is capable of providing</li>
043     *   <li>the services that this component type requires to operate (and the
044     *   names via which services are accessed)</li>
045     *   <li>information about the component lifestyle</li>
046     *   <li>component collection preferences</li>
047     * </ul>
048     *
049     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050     * @version 1.0.4
051     */
052    public class Type extends AbstractDirective implements Serializable
053    {
054        static final long serialVersionUID = 1L;
055        
056        private static final StateDecoder STATE_DECODER = new StateDecoder();
057        
058        private final InfoDescriptor m_info;
059        private final CategoryDescriptor[] m_categories;
060        private final ContextDescriptor m_context;
061        private final ServiceDescriptor[] m_services;
062        private final State m_graph;
063    
064       /**
065        * Creation of a new Type instance using a supplied component descriptor,
066        * logging, context, services, and part references.
067        *
068        * @param info information about the component type
069        * @param loggers a set of logger descriptors the declare the logging channels
070        *   required by the type
071        * @param context a component context descriptor that declares the context type
072        *   and context entry key and value classnames
073        * @param services a set of service descriptors that detail the service that
074        *   this component type is capable of supplying
075        * @param graph the state graph
076        * @exception NullPointerException if the info, loggers, state, or context is null
077        */
078        public Type( 
079          final InfoDescriptor info, final CategoryDescriptor[] loggers,
080          final ContextDescriptor context, final ServiceDescriptor[] services, final State graph )
081          throws NullPointerException 
082        {
083            if( null == info )
084            {
085                throw new NullPointerException( "info" );
086            }
087            if( null == loggers )
088            {
089                throw new NullPointerException( "loggers" );
090            }
091            if( null == context )
092            {
093                throw new NullPointerException( "context" );
094            }
095            if( null == graph )
096            {
097                throw new NullPointerException( "graph" );
098            }
099            if( null == services )
100            {
101                m_services = new ServiceDescriptor[0];
102            }
103            else
104            {
105                m_services = services;
106            }
107    
108            m_info = info;
109            m_categories = loggers;
110            m_context = context;
111            m_graph = graph;
112        }
113        
114       /**
115        * Return the state graph for the component type.
116        * @return the state graph
117        */
118        public State getStateGraph()
119        {
120            return m_graph;
121        }
122    
123       /**
124        * Return the info descriptor.
125        *
126        * @return the component info descriptor.
127        */
128        public InfoDescriptor getInfo()
129        {
130            return m_info;
131        }
132    
133        /**
134         * Return the set of Logger that this Component will use.
135         *
136         * @return the set of Logger that this Component will use.
137         */
138        public CategoryDescriptor[] getCategoryDescriptors()
139        {
140            return m_categories;
141        }
142    
143        /**
144         * Return TRUE if the logging categories includes a category with
145         * a matching name.
146         *
147         * @param name the logging category name
148         * @return TRUE if the logging category is declared.
149         */
150        public boolean isaCategory( String name )
151        {
152            CategoryDescriptor[] loggers = getCategoryDescriptors();
153            for( int i = 0; i < loggers.length; i++ )
154            {
155                CategoryDescriptor logger = loggers[ i ];
156                if( logger.getName().equals( name ) )
157                {
158                    return true;
159                }
160            }
161            return false;
162        }
163    
164        /**
165         * Return the ContextDescriptor for component.
166         *
167         * @return the ContextDescriptor for component.
168         */
169        public ContextDescriptor getContextDescriptor()
170        {
171            return m_context;
172        }
173    
174        /**
175         * Get the set of service descriptors defining the set of services that
176         * the component type exports.
177         *
178         * @return the array of service descriptors
179         */
180        public ServiceDescriptor[] getServiceDescriptors()
181        {
182            return m_services;
183        }
184    
185        /**
186         * Retrieve a service descriptor matching the supplied reference.
187         *
188         * @param reference a service descriptor to match against
189         * @return a matching service descriptor or null if no match found
190         */
191        public ServiceDescriptor getServiceDescriptor( final ServiceDescriptor reference )
192        {
193            for ( int i = 0; i < m_services.length; i++ )
194            {
195                final ServiceDescriptor service = m_services[i];
196                if ( service.matches( reference ) )
197                {
198                    return service;
199                }
200            }
201            return null;
202        }
203    
204        /**
205         * Retrieve a service descriptor matching the supplied classname.
206         *
207         * @param classname the service classname
208         * @return the matching service descriptor or null if it does not exist
209         */
210        public ServiceDescriptor getServiceDescriptor( final String classname )
211        {
212            for ( int i = 0; i < m_services.length; i++ )
213            {
214                final ServiceDescriptor service = m_services[i];
215                if ( service.getClassname().equals( classname ) )
216                {
217                    return service;
218                }
219            }
220            return null;
221        }
222    
223        /**
224         * Return a string representation of the type.
225         * @return the stringified type
226         */
227        public String toString()
228        {
229            return getInfo().toString();
230        }
231    
232       /**
233        * Test is the supplied object is equal to this object.
234        * @param other the other object
235        * @return true if the object are equivalent
236        */
237        public boolean equals( Object other )
238        {
239            if( !super.equals( other ) )
240            {
241                return false;
242            }
243            if( !( other instanceof Type ) )
244            {
245                return false;
246            }
247            Type t = (Type) other;
248            if( !m_info.equals( t.m_info ) )
249            {
250                return false;
251            }
252            if( !m_context.equals( t.m_context ) )
253            {
254                return false;
255            }
256            if( !m_graph.equals( t.m_graph ) )
257            {
258                return false;
259            }
260            for( int i=0; i<m_categories.length; i++ )
261            {
262                if( !m_categories[i].equals( t.m_categories[i] ) )
263                {
264                    return false;
265                }
266            }
267            for( int i=0; i<m_services.length; i++ )
268            {
269                if( !m_services[i].equals( t.m_services[i] ) )
270                {
271                    return false;
272                }
273            }
274            return true;
275        }
276    
277       /**
278        * Return the hashcode for the object.
279        * @return the hashcode value
280        */
281        public int hashCode()
282        {
283            int hash = super.hashCode();
284            hash ^= m_info.hashCode();
285            hash ^= m_context.hashCode();
286            hash ^= m_graph.hashCode();
287            for( int i = 0; i < m_services.length; i++ )
288            {
289                hash ^= m_services[i].hashCode();
290                hash = hash - 163611323;
291            }
292            for( int i = 0; i < m_categories.length; i++ )
293            {
294                hash ^= m_categories[i].hashCode();
295                hash = hash + 471312761;
296            }
297            return hash;
298        }
299        
300       /**
301        * Utility operation to construct a default type given a supplied class.
302        * @param subject the component implementation class
303        * @return the type descriptor for the class
304        * @exception IntrospectionException if an introspection error occurs
305        */
306        public static Type createType( Class subject )
307           throws IntrospectionException
308        {
309            final InfoDescriptor info = new InfoDescriptor( null, subject.getName() );
310            final CategoryDescriptor[] loggers = new CategoryDescriptor[0];
311            final ContextDescriptor context = createContextDescriptor( subject );
312            final ServiceDescriptor[] services = 
313              new ServiceDescriptor[]{
314                new ServiceDescriptor( subject.getName() )
315              };
316            State state = loadStateFromResource( subject );
317            return new Type( info, loggers, context, services, state );
318        }
319        
320        private static ContextDescriptor createContextDescriptor( Class subject ) 
321          throws IntrospectionException
322        {
323            EntryDescriptor[] entries = createEntryDescriptors( subject );
324            return new ContextDescriptor( entries );
325        }
326    
327        private static EntryDescriptor[] createEntryDescriptors( Class subject ) 
328           throws IntrospectionException
329        {
330            String classname = subject.getName();
331            Class[] classes = subject.getClasses();
332            Class param = locateClass( "$Context", classes );
333            if( null == param )
334            {
335                return new EntryDescriptor[0];
336            }
337            else
338            {
339                //
340                // For each method in the Context inner-interface we construct a 
341                // descriptor that establishes the key, type, and required status.
342                //
343    
344                Method[] methods = param.getMethods();
345                ArrayList list = new ArrayList();
346                for( int i=0; i<methods.length; i++ )
347                {
348                    Method method = methods[i];
349                    String name = method.getName();
350                    if( name.startsWith( "get" ) )
351                    {
352                        EntryDescriptor descriptor = 
353                          createEntryDescriptor( method );
354                        list.add( descriptor );
355                    }
356                }
357                return (EntryDescriptor[]) list.toArray( new EntryDescriptor[0] );
358            }
359        }
360    
361       /**
362        * Creation of a new parameter descriptor using a supplied method.
363        * The method is the method used by the component implementation to get the parameter 
364        * instance. 
365        */
366        private static EntryDescriptor createEntryDescriptor( Method method ) 
367          throws IntrospectionException
368        {
369            validateMethodName( method );
370            validateNoExceptions( method );
371    
372            String key = EntryDescriptor.getEntryKey( method );
373    
374            Class returnType = method.getReturnType();
375            if( method.getParameterTypes().length == 0 )
376            {
377                //
378                // required context entry
379                //
380    
381                validateNonNullReturnType( method );
382                String type = returnType.getName();
383                return new EntryDescriptor( key, type, EntryDescriptor.REQUIRED );
384            }
385            else if( method.getParameterTypes().length == 1 )
386            {
387                Class[] params = method.getParameterTypes();
388                Class param = params[0];
389                if( returnType.isAssignableFrom( param ) )
390                {
391                    String type = param.getName();
392                    return new EntryDescriptor( key, type, EntryDescriptor.OPTIONAL );
393                }
394                else
395                {
396                    final String error = 
397                      "Context entry assessor declares an optional default parameter class ["
398                      + param.getName()
399                      + "] which is not assignable to the return type ["
400                      + returnType.getName()
401                      + "]";
402                    throw new IntrospectionException( error );
403                }
404            }
405            else
406            {
407                final String error =
408                  "Unable to establish a required or optional context entry method pattern on ["
409                  + method.getName()
410                  + "]";
411                throw new IntrospectionException( error );
412            }
413        }
414        
415        private static void validateMethodName( Method method ) 
416          throws IntrospectionException
417        {
418            if( !method.getName().startsWith( "get" ) )
419            {
420                final String error = 
421                  "Method ["
422                  + method.getName()
423                  + "] does not start with 'get'.";
424                throw new IntrospectionException( error );
425            }
426        }
427    
428        private static void validateNoExceptions( Method method ) 
429          throws IntrospectionException
430        {
431            Class[] exceptionTypes = method.getExceptionTypes();
432            if( exceptionTypes.length > 0 )
433            {
434                final String error = 
435                  "Method ["
436                  + method.getName()
437                  + "] declares one or more exceptions.";
438                throw new IntrospectionException( error );
439            }
440        }
441    
442        private static void validateNonNullReturnType( Method method ) 
443           throws IntrospectionException
444        {
445            Class returnType = method.getReturnType();
446            if( Void.TYPE.equals( returnType ) ) 
447            {
448                final String error = 
449                  "Method ["
450                  + method.getName()
451                  + "] does not declare a return type.";
452                throw new IntrospectionException( error );
453            }
454        }
455    
456        private static Class locateClass( String postfix, Class[] classes )
457        {
458            for( int i=0; i<classes.length; i++ )
459            {
460                Class inner = classes[i];
461                String name = inner.getName();
462                if( name.endsWith( postfix ) )
463                {
464                    return inner;
465                }
466            }
467            return null;
468        }
469    
470        private static State loadStateFromResource( Class subject )
471        {
472            String resource = subject.getName().replace( '.', '/' ) + ".xgraph";
473            try
474            {
475                URL url = subject.getClassLoader().getResource( resource );
476                if( null == url )
477                {
478                    return State.NULL_STATE;
479                }
480                else
481                {
482                    URI uri = new URI( url.toString() );
483                    return STATE_DECODER.loadState( uri );
484                }
485            }
486            catch( Throwable e )
487            {
488                final String error = 
489                  "Internal error while attempting to load component state graph resource [" 
490                  + resource 
491                  + "].";
492                throw new StateBuilderRuntimeException( error, e );
493            }
494        }
495    }