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 }