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.state;
020    
021    import java.io.IOException;
022    import java.net.URI;
023    
024    import javax.xml.XMLConstants;
025    
026    import net.dpml.state.Trigger.TriggerEvent;
027    
028    import net.dpml.util.DOM3DocumentBuilder;
029    import net.dpml.util.ElementHelper;
030    
031    import org.w3c.dom.Document;
032    import org.w3c.dom.Element;
033    
034    /**
035     * Construct a state graph.
036     */
037    public class StateDecoder
038    {
039        private static final String XML_HEADER = 
040          "<?xml version=\"1.0\"?>";
041        
042        private static final String STATE_SCHEMA_URN = "link:xsd:dpml/lang/dpml-state#1.0";
043        
044        private static final String STATE_HEADER = 
045          "<state xmlns=\"" 
046          + STATE_SCHEMA_URN 
047          + "\""
048          + "\n    xmlns:xsi=\"" 
049          + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
050          + "\">";
051    
052        private static final String STATE_FOOTER = "</state>";
053        
054        private static final DOM3DocumentBuilder BUILDER = new DOM3DocumentBuilder();
055        
056       /**
057        * Load a state graph.
058        * @param uri the graph uri
059        * @return the constructed state graph
060        * @exception IOException if an IO error occurs while reading the 
061        *   graph data
062        */
063        public State loadState( URI uri ) throws IOException
064        {
065            if( null == uri )
066            {
067                throw new NullPointerException( "uri" );
068            }
069            try
070            {
071                final Document document = BUILDER.parse( uri );
072                final Element root = document.getDocumentElement();
073                return buildStateGraph( root );
074            }
075            catch( Throwable e )
076            {
077                final String error =
078                  "An error while attempting to load a state graph."
079                  + "\nURI: " + uri;
080                IOException exception = new IOException( error );
081                exception.initCause( e );
082                throw exception;
083            }
084        }
085        
086       /**
087        * Build a state graph.
088        * @param element a DOM element representing the root of the state graph
089        * @return the constructed state
090        */
091        public State buildStateGraph( Element element )
092        {
093            if( null == element )
094            {
095                throw new NullPointerException( "element" );
096            }
097            
098            boolean terminal = ElementHelper.getBooleanAttribute( element, "terminal" );
099            DefaultTransition[] transitions = buildNestedTransitions( element );
100            DefaultOperation[] operations = buildNestedOperations( element );
101            DefaultInterface[] interfaces = buildNestedInterfaces( element );
102            DefaultState[] states = buildNestedStates( 1, element );
103            DefaultTrigger[] triggers = buildNestedTriggers( element );
104            return new DefaultState( 
105              "root", triggers, transitions, interfaces, operations, states, terminal );
106        }
107        
108        private DefaultTransition[] buildNestedTransitions( Element element )
109        {
110            Element[] children = ElementHelper.getChildren( element, "transition" );
111            DefaultTransition[] transitions = new DefaultTransition[ children.length ];
112            for( int i=0; i<children.length; i++ )
113            {
114                Element child = children[i];
115                transitions[i] = buildTransition( child );
116            }
117            return transitions;
118        }
119        
120        private DefaultOperation[] buildNestedOperations( Element element )
121        {
122            Element[] children = ElementHelper.getChildren( element, "operation" );
123            DefaultOperation[] operations = new DefaultOperation[ children.length ];
124            for( int i=0; i<children.length; i++ )
125            {
126                Element child = children[i];
127                operations[i] = buildOperation( child );
128            }
129            return operations;
130        }
131        
132        private DefaultInterface[] buildNestedInterfaces( Element element )
133        {
134            Element[] children = ElementHelper.getChildren( element, "interface" );
135            DefaultInterface[] interfaces = new DefaultInterface[ children.length ];
136            for( int i=0; i<children.length; i++ )
137            {
138                Element child = children[i];
139                interfaces[i] = buildInterface( child );
140            }
141            return interfaces;
142        }
143        
144        private DefaultState[] buildNestedStates( int n, Element element )
145        {
146            Element[] children = ElementHelper.getChildren( element, "state" );
147            DefaultState[] states = new DefaultState[ children.length ];
148            for( int i=0; i<children.length; i++ )
149            {
150                Element child = children[i];
151                states[i] = buildState( n, child );
152            }
153            return states;
154        }
155        
156        private DefaultTrigger[] buildNestedTriggers( Element element )
157        {
158            Element[] children = ElementHelper.getChildren( element, "trigger" );
159            DefaultTrigger[] triggers = new DefaultTrigger[ children.length ];
160            for( int i=0; i<children.length; i++ )
161            {
162                Element child = children[i];
163                triggers[i] = buildTrigger( child );
164            }
165            return triggers;
166        }
167        
168        private DefaultTransition buildTransition( Element element )
169        {
170            String name = ElementHelper.getAttribute( element, "name" );
171            String target = ElementHelper.getAttribute( element, "target" );
172            Element child = ElementHelper.getChild( element, "operation" );
173            DefaultOperation operation = buildOperation( child );
174            return new DefaultTransition( name, target, operation );
175        }
176        
177        private DefaultOperation buildOperation( Element element )
178        {
179            if( null == element )
180            {
181                return null;
182            }
183            String name = ElementHelper.getAttribute( element, "name" );
184            String method = ElementHelper.getAttribute( element, "method" );
185            return new DefaultOperation( name, method );
186        }
187        
188        private DefaultInterface buildInterface( Element element )
189        {
190            String classname = ElementHelper.getAttribute( element, "class" );
191            return new DefaultInterface( classname );
192        }
193        
194        private DefaultState buildState( int n, Element element )
195        {
196            String name = ElementHelper.getAttribute( element, "name" );
197            boolean terminal = ElementHelper.getBooleanAttribute( element, "terminal" );
198            DefaultTransition[] transitions = buildNestedTransitions( element );
199            DefaultOperation[] operations = buildNestedOperations( element );
200            DefaultInterface[] interfaces = buildNestedInterfaces( element );
201            DefaultState[] states = buildNestedStates( n+1, element );
202            DefaultTrigger[] triggers = buildNestedTriggers( element );
203            return new DefaultState( 
204              name, triggers, transitions, interfaces, operations, states, terminal );
205        }
206        
207        private DefaultTrigger buildTrigger( Element element )
208        {
209            String type = ElementHelper.getAttribute( element, "event" );
210            TriggerEvent event = TriggerEvent.parse( type );
211            Element child = getSingleNestedElement( element );
212            Action action = buildAction( child );
213            return new DefaultTrigger( event, action );
214        }
215        
216        private Action buildAction( Element element )
217        {
218            String name = element.getTagName();
219            if( name.equals( "transition" ) )
220            {
221                return buildTransition( element );
222            }
223            else if( name.equals( "operation" ) )
224            {
225                return buildOperation( element );
226            }
227            else if( name.equals( "apply" ) )
228            {
229                String id = ElementHelper.getAttribute( element, "id" );
230                return new ApplyAction( id );
231            }
232            else if( name.equals( "exec" ) )
233            {
234                String id = ElementHelper.getAttribute( element, "id" );
235                return new ExecAction( id );
236            }
237            else
238            {
239                final String error = 
240                  "Illegal element name ["
241                  + name
242                  + "] supplied to action builder.";
243                throw new StateBuilderRuntimeException( error );
244            }
245        }
246        
247        private Element getSingleNestedElement( Element parent )
248        {
249            if( null == parent )
250            {
251                throw new NullPointerException( "parent" );
252            }
253            else
254            {
255                Element[] children = ElementHelper.getChildren( parent );
256                if( children.length == 1 )
257                {
258                    return children[0];
259                }
260                else
261                {
262                    final String error = 
263                      "Parent element does not contain a single child.";
264                    throw new IllegalArgumentException( error );
265                }
266            }
267        }
268    }