View Javadoc

1   /*
2    * All Rights Reserved.
3    * Copyright (C) 1999-2005 Tsukuba Bunko.
4    *
5    * Licensed under the BSD License ("the License"); you may not use
6    * this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *       http://www.tsukuba-bunko.org/licenses/LICENSE.txt
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   *
17   * $Id: SceneProcessor.java,v 1.8 2005/11/23 05:41:15 ppoi Exp $
18   */
19  package tsukuba_bunko.peko.scenario;
20  
21  import java.io.FileNotFoundException;
22  import	java.io.InputStream;
23  
24  import	java.net.URL;
25  
26  import	javax.xml.parsers.ParserConfigurationException;
27  import	javax.xml.parsers.SAXParser;
28  import	javax.xml.parsers.SAXParserFactory;
29  
30  import	org.xml.sax.Attributes;
31  import	org.xml.sax.InputSource;
32  import	org.xml.sax.SAXException;
33  import	org.xml.sax.SAXParseException;
34  import	org.xml.sax.helpers.DefaultHandler;
35  
36  import	tsukuba_bunko.peko.ActionControler;
37  import	tsukuba_bunko.peko.Logger;
38  import	tsukuba_bunko.peko.PekoSystem;
39  
40  import	tsukuba_bunko.peko.resource.ResourceManager;
41  
42  import	tsukuba_bunko.peko.scenario.select.SelectCoordinator;
43  
44  import	tsukuba_bunko.peko.scenario.stage.StageCoordinator;
45  
46  import	tsukuba_bunko.peko.scenario.text.TextCoordinator;
47  
48  import	tsukuba_bunko.peko.session.Session;
49  
50  
51  /***
52   * シーンの処理を行うプロセッサモジュールです。
53   * @author	$Author: ppoi $
54   * @version	$Revision: 1.8 $
55   */
56  public class SceneProcessor	extends DefaultHandler	implements Runnable	{
57  
58  	/***
59  	 * PSML 1.0 Scene DTD Public ID
60  	 */
61  	public static final String	PSML_PUBLICID = "-//Tsukuba Bunko//DTD PSML 1.0 Scene//EN";
62  
63  	/***
64  	 * PSML 1.0 Scene (ILLEGAL) DTD Public ID
65  	 */
66  	public static final String	PSML_PUBLICID_ILLEGAL = "-//Tsukuba Bunko//DTD PSML Scene 1.0//EN";
67  
68  	/***
69  	 * PSML 1.0 Scene DTD System ID
70  	 */
71  	public static final String	PSML_SYSTEMID = "http://softlab.tsukuba-bunko.org/dtd/psml10-scene.dtd";
72  
73  
74  	/***
75  	 * PSML 解析を行う SAX Parser
76  	 */
77  	protected SAXParser	_parser = null;
78  
79  	/***
80  	 * 処理状態フラグ
81  	 */
82  	protected boolean	_running = false;
83  
84  	/***
85  	 * 中止フラグ
86  	 */
87  	protected boolean	_aborted = false;
88  
89  	/***
90  	 * 処理中のシーン
91  	 */
92  	protected URL	_sceneURL = null;
93  
94  	/***
95  	 * 処理中のシーンのコンテクスト
96  	 */
97  	protected SceneContext	_sceneContext = null;
98  
99  	/***
100 	 * この SceneProcessor を制御する ScenarioProcessor
101 	 */
102 	protected ScenarioProcessor	_owner = null;
103 
104 	/***
105 	 * 現在のツリー中のレベル
106 	 */
107 	protected int	_level = -1;
108 
109 	/***
110 	 * ElementHandler が処理中のサブツリーの開始レベル
111 	 */
112 	protected int	_processStartedLevel = -1;
113 
114 	/***
115 	 * 評価しないサブツリーの開始レベル
116 	 */
117 	protected int	_ignoreStartedLevel = -1;
118 
119 	/***
120 	 * ハンドラレジストリ
121 	 */
122 	protected HandlerRegistry	_registry = null;
123 
124 	/***
125 	 * 現在要素を処理中の ElementHandler
126 	 */
127 	protected ElementHandler	_handler = null;
128 
129 	/***
130 	 * スタートのノード位置
131 	 */
132 	protected SceneContext.Node	_startNode = null;
133 
134 
135 	/***
136 	 * TextCanvas コーディネータ
137 	 */
138 	protected TextCoordinator	_textCoordinator = null;
139 
140 	/***
141 	 * StageCanvas コーディネータ
142 	 */
143 	protected StageCoordinator	_stageCoordinator = null;
144 
145 	/***
146 	 * SelectCanvas コーディネータ
147 	 */
148 	protected SelectCoordinator	_selectCoordinator = null;
149 
150 
151 	/***
152 	 * <code>SceneProcessor</code> のインスタンスを生成します。
153 	 * @param	owner	この SceneProcessor インスタンスを制御する ScenarioProcessor
154 	 */
155 	public SceneProcessor( ScenarioProcessor owner )
156 	{
157 		super();
158 		_owner = owner;
159 		_registry = HandlerRegistry.newInstance();
160 		getSAXParser();
161 		Logger.debug( "[scenario] create SceneProcessor." );
162 	}
163 
164 
165 	/***
166 	 * シーンコンテクストを取得します。
167 	 * @return	シーンコンテクスト
168 	 */
169 	public SceneContext getSceneContext()
170 	{
171 		return _sceneContext;
172 	}
173 
174 
175 	/***
176 	 * ハンドラレジストリを取得します。
177 	 * @return	ハンドラレジストリ
178 	 */
179 	public HandlerRegistry getHandlerRegistry()
180 	{
181 		return _registry;
182 	}
183 
184 	/***
185 	 * TextCanvas コーディネータを設定します。
186 	 * @param	coordinator	TextCanvas コーディネータ
187 	 */
188 	public void setTextCoordinator( TextCoordinator coordinator )
189 	{
190 		_textCoordinator = coordinator;
191 	}	
192 
193 	/***
194 	 * TextCanvas コーディネータを取得します。
195 	 * @return	TextCanvas コーディネータ
196 	 */
197 	public TextCoordinator getTextCoordinator()
198 	{
199 		return _textCoordinator;
200 	}
201 
202 	/***
203 	 * StageCanvas コーディネータを設定します。
204 	 * @param	coordinator	StageCanvas コーディネータ
205 	 */
206 	public void setStageCoordinator( StageCoordinator coordinator )
207 	{
208 		_stageCoordinator = coordinator;
209 	}
210 
211 	/***
212 	 * StageCanvas コーディネータを取得します。
213 	 * @return	StageCanvas コーディネータ
214 	 */
215 	public StageCoordinator getStageCoordinator()
216 	{
217 		return _stageCoordinator;
218 	}
219 
220 	/***
221 	 * SelectCanvas コーディネータを設定します。
222 	 * @param	coordinator	SelectCanvas コーディネータ
223 	 */
224 	public void setSelectCoordinator( SelectCoordinator coordinator )
225 	{
226 		_selectCoordinator = coordinator;
227 	}
228 
229 	/***
230 	 * SelectCanvas コーディネータを取得します。
231 	 * @return	SelectCanvas コーディネータ
232 	 */
233 	public SelectCoordinator getSelectCoordinator()
234 	{
235 		return _selectCoordinator;
236 	}
237 
238 
239 	/***
240 	 * シーンの処理を開始します。
241 	 * @param	sceneName	処理するシーンのシーン名
242 	 */
243 	public void process( String sceneName, Session session )
244 	{
245 		if( _running )	{
246 			IllegalStateException	ise = new IllegalStateException( "scene processor is still running." );
247 			Logger.fatal( MessageIDs.SCN0007F );
248 			PekoSystem.showErrorDialog( MessageIDs.SCN0007F.getMessage(), ise.fillInStackTrace(), true );
249 		}
250 		_sceneURL = getSceneURL( sceneName );
251 		_sceneContext = new SceneContext( sceneName, session, this );
252 		_startNode = _sceneContext.getLastCommittedNode();
253 		if( _startNode != null )	{
254 			Logger.debug( "[scenario.scene] start from: " + _startNode.getPath() );
255 		}
256 
257 		Thread	thread = new Thread( this );
258 		session.setSceneContext( _sceneContext, thread );
259 
260 		_textCoordinator.prepare( _sceneContext, thread );
261 		_stageCoordinator.prepare( _sceneContext, thread );
262 		_selectCoordinator.prepare( _sceneContext, thread );
263 
264 		_aborted = false;
265 		thread.start();
266 	}
267 
268 	/***
269 	 * シーンの処理を中止します。
270 	 */
271 	public void abort()
272 	{
273 		_aborted = true;
274 		_textCoordinator.dormantize();
275 		_stageCoordinator.dormantize();
276 		_selectCoordinator.dormantize();
277 		Logger.debug( "[scenario] release stopped thread." );
278 		ActionControler	controler = PekoSystem.getInstance().getActionControler();
279 		controler.start();
280 	}
281 
282 	/***
283 	 * シーン処理が中止されているかどうかを判定します。
284 	 * @return	シーン処理が中止されている場合 <code>true</code>、それ以外の場合 <code>false</code>
285 	 */
286 	public boolean isAborted()
287 	{
288 		return _aborted;
289 	}
290 
291 	/***
292 	 * PSML Scene データの構文解析を行う SAX パーザを取得します。
293 	 * @return	PSML Scene データの構文解析を行う SAX パーザ
294 	 */
295 	protected SAXParser getSAXParser()
296 	{
297 		if( _parser == null )	{
298 			try	{
299 				SAXParserFactory	factory = SAXParserFactory.newInstance();
300 				factory.setNamespaceAware( true );
301 				if( "on".equals(System.getProperty("debug", "off")) )	{
302 					Logger.debug( "[scenario.scene] set PSML validating on." );
303 					factory.setValidating( true );
304 				}
305 				else	{
306 					Logger.debug( "[scenario.scene] set PSML validating off." );
307 					factory.setValidating( false );
308 				}
309 				_parser = factory.newSAXParser();
310 			}
311 			catch( SAXException se )	{
312 				Logger.fatal( MessageIDs.SCN0000F );
313 				PekoSystem.showErrorDialog( MessageIDs.SCN0000F.getMessage(), se, true );
314 			}
315 			catch( ParserConfigurationException pce )	{
316 				Logger.fatal( MessageIDs.SCN0000F );
317 				PekoSystem.showErrorDialog( MessageIDs.SCN0000F.getMessage(), pce, true );
318 			}
319 		}
320 		return _parser;
321 	}
322 
323 	/***
324 	 * シーンデータの URL を取得します。
325 	 * @param	scene	シーン名
326 	 * @return	シーンデータの URL
327 	 */
328 	protected URL getSceneURL( String scene )
329 	{
330 		ResourceManager	resources = ResourceManager.getInstance();
331 		URL	sceneDir = resources.getLocationResources().getScenesDirecotryURL();
332 		try	{
333 			return new URL( sceneDir, scene );
334 		}
335 		catch( Exception e )	{
336 			Logger.fatal( MessageIDs.SCN0006F, e );
337 			PekoSystem.showErrorDialog( MessageIDs.SCN0006F.getMessage(), e, true );
338 			return null;
339 		}
340 	}
341 
342 //
343 //	Runnable の実装
344 //
345 	public void run()
346 	{
347 		_running = true;
348 		InputStream	is = null;
349 		try	{
350 			Logger.debug( _sceneURL.toString() );
351 			is = _sceneURL.openStream();
352 			SAXParser	parser = getSAXParser();
353 			parser.parse( is, this );
354 		}
355 		catch( SAXParseException spe )	{
356 			PekoSystem.getInstance().getActionControler().returnTitle( true );
357 		}
358 		catch( FileNotFoundException fnfe )	{
359 			Logger.error( MessageIDs.SCN0014E, new Object[]{getSceneContext().getSceneName()} );
360 			PekoSystem.getInstance().getActionControler().returnTitle( true );
361 		}
362 		catch( Exception e )	{
363 			Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
364 			PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
365 		}
366 		finally	{
367 			if( is != null )	{
368 				try	{
369 					is.close();
370 					Logger.debug( "[scene] close scene. scene=" + getSceneContext().getSceneName() );
371 				}
372 				catch( Exception e )	{
373 					//ignore.
374 				}
375 			}
376 		}
377 		_running = false;
378 		_owner.pushSceneProcessor( this );
379 	}
380 
381 
382 //
383 //	ContentHandler の実装
384 //
385 	public void startDocument()
386 		throws SAXException
387 	{
388 		if( _aborted )	{
389 			return;
390 		}
391 
392 		_level  = 0;
393 		_processStartedLevel = -1;
394 		_ignoreStartedLevel = -1;
395 		_handler = null;
396 	}
397 
398 	public void endDocument()
399 		throws SAXException
400 	{
401 		if( _aborted )	{
402 			return;
403 		}
404 		else	{
405 			_owner.sceneEnded( this );
406 		}
407 	}
408 
409 	public void startElement( String namespaceURI, String localName, String qName, Attributes attrs )
410 		throws SAXException
411 	{
412 		if( _aborted )	{
413 			return;
414 		}
415 
416 		++_level;
417 		_sceneContext.pushNode( qName );
418 
419 		if( _startNode != null )	{
420 			if( _sceneContext.isCurrentNode(_startNode) )	{
421 				_startNode = null;
422 			}
423 			else	{
424 				return;
425 			}
426 		}
427 		else if( _ignoreStartedLevel != -1 )	{
428 			return;
429 		}
430 		else if( !PSMLUtil.isEvaluatable(attrs, _sceneContext) )	{
431 			_ignoreStartedLevel = _level;
432 			return;
433 		}
434 
435 		if( _handler == null )	{
436 			_handler = _registry.getElementHandler( namespaceURI, localName );
437 			if( _handler == null )	{
438 				return;
439 			}
440 			else	{
441 				_sceneContext.saveCurrentNode();
442 				_handler.setSceneContext( _sceneContext );
443 				_processStartedLevel = _level;
444 				_handler.startDocument();
445 			}
446 		}
447 		try	{
448 			_handler.startElement( namespaceURI, localName, qName, attrs );
449 		}
450 		catch( Exception e )	{
451 			Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
452 			PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
453 		}
454 	}
455 
456 	public void endElement( String namespaceURI, String localName, String qName )
457 		throws SAXException
458 	{
459 		if( _aborted )	{
460 			return;
461 		}
462 
463 		if( _startNode != null )	{
464 			//NOOP
465 		}
466 		else if( _level == _ignoreStartedLevel )	{
467 			_ignoreStartedLevel = -1;
468 		}
469 		else if( _ignoreStartedLevel != -1 )	{
470 			//NOOP
471 		}
472 		else if( _handler != null )	{
473 			try	{
474 				_handler.endElement( namespaceURI, localName, qName );
475 				if( _level == _processStartedLevel )	{
476 					_handler.endDocument();
477 					if( _handler.isEndOfScene() )	{
478 						_owner.sceneEnded( this );
479 						_aborted = true;
480 					}
481 					_handler = null;
482 					_processStartedLevel = -1;
483 				}
484 			}
485 			catch( Exception e )	{
486 				Logger.fatal( MessageIDs.SCN0005F, new Object[]{_sceneContext.getCurrentPath()}, e );
487 				PekoSystem.showErrorDialog( MessageIDs.SCN0005F.getMessage(new Object[]{_sceneContext.getCurrentPath()}), e, true );
488 			}
489 		}
490 
491 		--_level;
492 		_sceneContext.popNode();
493 	}
494 
495 	public void characters( char[] ch, int begin, int length )
496 		throws SAXException
497 	{
498 		if( _aborted )	{
499 			return;
500 		}
501 
502 		if( (_ignoreStartedLevel != -1 ) || (_handler == null) )	{
503 			return;
504 		}
505 		else if( _handler != null )	{
506 			_handler.characters( ch, begin, length );
507 		}
508 	}
509 
510 	public void processingInstruction( String target, String data )
511 	{
512 	}
513 
514 //
515 //	ErrorHandler の実装
516 //
517 	public void warning( SAXParseException exception )
518 	{
519 		Logger.warn( MessageIDs.SCN0002W, new Object[]{exception.getMessage(), _sceneContext.getCurrentPath()}, exception );
520 	}
521 
522 	public void error( SAXParseException exception )
523 	{
524 		Logger.error( MessageIDs.SCN0003E, new Object[]{exception.getMessage(), _sceneContext.getCurrentPath()}, exception );
525 	}
526 
527 	public void fatalError( SAXParseException exception )
528 	{
529 		Logger.error( MessageIDs.SCN0004E, new Object[]{exception.getMessage(), _sceneContext.getCurrentPath()}, exception );
530 	}
531 
532 
533 //
534 //	EntityResolver の実装
535 //
536 	public InputSource resolveEntity( String publicId, String systemId )
537 	{
538 		boolean	isPSMLScene = false;
539 		if( publicId != null )	{
540 			if( SceneProcessor.PSML_PUBLICID.equals(publicId) )	{
541 				isPSMLScene = true;
542 			}
543 			else if( SceneProcessor.PSML_PUBLICID_ILLEGAL.equals(publicId) )	{
544 				Logger.warn( MessageIDs.SCN0013W );
545 				isPSMLScene = true;
546 			}
547 		}
548 		else if( systemId != null )	{
549 			if( SceneProcessor.PSML_SYSTEMID.equals(systemId) )	{
550 				isPSMLScene = true;
551 			}
552 		}
553 
554 		if( isPSMLScene )	{
555 			InputStream	is = null;
556 			Logger.debug( "Load internal PSML 1.0 Scene DTD." );
557 			try	{
558 				is = getClass().getClassLoader().getResourceAsStream( "psml10-scene.dtd" );
559 			}
560 			catch( Exception e )	{
561 				Logger.warn( MessageIDs.SCN0001W, e );
562 				return null;
563 			}
564 
565 			if( is != null )	{
566 				return new InputSource( is );
567 			}
568 			else	{
569 				Logger.warn( MessageIDs.SCN0001W );
570 				return null;
571 			}
572 		}
573 		else	{
574 			return null;
575 		}
576 	}
577 }