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.io.OutputStream;
023    import java.io.OutputStreamWriter;
024    import java.io.Writer;
025    import java.net.URI;
026    
027    import javax.xml.XMLConstants;
028    
029    import net.dpml.component.ActivationPolicy;
030    import net.dpml.component.Directive;
031    
032    import net.dpml.lang.Value;
033    import net.dpml.lang.ValueEncoder;
034    
035    import net.dpml.metro.data.ContextDirective;
036    import net.dpml.metro.data.CategoryDirective;
037    import net.dpml.metro.data.CategoriesDirective;
038    import net.dpml.metro.data.ComponentDirective;
039    import net.dpml.metro.data.LookupDirective;
040    import net.dpml.metro.data.ValueDirective;
041    
042    import net.dpml.metro.info.LifestylePolicy;
043    import net.dpml.metro.info.CollectionPolicy;
044    import net.dpml.metro.info.PartReference;
045    import net.dpml.metro.info.Priority;
046    
047    import net.dpml.util.Encoder;
048    
049    /**
050     * Component part handler.
051     *
052     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
053     * @version 1.2.0
054     */
055    public class ComponentEncoder extends ComponentConstants implements Encoder
056    {
057        private static final String XML_HEADER = "<?xml version=\"1.0\"?>";
058        private static final String TYPE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-type#1.0";
059        private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0";
060        private static final String PART_SCHEMA_URN = "link:xsd:dpml/lang/dpml-part#1.0";
061        private static final String COMPONENT_SCHEMA_URN = "link:xsd:dpml/lang/dpml-component#1.0";
062        private static final String PARTIAL_COMPONENT_HEADER = 
063          "<component xmlns=\"" 
064          + COMPONENT_SCHEMA_URN 
065          + "\""
066          + "\n    xmlns:xsi=\"" 
067          + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
068          + "\"\n    xmlns:part=\"" 
069          + PART_SCHEMA_URN
070          + "\"\n    xmlns:type=\"" 
071          + TYPE_SCHEMA_URN
072          + "\"\n    xmlns:component=\"" 
073          + COMPONENT_SCHEMA_URN
074          + "\"";
075        
076        private static final ValueEncoder VALUE_ENCODER = new ValueEncoder();
077        
078       /** 
079        * Export a component directive to an output stream as XML.
080        * @param directive the component directive
081        * @param output the output stream
082        * @exception IOException if an IO error occurs
083        */
084        public void export( ComponentDirective directive, OutputStream output ) throws IOException
085        {
086            final Writer writer = new OutputStreamWriter( output );
087            
088            writer.write( XML_HEADER );
089            writer.write( "\n\n" );
090            writer.write( PARTIAL_COMPONENT_HEADER );
091            writeAttributes( writer, directive, "" );
092            writeBody( writer, directive, "  " );
093            writer.write( "\n" );
094            writer.write( "</component>" );
095            writer.write( "\n" );
096            writer.flush();
097            output.close();
098        }
099        
100       /** 
101        * Export a component directive to an output stream as XML.
102        * @param writer the print writer
103        * @param object the object to encode
104        * @param pad character offset
105        * @exception IOException if an IO error occurs
106        */
107        public void encode( Writer writer, Object object, String pad ) throws IOException
108        {
109            if( object instanceof ComponentDirective )
110            {
111                writeTaggedComponent( writer, (ComponentDirective) object, null, pad, true );
112            }
113            else
114            {
115                final String error = 
116                  "Encoding subject is not recognized."
117                  + "\nClass: " + object.getClass().getName();
118                throw new IllegalArgumentException( error );
119            }
120        }
121    
122       /** 
123        * Export a component directive to an output stream as XML.
124        * @param writer the print writer
125        * @param directive the component directive
126        * @param pad character offset
127        * @exception IOException if an IO error occurs
128        */
129        public void writeComponent( 
130          Writer writer, ComponentDirective directive, String pad ) throws IOException
131        {
132            writeTaggedComponent( writer, directive, null, pad );
133        }
134        
135       /** 
136        * Export a tagged component directive to an output stream as XML.
137        * @param writer the print writer
138        * @param directive the component directive
139        * @param key the key identifying the component
140        * @param pad character offset
141        * @exception IOException if an IO error occurs
142        */
143        public void writeTaggedComponent( 
144          Writer writer, ComponentDirective directive, String key, String pad ) throws IOException
145        {
146            writeTaggedComponent( writer, directive, key, pad, true );
147        }
148        
149       /** 
150        * Export a tagged component directive to an output stream as XML.
151        * @param writer the print writer
152        * @param directive the component directive
153        * @param key the key identifying the component
154        * @param pad character offset
155        * @param flag true if the xml namespace should be included
156        * @exception IOException if an IO error occurs
157        */
158        public void writeTaggedComponent( 
159          Writer writer, ComponentDirective directive, String key, String pad, boolean flag ) throws IOException
160        {
161            writer.write( "\n" + pad + "<component" );
162            if( flag )
163            {
164                writer.write( " xmlns=\"" + COMPONENT_SCHEMA_URN + "\"" );
165            }
166            if( null != key )
167            {
168                writer.write( " key=\"" + key + "\"" );
169            }
170            writer.write( "\n" + pad + "   " );
171            writeAttributes( writer, directive, pad + "   " );
172            writeBody( writer, directive, pad + "  " );
173            writer.write( "\n" + pad + "</component>" );
174        }
175        
176        void writeAttributes( 
177          Writer writer, ComponentDirective directive, String pad ) throws IOException
178        {
179            String classname = directive.getClassname();
180            if( null != classname )
181            {
182                writer.write( " type=\"" + classname + "\"" );
183            }
184            URI uri = directive.getBaseURI();
185            if( null != uri )
186            {
187                writer.write( " uri=\"" + uri.toASCIIString() + "\"" );
188            }
189            String name = directive.getName();
190            if( null != name )
191            {
192                writer.write( "\n" + pad + " name=\"" + name + "\"" );
193            }
194            LifestylePolicy lifestyle = directive.getLifestylePolicy();
195            if( null != lifestyle )
196            {
197                writer.write( "\n" + pad + " lifestyle=\"" + lifestyle.getName() + "\"" );
198            }
199            CollectionPolicy collection = directive.getCollectionPolicy();
200            if( null != collection )
201            {
202                writer.write( "\n" + pad + " collection=\"" + collection.getName() + "\"" );
203            }
204            ActivationPolicy activation = directive.getActivationPolicy();
205            if( null != activation )
206            {
207                writer.write( "\n" + pad + " activation=\"" + activation.getName() + "\"" );
208            }
209            writer.write( ">" );
210        }
211        
212        void writeBody( 
213          Writer writer, ComponentDirective directive, String pad ) throws IOException
214        {
215            CategoriesDirective categories = directive.getCategoriesDirective();
216            ContextDirective context = directive.getContextDirective();
217            PartReference[] parts = directive.getPartReferences();
218            writeCategoriesDirective( writer, categories, pad );
219            writeContextDirective( writer, context, pad );
220            writeParts( writer, parts, pad, false );
221        }
222        
223        private void writeCategoriesDirective( 
224          Writer writer, CategoriesDirective categories, String pad ) throws IOException
225        {
226            if( null == categories )
227            {
228                return;
229            }
230            
231            String name = categories.getName();
232            Priority priority = categories.getPriority();
233            String target = categories.getTarget();
234            CategoryDirective[] subCategories = categories.getCategories();
235            
236            if( isaNullValue( name ) && isaNullPriority( priority ) && isaNullValue( target ) 
237              &&  ( subCategories.length == 0 ) )
238            {
239                return;
240            }
241            
242            writer.write( "\n" + pad + "<categories" );
243            if( !isaNullValue( name ) )
244            {
245                writer.write( " name=\"" + name + "\"" );
246            }
247            if( !isaNullPriority( priority ) )
248            {
249                writer.write( " priority=\"" + priority.getName() + "\"" );
250            }
251            if( !isaNullValue( target ) )
252            {
253                writer.write( " target=\"" + target + "\"" );
254            }
255            if( subCategories.length == 0 )
256            {
257                writer.write( "/>" );
258            }
259            else
260            {
261                writer.write( ">" );
262                for( int i=0; i<subCategories.length; i++ )
263                {
264                    CategoryDirective directive = subCategories[i];
265                    if( directive instanceof CategoriesDirective )
266                    {
267                        CategoriesDirective c = (CategoriesDirective) directive;
268                        writeCategoriesDirective( writer, c, pad + "  " );
269                    }
270                    else
271                    {
272                        writeCategoryDirective( writer, directive, pad + "  " );
273                    }
274                }
275                writer.write( "\n" + pad + "</categories>" );
276            }
277        }
278        
279        private boolean isaNullPriority( Priority priority )
280        {
281            if( null == priority )
282            {
283                return true;
284            }
285            else
286            {
287                return Priority.DEBUG.equals( priority );
288            }
289        }
290        
291        private boolean isaNullValue( String value )
292        {
293            if( null == value )
294            {
295                return true;
296            }
297            else
298            {
299                return "".equals( value );
300            }
301        }
302        
303        private void writeCategoryDirective( 
304          Writer writer, CategoryDirective category, String pad ) throws IOException
305        {
306            String name = category.getName();
307            Priority priority = category.getPriority();
308            String target = category.getTarget();
309            
310            writer.write( "\n" + pad + "<category" );
311            if( null != name )
312            {
313                writer.write( " name=\"" + name + "\"" );
314            }
315            if( null != priority )
316            {
317                writer.write( " priority=\"" + priority.getName() + "\"" );
318            }
319            if( null != target )
320            {
321                writer.write( " target=\"" + target + "\"" );
322            }
323            writer.write( "/>" );
324        }
325        
326        private void writeContextDirective( 
327          Writer writer, ContextDirective context, String pad ) throws IOException
328        {
329            if( null == context )
330            {
331                return;
332            }
333            
334            String classname = context.getClassname();
335            PartReference[] parts = context.getDirectives();
336            
337            if( ( null == classname ) && ( parts.length == 0 ) )
338            {
339                return;
340            }
341            
342            writer.write( "\n" + pad + "<context" );
343            if( null != classname )
344            {
345                writer.write( " class=\"" + classname + "\"" );
346            }
347            if( parts.length == 0 )
348            {
349                writer.write( "/>" );
350            }
351            else
352            {
353                writer.write( ">" );
354                writeContextEntries( writer, parts, pad + "  " );
355                writer.write( "\n" + pad + "</context>" );
356            }
357        }
358        
359       /**
360        * Write a collection of part references.
361        * @param writer the writer
362        * @param parts the part refernece array
363        * @param pad the offset
364        * @param flag true if the xml namespace should be included
365        * @exception IOException if an IO error occurs
366        */
367        protected void writeParts( 
368          Writer writer, PartReference[] parts, String pad, boolean flag ) throws IOException
369        {
370            if( null == parts )
371            {
372                return;
373            }
374            
375            if( parts.length == 0 )
376            {
377                return;
378            }
379            else
380            {
381                writer.write( "\n" + pad + "<parts>" );
382                writePartReferences( writer, parts, pad + "  ", flag );
383                writer.write( "\n" + pad + "</parts>" );
384            }
385        }
386        
387        private void writePartReferences(
388          Writer writer, PartReference[] parts, String pad, boolean flag ) throws IOException
389        {
390            for( int i=0; i<parts.length; i++ )
391            {
392                PartReference ref = parts[i];
393                writePartReference( writer, ref, pad, flag );
394            }
395        }
396        
397        private void writeContextEntries(
398          Writer writer, PartReference[] parts, String pad ) throws IOException
399        {
400            for( int i=0; i<parts.length; i++ )
401            {
402                PartReference ref = parts[i];
403                writeContextEntry( writer, ref, pad );
404            }
405        }
406        
407        private void writeContextEntry(
408          Writer writer, PartReference part, String pad ) throws IOException
409        {
410            String key = part.getKey();
411            if( null == key )
412            {
413                throw new IllegalStateException( "key" );
414            }
415            Directive directive = part.getDirective();
416            if( null == directive )
417            {
418                throw new IllegalStateException( "directive" );
419            }
420            if( directive instanceof ValueDirective )
421            {
422                ValueDirective value = (ValueDirective) directive;
423                writeEntry( writer, key, value, pad );
424            }
425            else if( directive instanceof LookupDirective )
426            {
427                LookupDirective value = (LookupDirective) directive;
428                writeLookupEntry( writer, key, value, pad );
429            }
430            else
431            {
432                String classname = directive.getClass().getName();
433                final String message = "WARNING: UNRECOGNIZED ENTRY: "+ classname;
434                System.out.println( "# " + message );
435                System.out.println( "# key: " + key );
436                System.out.println( "# class: " + classname );
437                writer.write( "\n" + pad + "<!-- " + message + " -->" );
438                writer.write( "\n" + pad + "<!-- " );
439                writer.write( "\n" + pad + "key: " + key );
440                writer.write( "\n" + pad + "class: " + directive.getClass().getName() );
441                writer.write( "\n" + pad + "-->" );
442            }
443        }
444        
445        private void writeLookupEntry(
446          Writer writer, String key, LookupDirective directive, String pad ) throws IOException
447        {
448            String classname = directive.getServiceClassname();
449            writer.write( "\n" + pad + "<entry key=\"" + key + "\" lookup=\"" + classname + "\"/>" );
450        }
451        
452        private void writePartReference(
453          Writer writer, PartReference part, String pad, boolean flag ) throws IOException
454        {
455            String key = part.getKey();
456            if( null == key )
457            {
458                throw new IllegalStateException( "key" );
459            }
460            
461            Directive directive = part.getDirective();
462            if( null == directive )
463            {
464                throw new IllegalStateException( "directive" );
465            }
466            if( directive instanceof ComponentDirective )
467            {
468                ComponentDirective component = (ComponentDirective) directive;
469                writeTaggedComponent( writer, component, key, pad, flag );
470            }
471            else
472            {
473                String classname = directive.getClass().getName();
474                final String error = 
475                  "Part reference directive class not recognized."
476                  + "\nClass: " + classname;
477                throw new IllegalArgumentException( error );
478            }
479        }
480    
481       /**
482        * Write a context entry.
483        * @param writer the writer
484        * @param key the entry key
485        * @param value the value directive
486        * @param pad the offset
487        * @exception IOException if an IO error occurs
488        */
489        protected void writeEntry( Writer writer, String key, ValueDirective value, String pad ) throws IOException
490        {
491            String target = value.getTargetExpression();
492            String method = value.getMethodName();
493            
494            writer.write( "\n" + pad + "<entry key=\"" + key + "\"" );
495            if( null != target )
496            {
497                writer.write( " class=\"" + target + "\"" );
498            }
499            if( null != method )
500            {
501                writer.write( " method=\"" + method  + "\"" );
502            }
503            Value[] values = value.getValues();
504            if( values.length > 0 )
505            {
506                writer.write( ">" );
507                VALUE_ENCODER.encodeValues( writer, values, pad + "  " );
508                writer.write( "\n" + pad + "</entry>" );
509            }
510            else
511            {
512                String v = value.getBaseValue();
513                if( null != v )
514                {
515                    writer.write( " value=\"" + v + "\"" );
516                }
517                writer.write( "/>" );
518            }
519        }    
520    }