/*
* $Id: MachineFactory.java 563 2008-04-18 17:38:16Z weiju $
*
* Created on 2006/02/15
* Copyright 2005-2008 by Wei-ju Wu
* This file is part of The Z-machine Preservation Project (ZMPP).
*
* ZMPP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZMPP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZMPP. If not, see <http://www.gnu.org/licenses/>.
*/
package org.zmpp.vm;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.zmpp.base.DefaultMemory;
import org.zmpp.blorb.BlorbResources;
import org.zmpp.blorb.BlorbStory;
import org.zmpp.iff.DefaultFormChunk;
import org.zmpp.iff.FormChunk;
import org.zmpp.instructions.DefaultInstructionDecoder;
import org.zmpp.io.FileInputStream;
import org.zmpp.io.IOSystem;
import org.zmpp.io.InputStream;
import org.zmpp.io.TranscriptOutputStream;
import org.zmpp.media.Resources;
import org.zmpp.vmutil.FileUtils;
/**
* Constructing a Machine object is a very complex task, the building process
* deals with creating the game objects, the UI and the I/O system. This factory
* class offers a template for the building and leaves the concrete
* implementation which are dependent on specific input sources and UI
* technologies to sub classes.
*
* @author Wei-ju Wu
* @version 1.0
*/
public abstract class MachineFactory<T> {
private byte[] storydata, blorbdata;
private File storyfile, blorbfile;
private URL storyurl, blorburl;
private FormChunk blorbchunk;
public MachineFactory(File storyfile, File blorbfile) {
this.storyfile = storyfile;
this.blorbfile = blorbfile;
}
public MachineFactory(File blorbfile) {
this(null, blorbfile);
}
public MachineFactory(URL storyurl, URL blorburl) {
this.storyurl = storyurl;
this.blorburl = blorburl;
}
public MachineFactory(byte[] storydata, byte[] blorbdata) {
this.storydata = storydata;
this.blorbdata = blorbdata;
}
public MachineFactory(byte[] blorbdata) {
this.blorbdata = blorbdata;
}
/**
* This is the main creation function.
*
* @return the machine
*/
public Machine buildMachine() throws IOException {
final GameData gamedata = new GameDataImpl(readStoryData(), readResources());
if (isInvalidStory(gamedata.getStoryFileHeader().getVersion())) {
reportInvalidStory();
}
final Machine machine = new MachineImpl();
final InstructionDecoder decoder = new DefaultInstructionDecoder();
machine.initialize(gamedata, decoder);
initUI(machine);
initIOSystem(machine);
return machine;
}
// ***********************************************************************
// ****** Protected methods to be overridden
// ***************************************************
/**
* Initializes the user interface objects.
*
* @param machine the machine object
* @return the resulting top level user interface object
*/
abstract protected T initUI(Machine machine);
/**
* Returns the top level user interface object.
*
* @return the top level user interface object
*/
abstract public T getUI();
/**
* Reads the story data.
*
* @return the story data
* @throws IOException if reading story file revealed an error
*/
protected byte[] readStoryData() throws IOException {
if (storydata != null) {
return storydata;
}
if (blorbdata != null) {
return new BlorbStory(new DefaultFormChunk(new DefaultMemory(blorbdata))).getStoryData();
}
if (storyfile != null || blorbfile != null) {
return readStoryDataFromFile();
}
if (storyurl != null || blorburl != null) {
return readStoryDataFromUrl();
}
return null;
}
/**
* {@inheritDoc}
*/
private byte[] readStoryDataFromUrl() throws IOException {
java.io.InputStream storyis = null, blorbis = null;
try {
if (storyurl != null) {
storyis = storyurl.openStream();
}
if (blorburl != null) {
blorbis = blorburl.openStream();
}
} catch (Exception ex) {
ex.printStackTrace();
}
if (storyis != null) {
return FileUtils.readFileBytes(storyis);
} else {
return new BlorbStory(readBlorb(blorbis)).getStoryData();
}
}
/**
* {@inheritDoc}
*/
private byte[] readStoryDataFromFile() throws IOException {
if (storyfile != null) {
return FileUtils.readFileBytes(storyfile);
} else {
// Read from Z BLORB
FormChunk formchunk = readBlorbFromFile();
return formchunk != null ? new BlorbStory(formchunk).getStoryData() : null;
}
}
/**
* Reads the resource data.
*
* @return the resource data
* @throws IOException if reading resources revealed an error
*/
protected Resources readResources() throws IOException {
if (blorbfile != null) {
return readResourcesFromFile();
}
if (blorburl != null) {
return readResourcesFromUrl();
}
return null;
}
private FormChunk readBlorbFromFile() throws IOException {
if (blorbchunk == null) {
byte[] data = FileUtils.readFileBytes(blorbfile);
if (data != null) {
blorbchunk = new DefaultFormChunk(new DefaultMemory(data));
if (!"IFRS".equals(new String(blorbchunk.getSubId()))) {
throw new IOException("not a valid Blorb file");
}
}
}
return blorbchunk;
}
private Resources readResourcesFromFile() throws IOException {
FormChunk formchunk = readBlorbFromFile();
return (formchunk != null) ? new BlorbResources(formchunk) : null;
}
private FormChunk readBlorb(java.io.InputStream blorbis) throws IOException {
if (blorbchunk == null) {
byte[] data = FileUtils.readFileBytes(blorbis);
if (data != null) {
blorbchunk = new DefaultFormChunk(new DefaultMemory(data));
}
}
return blorbchunk;
}
private Resources readResourcesFromUrl() throws IOException {
FormChunk formchunk = readBlorb(blorburl.openStream());
return (formchunk != null) ? new BlorbResources(formchunk) : null;
}
/**
* This function is called to report an invalid story file.
*/
abstract protected void reportInvalidStory();
/**
* The IOSystem object.
*
* @return the IOSystem object
*/
abstract protected IOSystem getIOSystem();
/**
* The keyboard input stream object.
*
* @return the keyboard input stream
*/
abstract protected InputStream getKeyboardInputStream();
/**
* Returns the status line object.
*
* @return the status line object
*/
abstract protected StatusLine getStatusLine();
/**
* Returns the screen model object.
*
* @return the screen model
*/
abstract protected ScreenModel getScreenModel();
/**
* Returns the save game data store object.
*
* @return the save game data store
*/
abstract protected SaveGameDataStore getSaveGameDataStore();
// ************************************************************************
// ****** Private methods
// ********************************
/**
* Checks the story file version.
*
* @param version the story file version
* @return true if not supported
*/
private boolean isInvalidStory(final int version) {
return version < 1 || version > 8;
}
/**
* Initializes the I/O system.
*
* @param machine the machine object
*/
private void initIOSystem(final Machine machine) {
initInputStreams(machine);
initOutputStreams(machine);
machine.setStatusLine(getStatusLine());
machine.setScreen(getScreenModel());
machine.setSaveGameDataStore(getSaveGameDataStore());
}
/**
* Initializes the input streams.
*
* @param machine the machine object
*/
private void initInputStreams(final Machine machine) {
machine.getInput().setInputStream(0, getKeyboardInputStream());
machine.getInput().setInputStream(1, new FileInputStream(getIOSystem(),
machine.getGameData().getZsciiEncoding()));
}
/**
* Initializes the output streams.
*
* @param machine the machine object
*/
private void initOutputStreams(final Machine machine) {
final Output output = machine.getOutput();
output.setOutputStream(1, getScreenModel().getOutputStream());
output.selectOutputStream(1, true);
output.setOutputStream(2, new TranscriptOutputStream(
getIOSystem(), machine.getGameData().getZsciiEncoding()));
output.selectOutputStream(2, false);
output.setOutputStream(3, new MemoryOutputStream(machine));
output.selectOutputStream(3, false);
}
}