/*
* Copyright (c) 2000-2011 by Rodney Kinney, Brent Easton
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.launch;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.GameModule;
import VASSAL.build.module.BasicCommandEncoder;
import VASSAL.build.module.BasicLogger;
import VASSAL.build.module.Chatter;
import VASSAL.build.module.Documentation;
import VASSAL.build.module.GameRefresher;
import VASSAL.build.module.GameState;
import VASSAL.build.module.GlobalOptions;
import VASSAL.build.module.Map;
import VASSAL.build.module.PieceWindow;
import VASSAL.build.module.PlayerRoster;
import VASSAL.build.module.PluginsLoader;
import VASSAL.build.module.PrototypesContainer;
import VASSAL.build.module.gamepieceimage.GamePieceImageDefinitions;
import VASSAL.build.module.properties.GlobalProperties;
import VASSAL.chat.AddressBookServerConfigurer;
import VASSAL.chat.ChatServerFactory;
import VASSAL.chat.DynamicClient;
import VASSAL.chat.DynamicClientFactory;
import VASSAL.chat.HybridClient;
import VASSAL.chat.jabber.JabberClientFactory;
import VASSAL.chat.node.NodeClientFactory;
import VASSAL.chat.peer2peer.P2PClientFactory;
import VASSAL.chat.ui.ChatServerControls;
import VASSAL.command.Command;
import VASSAL.configure.PasswordConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.configure.TextConfigurer;
import VASSAL.i18n.Language;
import VASSAL.i18n.Resources;
import VASSAL.preferences.PositionOption;
import VASSAL.preferences.Prefs;
import VASSAL.tools.DataArchive;
import VASSAL.tools.ReflectionUtils;
import VASSAL.tools.SequenceEncoder;
import VASSAL.tools.io.IOUtils;
import VASSAL.tools.menu.MenuManager;
public class BasicModule extends GameModule {
private static final Logger log =
LoggerFactory.getLogger(BasicModule.class);
private static char COMMAND_SEPARATOR = (char) KeyEvent.VK_ESCAPE;
protected ChatServerControls serverControls;
protected GameRefresher gameRefresher;
public BasicModule(DataArchive archive) {
super(archive);
}
protected void build() throws IOException {
final DataArchive darch = getDataArchive();
final File f = new File(darch.getName());
if (!f.exists() || f.length() == 0) {
// new module, no buildFile
build(null);
}
else {
// existing module
BufferedInputStream in = null;
try {
try {
in = new BufferedInputStream(darch.getInputStream(BUILDFILE));
}
// FIXME: review error message
// FIXME: this should be more specific, to separate the case where
// we have failed I/O from when we read ok but have no module
catch (IOException e) {
throw (IOException) new IOException(
Resources.getString("BasicModule.not_a_module") //$NON-NLS-1$
).initCause(e);
}
final Document doc = Builder.createDocument(in);
build(doc.getDocumentElement());
in.close();
}
finally {
IOUtils.closeQuietly(in);
}
}
MenuManager.getInstance().addAction("Prefs.edit_preferences",
getPrefs().getEditor().getEditAction());
gameRefresher = new GameRefresher(this);
gameRefresher.addTo(this);
MenuManager.getInstance().addAction("GameRefresher.refresh_counters",
gameRefresher.getRefreshAction());
}
public void build(Element e) {
/*
* We determine the name of the module at the very beginning, so we
* know which preferences to read.
*/
if (e != null) {
gameName = e.getAttribute(MODULE_NAME);
if (e.getAttribute(VASSAL_VERSION_CREATED).length() > 0) {
vassalVersionCreated = e.getAttribute(VASSAL_VERSION_CREATED);
}
}
initIdentityPreferences();
initImagePreferences();
Prefs.initSharedGlobalPrefs();
initGameState();
initLogger();
initServer();
new PluginsLoader().addTo(this);
if (e != null) {
super.build(e);
ensureComponent(GamePieceImageDefinitions.class);
ensureComponent(GlobalProperties.class);
ensureComponent(Language.class);
}
else {
buildDefaultComponents();
}
initFrame();
}
protected void initIdentityPreferences() {
idChangeSupport = new PropertyChangeSupport(this);
StringConfigurer fullName = new StringConfigurer(GameModule.REAL_NAME, Resources.getString("Prefs.name_label"), Resources.getString("Prefs.newbie")); //$NON-NLS-1$ //$NON-NLS-2$
fullName.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
idChangeSupport.firePropertyChange(evt);
}});
TextConfigurer profile = new TextConfigurer(GameModule.PERSONAL_INFO, Resources.getString("Prefs.personal_info"), ""); //$NON-NLS-1$ //$NON-NLS-2$
profile.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
idChangeSupport.firePropertyChange(evt);
}});
StringConfigurer user = new PasswordConfigurer(GameModule.SECRET_NAME, Resources.getString("Prefs.password_label"), Resources.getString("Prefs.password_prompt", System.getProperty("user.name"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
user.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
GameModule.setUserId((String) evt.getNewValue());
}
});
GameModule.getGameModule().getPrefs().addOption(Resources.getString("Prefs.personal_tab"), fullName); //$NON-NLS-1$ //$NON-NLS-2$
GameModule.getGameModule().getPrefs().addOption(Resources.getString("Prefs.personal_tab"), user); //$NON-NLS-1$ //$NON-NLS-2$
GameModule.getGameModule().getPrefs().addOption(Resources.getString("Prefs.personal_tab"), profile); //$NON-NLS-1$
GameModule.setUserId(user.getValueString());
}
protected void initImagePreferences() {
}
protected void initServer() {
ChatServerFactory.register(NodeClientFactory.NODE_TYPE, new NodeClientFactory());
ChatServerFactory.register(DynamicClientFactory.DYNAMIC_TYPE, new DynamicClientFactory());
ChatServerFactory.register(P2PClientFactory.P2P_TYPE, new P2PClientFactory());
ChatServerFactory.register(JabberClientFactory.JABBER_SERVER_TYPE, new JabberClientFactory());
server = new DynamicClient();
AddressBookServerConfigurer config = new AddressBookServerConfigurer("ServerImpl", "Server", (HybridClient) server);
Prefs.getGlobalPrefs().addOption(Resources.getString("Chat.server"), config); //$NON-NLS-1$
serverControls = new ChatServerControls();
serverControls.addTo(this);
}
protected void initLogger() {
logger = new BasicLogger();
((BasicLogger) logger).build(null);
((BasicLogger) logger).addTo(this);
}
protected void initGameState() {
theState = new GameState();
theState.addTo(this);
addCommandEncoder(theState);
}
public Command decode(String command) {
if (command == null) {
return null;
}
Command c = null;
final SequenceEncoder.Decoder st =
new SequenceEncoder.Decoder(command, COMMAND_SEPARATOR);
String first = st.nextToken();
if (command.equals(first)) {
c = decodeSubCommand(first);
}
else {
Command next = null;
c = decode(first);
while (st.hasMoreTokens()) {
next = decode(st.nextToken());
c = c == null ? next : c.append(next);
}
}
return c;
}
private Command decodeSubCommand(String subCommand) {
Command c = null;
for (int i = 0; i < commandEncoders.length && c == null; ++i) {
c = commandEncoders[i].decode(subCommand);
}
return c;
}
public String encode(Command c) {
if (c == null) {
return null;
}
String s = encodeSubCommand(c);
String s2;
Command sub[] = c.getSubCommands();
if (sub.length > 0) {
SequenceEncoder se = new SequenceEncoder(s, COMMAND_SEPARATOR);
for (int i = 0; i < sub.length; ++i) {
s2 = encode(sub[i]);
if (s2 != null) {
se.append(s2);
}
}
s = se.getValue();
}
return s;
}
private String encodeSubCommand(Command c) {
String s = null;
for (int i = 0; i < commandEncoders.length && s == null; ++i) {
s = commandEncoders[i].encode(c);
}
return s;
}
protected void buildDefaultComponents() {
addComponent(BasicCommandEncoder.class);
addComponent(Documentation.class);
addComponent(PlayerRoster.class);
addComponent(GlobalOptions.class);
addComponent(Map.class);
addComponent(GamePieceImageDefinitions.class);
addComponent(GlobalProperties.class);
addComponent(PrototypesContainer.class);
addComponent(PieceWindow.class);
addComponent(Chatter.class);
addComponent(Language.class);
}
protected void initFrame() {
final Rectangle screen = VASSAL.Info.getScreenBounds(frame);
if (GlobalOptions.getInstance().isUseSingleWindow()) {
// FIXME: annoying!
frame.setLocation(screen.getLocation());
frame.setSize(screen.width, screen.height / 3);
}
else {
final String key = "BoundsOfGameModule"; //$NON-NLS-1$
final Rectangle r = new Rectangle(0, 0, screen.width, screen.height / 4);
getPrefs().addOption(new PositionOption(key, frame, r));
}
final String mess = Resources.getString(
"BasicModule.version_message", getLocalizedGameName(), moduleVersion); //$NON-NLS-1$
warn(mess);
log.warn(mess);
initFrameTitle();
}
protected void ensureComponent(Class<? extends Buildable> componentClass) {
if (getComponentsOf(componentClass).isEmpty()) {
addComponent(componentClass);
}
}
protected void addComponent(Class<? extends Buildable> componentClass) {
Buildable child = null;
try {
child = componentClass.getConstructor().newInstance();
}
catch (Throwable t) {
ReflectionUtils.handleNewInstanceFailure(t, componentClass);
}
if (child != null) {
child.build(null);
child.addTo(this);
add(child);
}
}
public ChatServerControls getServerControls() {
return serverControls;
}
/*
* The module I18n key prefix is null for the top level.
*/
public String getI18nPrefix() {
return "";
}
}