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 }