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.1.3 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 }