001    /*
002     * Copyright (c) 2005 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.tools;
020    
021    import java.io.File;
022    import java.net.URI;
023    import java.net.URL;
024    import java.util.ArrayList;
025    import java.util.List;
026    
027    import net.dpml.library.info.Scope;
028    
029    import net.dpml.tools.tasks.GenericTask;
030    
031    import net.dpml.state.Trigger;
032    import net.dpml.state.State;
033    import net.dpml.state.Operation;
034    import net.dpml.state.Interface;
035    import net.dpml.state.Transition;
036    import net.dpml.state.StateDecoder;
037    import net.dpml.state.DefaultState;
038    
039    import org.apache.tools.ant.Project;
040    import org.apache.tools.ant.BuildException;
041    import org.apache.tools.ant.types.Path;
042    import org.apache.tools.ant.AntClassLoader;
043    
044    /**
045     * Utility datatype supporting State instance construction.
046     *
047     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
048     * @version 1.2.0
049     */
050    public class StateDataType
051    {
052        private static final StateDecoder STATE_DECODER = new StateDecoder();
053        
054        private final boolean m_root;
055        private final GenericTask m_task;
056        
057        private String m_name;
058        private List m_states = new ArrayList();
059        private List m_operations = new ArrayList();
060        private List m_interfaces = new ArrayList();
061        private List m_transitions = new ArrayList();
062        private List m_triggers = new ArrayList();
063        private boolean m_terminal = false;
064        
065        private URI m_uri;
066        private String m_classname;
067        
068        StateDataType( GenericTask task )
069        {
070            this( task, false );
071        }
072        
073        StateDataType( GenericTask task, boolean root )
074        {
075            m_root = root;
076            m_task = task;
077        }
078        
079       /**
080        * Set the state name.  Note that state names are only applicable to
081        * substates.  A state name assigned to the root state will be ignored.
082        * @param name the name of the state
083        */
084        public void setName( final String name )
085        {
086            if( null == name )
087            {
088                throw new NullPointerException( "name" );
089            }
090            m_name = name;
091        }
092        
093       /**
094        * Set a uri from which to resolve an encoded state graph.  May only 
095        * applied to a root state.
096        * @param uri a uri referencing a state graph artifact
097        */
098        public void setUri( final URI uri )
099        {
100            if( !m_root )
101            {
102                final String error = 
103                  "Illegal attempt to request import of a state graph within a nested state.";
104                throw new BuildException( error, m_task.getLocation() );
105            }
106            m_uri = uri;
107        }
108        
109       /**
110        * Set a classname from which to resolve an embedded state graph.  May only 
111        * applied to a root state.  May not be used in conjuction with other attributes or
112        * nested elements.
113        * @param classname the classname of a class containing a collocated xgraph resource
114        */
115        public void setClass( final String classname )
116        {
117            if( !m_root )
118            {
119                final String error = 
120                  "Illegal attempt to request import of a state graph within a nested state.";
121                throw new BuildException( error, m_task.getLocation() );
122            }
123            if( null != m_uri )
124            {
125                final String error = 
126                  "Illegal attempt to request import of a embedded state graph in conjuction with the uri attribute.";
127                throw new BuildException( error, m_task.getLocation() );
128            }
129            m_classname = classname;
130        }
131        
132       /**
133        * Mark the state as a terminal state.
134        * @param flag true if this is a terminal state
135        */
136        public void setTerminal( final boolean flag )
137        {
138            if( null != m_uri )
139            {
140                final String error = 
141                  "Terminal attribute may not be used in conjuction with a uri import.";
142                throw new BuildException( error, m_task.getLocation() );
143            }
144            if( null != m_classname )
145            {
146                final String error = 
147                  "Terminal attribute may not be used in conjuction with the class attribute.";
148                throw new BuildException( error, m_task.getLocation() );
149            }
150            m_terminal = flag;
151        }
152        
153       /**
154        * Add a substate within the state.
155        * @return the sub-state datatype
156        */
157        public StateDataType createState()
158        {
159            if( null != m_uri )
160            {
161                final String error = 
162                  "Substates may not be used in conjuction with a uri import.";
163                throw new BuildException( error, m_task.getLocation() );
164            }
165            if( null != m_classname )
166            {
167                final String error = 
168                  "Substates may not be used in conjuction with the class attribute.";
169                throw new BuildException( error, m_task.getLocation() );
170            }
171            final StateDataType state = new StateDataType( m_task );
172            m_states.add( state );
173            return state;
174        }
175        
176       /**
177        * Add an operation within this state.
178        * @return the operation datatype
179        */
180        public OperationDataType createOperation()
181        {
182            if( null != m_uri )
183            {
184                final String error = 
185                  "Operations may not be used in conjuction with a uri import.";
186                throw new BuildException( error, m_task.getLocation() );
187            }
188            if( null != m_classname )
189            {
190                final String error = 
191                  "Operations may not be used in conjuction with the class attribute.";
192                throw new BuildException( error, m_task.getLocation() );
193            }
194            final OperationDataType operation = new OperationDataType();
195            m_operations.add( operation );
196            return operation;
197        }
198        
199       /**
200        * Add an interface within this state.
201        * @return the interface datatype
202        */
203        public InterfaceDataType createInterface()
204        {
205            if( null != m_uri )
206            {
207                final String error = 
208                  "Interfaces may not be used in conjuction with a uri import.";
209                throw new BuildException( error, m_task.getLocation() );
210            }
211            if( null != m_classname )
212            {
213                final String error = 
214                  "Interfaces may not be used in conjuction with the class attribute.";
215                throw new BuildException( error, m_task.getLocation() );
216            }
217            final InterfaceDataType data = new InterfaceDataType();
218            m_interfaces.add( data );
219            return data;
220        }
221        
222       /**
223        * Add an transition within this state.
224        * @return the operation datatype
225        */
226        public TransitionDataType createTransition()
227        {
228            if( null != m_uri )
229            {
230                final String error = 
231                  "Transitions may not be used in conjuction with a uri import.";
232                throw new BuildException( error, m_task.getLocation() );
233            }
234            if( null != m_classname )
235            {
236                final String error = 
237                  "Transitions may not be used in conjuction with the class attribute.";
238                throw new BuildException( error, m_task.getLocation() );
239            }
240            final TransitionDataType transition = new TransitionDataType();
241            m_transitions.add( transition );
242            return transition;
243        }
244    
245       /**
246        * Add an trigger to the state.
247        * @return the trigger datatype
248        */
249        public TriggerDataType createTrigger()
250        {
251            if( null != m_uri )
252            {
253                final String error = 
254                  "Triggers may not be used in conjuction with a uri import.";
255                throw new BuildException( error, m_task.getLocation() );
256            }
257            if( null != m_classname )
258            {
259                final String error = 
260                  "Triggers may not be used in conjuction with the class attribute.";
261                throw new BuildException( error, m_task.getLocation() );
262            }
263            final TriggerDataType trigger = new TriggerDataType();
264            m_triggers.add( trigger );
265            return trigger;
266        }
267        
268        State getState()
269        {
270            if( null != m_uri )
271            {
272                m_task.log( "importing state graph: " + m_uri, Project.MSG_VERBOSE );
273                try
274                {
275                    return STATE_DECODER.loadState( m_uri );
276                }
277                catch( Exception e )
278                {
279                    final String error = 
280                      "Unable to load an external state graph"
281                      + "\nURI: " 
282                      + m_uri;
283                    throw new BuildException( error, e );
284                }
285            }
286            else if( null != m_classname )
287            {
288                m_task.log( "loading state from resource: " + m_classname, Project.MSG_VERBOSE );
289                try
290                {
291                    ClassLoader classloader = createClassLoader();
292                    Class clazz = classloader.loadClass( m_classname );
293                    return loadStateFromResource( clazz );
294                }
295                catch( Exception e )
296                {
297                    final String error = 
298                      "Unable to load an embedded state xgraph from the resource: " 
299                      + m_classname + ".xgraph";
300                    throw new BuildException( error, e, m_task.getLocation() );
301                }
302            }
303            else
304            {
305                m_task.log( "creating embedded state graph", Project.MSG_VERBOSE );
306                String name = getStateName();
307                Trigger[] triggers = getTriggers();
308                Operation[] operations = getOperations();
309                Interface[] interfaces = getInterfaces();
310                Transition[] transitions = getTransitions();
311                State[] states = getStates();
312                return new DefaultState( name, triggers, transitions, interfaces, operations, states, m_terminal );
313            }
314        }
315    
316        String getStateName()
317        {
318            if( m_root )
319            {
320                return "";
321            }
322            else
323            {
324                return m_name;
325            }
326        }
327        
328        Trigger[] getTriggers()
329        {
330            TriggerDataType[] types = (TriggerDataType[]) m_triggers.toArray( new TriggerDataType[0] );
331            Trigger[] values = new Trigger[ types.length ];
332            for( int i=0; i<types.length; i++ )
333            {
334                TriggerDataType data = types[i];
335                values[i] = data.getTrigger();
336            }
337            return values;
338        }
339        
340        Operation[] getOperations()
341        {
342            OperationDataType[] types = (OperationDataType[]) m_operations.toArray( new OperationDataType[0] );
343            Operation[] values = new Operation[ types.length ];
344            for( int i=0; i<types.length; i++ )
345            {
346                OperationDataType data = types[i];
347                values[i] = data.getOperation();
348            }
349            return values;
350        }
351        
352        Interface[] getInterfaces()
353        {
354            InterfaceDataType[] types = (InterfaceDataType[]) m_interfaces.toArray( new InterfaceDataType[0] );
355            Interface[] values = new Interface[ types.length ];
356            for( int i=0; i<types.length; i++ )
357            {
358                InterfaceDataType data = types[i];
359                values[i] = data.getInterface();
360            }
361            return values;
362        }
363        
364        Transition[] getTransitions()
365        {
366            TransitionDataType[] types = (TransitionDataType[]) m_transitions.toArray( new TransitionDataType[0] );
367            Transition[] values = new Transition[ types.length ];
368            for( int i=0; i<types.length; i++ )
369            {
370                TransitionDataType data = types[i];
371                values[i] = data.getTransition();
372            }
373            return values;
374        }
375        
376        State[] getStates()
377        {
378            StateDataType[] types = (StateDataType[]) m_states.toArray( new StateDataType[0] );
379            State[] values = new State[ types.length ];
380            for( int i=0; i<types.length; i++ )
381            {
382                StateDataType data = types[i];
383                values[i] = data.getState();
384            }
385            return values;
386        }
387    
388       /**
389        * Return the runtime classloader.
390        * @return the classloader
391        */
392        private ClassLoader createClassLoader()
393        {
394            Project project = m_task.getProject();
395            Path path = m_task.getContext().getPath( Scope.RUNTIME );
396            File classes = m_task.getContext().getTargetClassesMainDirectory();
397            path.createPathElement().setLocation( classes );
398            ClassLoader parentClassLoader = getClass().getClassLoader();
399            return new AntClassLoader( parentClassLoader, project, path, true );
400        }
401    
402        private State loadStateFromResource( Class subject )
403        {
404            String resource = subject.getName().replace( '.', '/' ) + ".xgraph";
405            try
406            {
407                URL url = subject.getClassLoader().getResource( resource );
408                if( null == url )
409                {
410                    final String error = 
411                      "The requested state graph resource [" + resource + "] does not exist.";
412                    throw new BuildException( error, m_task.getLocation() );
413                }
414                else
415                {
416                    URI uri = new URI( url.toString() );
417                    return STATE_DECODER.loadState( uri );
418                }
419            }
420            catch( Throwable e )
421            {
422                final String error = 
423                  "Internal error while attempting to load component state graph resource [" 
424                  + resource 
425                  + "].";
426                throw new BuildException( error, e );
427            }
428        }
429    }