001    /*
002     * Copyright 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.builder;
020    
021    import java.io.IOException;
022    import java.net.URI;
023    
024    import net.dpml.component.ActivationPolicy;
025    
026    import net.dpml.metro.data.ContextDirective;
027    import net.dpml.metro.data.CategoryDirective;
028    import net.dpml.metro.data.CategoriesDirective;
029    import net.dpml.metro.data.ComponentDirective;
030    import net.dpml.metro.data.ValueDirective;
031    import net.dpml.metro.data.LookupDirective;
032    
033    import net.dpml.metro.info.LifestylePolicy;
034    import net.dpml.metro.info.CollectionPolicy;
035    import net.dpml.metro.info.PartReference;
036    import net.dpml.metro.info.Priority;
037    
038    import net.dpml.lang.ValueDecoder;
039    import net.dpml.lang.Value;
040    
041    import net.dpml.util.Resolver;
042    import net.dpml.util.DOM3DocumentBuilder;
043    import net.dpml.util.ElementHelper;
044    import net.dpml.util.DecodingException;
045    import net.dpml.util.SimpleResolver;
046    
047    import org.w3c.dom.Document;
048    import org.w3c.dom.Element;
049    
050    /**
051     * Construct a state graph.
052     */
053    public class ComponentDecoder
054    {
055        private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0";
056        
057        private static final String SCHEMA_URN = "link:xsd:dpml/lang/dpml-component#1.0";
058        
059        private static final DOM3DocumentBuilder DOCUMENT_BUILDER = new DOM3DocumentBuilder();
060        
061        private static final ComponentTypeDecoder TYPE_DECODER = new ComponentTypeDecoder();
062        
063        private static final ValueDecoder VALUE_DECODER = new ValueDecoder();
064        
065       /**
066        * Construct a component directive using the supplied uri. The uri
067        * must refer to an XML document containing a root component element
068        * (typically used in component data testcases).
069        *
070        * @param uri the part uri
071        * @return the component directive
072        * @exception IOException if an error occurs during directive creation
073        */
074        public ComponentDirective loadComponentDirective( URI uri ) throws IOException
075        {
076            if( null == uri )
077            {
078                throw new NullPointerException( "uri" );
079            }
080            try
081            {
082                final Document document = DOCUMENT_BUILDER.parse( uri );
083                final Element root = document.getDocumentElement();
084                //Resolver resolver = new SimpleResolver();
085                //return buildComponent( root, resolver );
086                return buildComponent( root, null );
087            }
088            catch( Throwable e )
089            {
090                final String error =
091                  "An error while attempting to load a component directive."
092                  + "\nURI: " + uri;
093                IOException exception = new IOException( error );
094                exception.initCause( e );
095                throw exception;
096            }
097        }
098        
099       /**
100        * Construct a component directive using the supplied DOM element.
101        * @param root the element representing the component directive definition
102        * @param resolver build-time uri resolver
103        * @return the component directive
104        * @exception DecodingException if an error occurs during directive creation
105        */
106        public ComponentDirective buildComponent( Element root, Resolver resolver ) throws DecodingException
107        {
108            if( null == root )
109            {
110                throw new NullPointerException( "root" );
111            }
112            String tag = root.getTagName();
113            if( "component".equals( tag ) )
114            {
115                return createComponentDirective( root, resolver );
116            }
117            else
118            {
119                final String error = 
120                  "Component directive element name [" 
121                  + tag 
122                  + "] is not recognized.";
123                throw new DecodingException( root, error );
124            }
125        }
126        
127        private ComponentDirective createComponentDirective( 
128          Element element, Resolver resolver ) throws DecodingException
129        {
130            String classname = buildComponentClassname( element, resolver );
131            String name = buildComponentName( element, resolver );
132            ActivationPolicy activation = buildActivationPolicy( element, resolver );
133            CollectionPolicy collection = buildCollectionPolicy( element, resolver );
134            LifestylePolicy lifestyle = buildLifestylePolicy( element, resolver );
135            CategoriesDirective categories = getNestedCategoriesDirective( element, resolver );
136            ContextDirective context = getNestedContextDirective( element, resolver );
137            PartReference[] parts = getNestedParts( element, resolver );
138            URI base = getBaseURI( element, resolver );
139            
140            if( null == base )
141            {
142                if( null == classname )
143                {
144                    final String error = 
145                      "Missing component type attribute.";
146                    throw new DecodingException( element, error );
147                }
148            }
149            else
150            {
151                if( null != classname )
152                {
153                    final String error = 
154                      "llegal attempt to override a base type in a supertype.";
155                    throw new DecodingException( element, error );
156                }
157            }
158            
159            try
160            {
161                return new ComponentDirective( 
162                  name, activation, collection, lifestyle, classname, 
163                  categories, context, parts, base );
164            }
165            catch( Exception e )
166            {
167                final String error = 
168                  "Component directive creation error.";
169                throw new DecodingException( element, error, e );
170            }
171        }
172        
173        private URI getBaseURI( Element element, Resolver resolver ) throws DecodingException
174        {
175            String base = ElementHelper.getAttribute( element, "uri", null, resolver );
176            if( null == base )
177            {
178                return null;
179            }
180            else
181            {
182                try
183                {
184                    return resolver.toURI( base );
185                }
186                catch( Exception e )
187                {
188                    final String error = 
189                      "Error resolving 'uri' attribute value: " 
190                      + base;
191                    throw new DecodingException( element, error, e );
192                }
193            }
194        }
195        
196        private String buildComponentClassname( Element element, Resolver resolver ) throws DecodingException
197        {
198            return ElementHelper.getAttribute( element, "type", null, resolver );
199        }
200        
201        private ActivationPolicy buildActivationPolicy( Element element, Resolver resolver ) throws DecodingException
202        {
203            String policy = ElementHelper.getAttribute( element, "activation", null, resolver );
204            if( null == policy )
205            {
206                return null;
207            }
208            else
209            {
210                return ActivationPolicy.parse( policy );
211            }
212        }
213        
214        private LifestylePolicy buildLifestylePolicy( Element element, Resolver resolver ) throws DecodingException
215        {
216            String policy = ElementHelper.getAttribute( element, "lifestyle", null, resolver );
217            if( null != policy )
218            { 
219                return LifestylePolicy.parse( policy );
220            }
221            else
222            {
223                return null;
224            }
225        }
226        
227        private CollectionPolicy buildCollectionPolicy( Element element, Resolver resolver ) throws DecodingException
228        {
229            String policy = ElementHelper.getAttribute( element, "collection", null, resolver );
230            if( null != policy )
231            {
232                return CollectionPolicy.parse( policy );
233            }
234            else
235            {
236                return null;
237            }
238        }
239        
240        private String buildComponentName( Element element, Resolver resolver )
241        {
242            return ElementHelper.getAttribute( element, "name", null, resolver );
243        }
244        
245        private CategoriesDirective getNestedCategoriesDirective( Element root, Resolver resolver )
246        {
247            Element element = ElementHelper.getChild( root, "categories" );
248            if( null == element )
249            {
250                return null;
251            }
252            else
253            {
254                return createCategoriesDirective( element, resolver );
255            }
256        }
257        
258        private CategoriesDirective createCategoriesDirective( Element element, Resolver resolver )
259        {
260            if( null == element )
261            {
262                return null;
263            }
264            else
265            {
266                String name = ElementHelper.getAttribute( element, "name", null, resolver );
267                Priority priority = createPriority( element, resolver );
268                String target = ElementHelper.getAttribute( element, "target", null, resolver );
269                CategoryDirective[] categories = createCategoryDirectiveArray( element, resolver );
270                return new CategoriesDirective( name, priority, target, categories );
271            }
272        }
273        
274        private CategoryDirective createCategoryDirective( Element element, Resolver resolver )
275        {
276            String name = ElementHelper.getAttribute( element, "name", null, resolver );
277            Priority priority = createPriority( element, resolver);
278            String target = ElementHelper.getAttribute( element, "target", null, resolver );
279            return new CategoryDirective( name, priority, target );
280        }
281        
282        private CategoryDirective[] createCategoryDirectiveArray( Element element, Resolver resolver )
283        {
284            Element[] children = ElementHelper.getChildren( element );
285            CategoryDirective[] categories = new CategoryDirective[ children.length ];
286            for( int i=0; i<categories.length; i++ )
287            {
288                Element elem = children[i];
289                if( "category".equals( elem.getTagName() ) )
290                {
291                    categories[i] = createCategoryDirective( elem, resolver );
292                }
293                else
294                {
295                    categories[i] = createCategoriesDirective( elem, resolver );
296                }
297            }
298            return categories;
299        }
300        
301        private Priority createPriority( Element element, Resolver resolver )
302        {
303            String priority = ElementHelper.getAttribute( element, "priority", null, resolver );
304            if( null == priority )
305            {
306                return null;
307            }
308            else
309            {
310                return Priority.parse( priority );
311            }
312        }
313        
314        private ContextDirective getNestedContextDirective( Element root, Resolver resolver ) throws DecodingException
315        {
316            Element context = ElementHelper.getChild( root, "context" );
317            if( null == context )
318            {
319                return null;
320            }
321            else
322            {
323                return createContextDirective( context, resolver );
324            }
325        }
326        
327        private ContextDirective createContextDirective( Element element, Resolver resolver ) throws DecodingException
328        {
329            String classname = ElementHelper.getAttribute( element, "class", null, resolver );
330            Element[] children = ElementHelper.getChildren( element );
331            PartReference[] entries = new PartReference[ children.length ];
332            for( int i=0; i<children.length; i++ )
333            {
334                Element elem = children[i];
335                entries[i] = createContextEntryPartReference( elem, resolver );
336            }
337            return new ContextDirective( classname, entries );
338        }
339        
340        private PartReference createContextEntryPartReference( Element element, Resolver resolver ) throws DecodingException
341        {
342            String key = ElementHelper.getAttribute( element, "key", null, resolver );
343            String spec = ElementHelper.getAttribute( element, "lookup", null, resolver );
344            if( null != spec )
345            {
346                LookupDirective directive = new LookupDirective( spec );
347                return new PartReference( key, directive );
348            }
349            else
350            {
351                String name = element.getTagName();
352                if( "entry".equals( name ) )
353                {
354                    ValueDirective directive = buildValueDirective( element, resolver );
355                    return new PartReference( key, directive );
356                }
357                else if( "context".equals( name ) )
358                {
359                    ContextDirective directive = createContextDirective( element, resolver );
360                    return new PartReference( key, directive );
361                }
362                else
363                {
364                    final String error = 
365                      "Context entry element is not recognized.";
366                    throw new DecodingException( element, error );
367                }
368            }
369        }
370        
371       /**
372        * Build a value directive using a supplied DOM element.
373        * @param element the DOM element
374        * @return the value directive
375        */
376        protected ValueDirective buildValueDirective( Element element, Resolver resolver )
377        {
378            String classname = ElementHelper.getAttribute( element, "class", null, resolver );
379            String method = ElementHelper.getAttribute( element, "method", null, resolver );
380            Element[] elements = ElementHelper.getChildren( element, "param" );
381            if( elements.length > 0 )
382            {
383                Value[] values = VALUE_DECODER.decodeValues( elements, resolver );
384                return new ValueDirective( classname, method, values );
385            }
386            else
387            {
388                String value = ElementHelper.getAttribute( element, "value", null, resolver );
389                return new ValueDirective( classname, method, value );
390            }
391        }
392        
393        private PartReference[] getNestedParts( Element root, Resolver resolver ) throws DecodingException
394        {
395            Element parts = ElementHelper.getChild( root, "parts" );
396            if( null == parts )
397            {
398                return null;
399            }
400            else
401            {
402                return createParts( parts, resolver );
403            }
404        }
405        
406        private PartReference[] createParts( Element element, Resolver resolver ) throws DecodingException
407        {
408            Element[] children = ElementHelper.getChildren( element );
409            PartReference[] parts = new PartReference[ children.length ];
410            for( int i=0; i<children.length; i++ )
411            {
412                Element elem = children[i];
413                parts[i] = createPartReference( elem, resolver );
414            }
415            return parts;
416        }
417        
418        private PartReference createPartReference( Element element, Resolver resolver ) throws DecodingException
419        {
420            String tag = element.getTagName();
421            String key = ElementHelper.getAttribute( element, "key", null, resolver );
422            int priority = getPriority( element, resolver );
423            if( "component".equals( tag ) )
424            {
425                ComponentDirective directive = buildComponent( element, resolver );
426                return new PartReference( key, directive, priority );
427            }
428            else
429            {
430                final String error = 
431                  "Component part element name [" 
432                  + tag 
433                  + "] is not recognized.";
434                throw new DecodingException( element, error );
435            }
436        }
437        
438        private int getPriority( Element element, Resolver resolver ) throws DecodingException
439        {
440            String priority = ElementHelper.getAttribute( element, "priority", "0", resolver );
441            try
442            {
443                return Integer.parseInt( priority );
444            }
445            catch( Exception e )
446            {
447                final String error = 
448                  "Unable to parse priority value.";
449                throw new DecodingException( element, error, e );
450            }
451        }
452        
453       /**
454        * Internal utility to get the name of the class without the package name. Used
455        * when constructing a default component name.
456        * @param classname the fully qualified classname
457        * @return the short class name without the package name
458        */
459        private String toName( String classname )
460        {
461            int i = classname.lastIndexOf( "." );
462            if( i == -1 )
463            {
464                return classname.toLowerCase();
465            }
466            else
467            {
468                return classname.substring( i + 1, classname.length() ).toLowerCase();
469            }
470        }
471    
472    }