From: <wak...@us...> - 2011-07-03 20:06:48
|
Revision: 1598 http://rails.svn.sourceforge.net/rails/?rev=1598&view=rev Author: wakko666 Date: 2011-07-03 20:06:38 +0000 (Sun, 03 Jul 2011) Log Message: ----------- Refactor GameInfo XML parsing Modified Paths: -------------- trunk/18xx/data/GamesList.xml trunk/18xx/rails/algorithms/RevenueAdapter.java trunk/18xx/rails/algorithms/RevenueBonusTemplate.java trunk/18xx/rails/algorithms/RevenueManager.java trunk/18xx/rails/algorithms/RevenueTrainRun.java trunk/18xx/rails/game/Bank.java trunk/18xx/rails/game/BonusToken.java trunk/18xx/rails/game/Company.java trunk/18xx/rails/game/CompanyManager.java trunk/18xx/rails/game/CompanyType.java trunk/18xx/rails/game/CompanyTypeI.java trunk/18xx/rails/game/ComponentManager.java trunk/18xx/rails/game/ConfigurableComponentI.java trunk/18xx/rails/game/EndOfGameRound.java trunk/18xx/rails/game/Game.java trunk/18xx/rails/game/GameManager.java trunk/18xx/rails/game/MapHex.java trunk/18xx/rails/game/MapManager.java trunk/18xx/rails/game/OperatingRound.java trunk/18xx/rails/game/Phase.java trunk/18xx/rails/game/PhaseManager.java trunk/18xx/rails/game/PlayerManager.java trunk/18xx/rails/game/Portfolio.java trunk/18xx/rails/game/PrivateCompany.java trunk/18xx/rails/game/PublicCertificate.java trunk/18xx/rails/game/PublicCompany.java trunk/18xx/rails/game/ReportBuffer.java trunk/18xx/rails/game/Round.java trunk/18xx/rails/game/ShareSellingRound.java trunk/18xx/rails/game/StartPacket.java trunk/18xx/rails/game/StartRound.java trunk/18xx/rails/game/StartRound_1830.java trunk/18xx/rails/game/StartRound_1835.java trunk/18xx/rails/game/StockMarket.java trunk/18xx/rails/game/StockRound.java trunk/18xx/rails/game/StockSpaceType.java trunk/18xx/rails/game/Tile.java trunk/18xx/rails/game/TileI.java trunk/18xx/rails/game/TileManager.java trunk/18xx/rails/game/TrainCertificateType.java trunk/18xx/rails/game/TrainManager.java trunk/18xx/rails/game/TrainType.java trunk/18xx/rails/game/TreasuryShareRound.java trunk/18xx/rails/game/action/LayBonusToken.java trunk/18xx/rails/game/correct/CashCorrectionManager.java trunk/18xx/rails/game/correct/CorrectionManager.java trunk/18xx/rails/game/correct/CorrectionModeAction.java trunk/18xx/rails/game/correct/MapCorrectionManager.java trunk/18xx/rails/game/move/MoveStack.java trunk/18xx/rails/game/special/ExchangeForShare.java trunk/18xx/rails/game/special/LocatedBonus.java trunk/18xx/rails/game/special/SellBonusToken.java trunk/18xx/rails/game/special/SpecialProperty.java trunk/18xx/rails/game/special/SpecialRight.java trunk/18xx/rails/game/special/SpecialTileLay.java trunk/18xx/rails/game/special/SpecialTokenLay.java trunk/18xx/rails/game/special/SpecialTrainBuy.java trunk/18xx/rails/game/specific/_1825/StartRound_1825.java trunk/18xx/rails/game/specific/_1835/OperatingRound_1835.java trunk/18xx/rails/game/specific/_1835/PrussianFormationRound.java trunk/18xx/rails/game/specific/_1835/StockRound_1835.java trunk/18xx/rails/game/specific/_1851/StartRound_1851.java trunk/18xx/rails/game/specific/_1856/CGRFormationRound.java trunk/18xx/rails/game/specific/_1856/OperatingRound_1856.java trunk/18xx/rails/game/specific/_1856/PublicCompany_CGR.java trunk/18xx/rails/game/specific/_1856/StockRound_1856.java trunk/18xx/rails/game/specific/_1880/StartRound_1880.java trunk/18xx/rails/game/specific/_1880/StockRound_1880.java trunk/18xx/rails/game/specific/_1889/OperatingRound_1889.java trunk/18xx/rails/game/specific/_18AL/NameTrains.java trunk/18xx/rails/game/specific/_18AL/NamedTrainRevenueModifier.java trunk/18xx/rails/game/specific/_18AL/NamedTrainToken.java trunk/18xx/rails/game/specific/_18AL/OperatingRound_18AL.java trunk/18xx/rails/game/specific/_18EU/FinalMinorExchangeRound.java trunk/18xx/rails/game/specific/_18EU/GameManager_18EU.java trunk/18xx/rails/game/specific/_18EU/OperatingRound_18EU.java trunk/18xx/rails/game/specific/_18EU/PullmanRevenueModifier.java trunk/18xx/rails/game/specific/_18EU/StartRound_18EU.java trunk/18xx/rails/game/specific/_18EU/StockRound_18EU.java trunk/18xx/rails/game/specific/_18GA/OperatingRound_18GA.java trunk/18xx/rails/game/specific/_18Kaas/RuhrRevenueModifier.java trunk/18xx/rails/test/GameTest.java trunk/18xx/rails/ui/swing/AbstractReportWindow.java trunk/18xx/rails/ui/swing/AutoSaveLoadDialog.java trunk/18xx/rails/ui/swing/ConfigWindow.java trunk/18xx/rails/ui/swing/GameSetupWindow.java trunk/18xx/rails/ui/swing/GameStatus.java trunk/18xx/rails/ui/swing/GameUIManager.java trunk/18xx/rails/ui/swing/ImageLoader.java trunk/18xx/rails/ui/swing/ORPanel.java trunk/18xx/rails/ui/swing/ORUIManager.java trunk/18xx/rails/ui/swing/ORWindow.java trunk/18xx/rails/ui/swing/RemainingTilesWindow.java trunk/18xx/rails/ui/swing/ReportWindow.java trunk/18xx/rails/ui/swing/ReportWindowDynamic.java trunk/18xx/rails/ui/swing/Scale.java trunk/18xx/rails/ui/swing/StartRoundWindow.java trunk/18xx/rails/ui/swing/StatusWindow.java trunk/18xx/rails/ui/swing/UpgradesPanel.java trunk/18xx/rails/ui/swing/WindowSettings.java trunk/18xx/rails/ui/swing/elements/CheckBoxDialog.java trunk/18xx/rails/ui/swing/elements/ConfirmationDialog.java trunk/18xx/rails/ui/swing/elements/MessageDialog.java trunk/18xx/rails/ui/swing/elements/RadioButtonDialog.java trunk/18xx/rails/ui/swing/gamespecific/_1835/StatusWindow_1835.java trunk/18xx/rails/ui/swing/gamespecific/_1856/StatusWindow_1856.java trunk/18xx/rails/ui/swing/gamespecific/_18AL/NameTrainsDialog.java trunk/18xx/rails/ui/swing/gamespecific/_18EU/GameStatus_18EU.java trunk/18xx/rails/ui/swing/gamespecific/_18EU/GameUIManager_18EU.java trunk/18xx/rails/ui/swing/gamespecific/_18EU/StatusWindow_18EU.java trunk/18xx/rails/ui/swing/hexmap/HexMap.java trunk/18xx/rails/ui/swing/hexmap/HexMapImage.java trunk/18xx/rails/util/ListAndFixSavedFiles.java trunk/18xx/rails/util/RunGame.java trunk/18xx/rails/util/Util.java trunk/18xx/test/TestGame.java trunk/18xx/test/TestGameBuilder.java trunk/18xx/tools/ConvertTilesXML.java trunk/18xx/tools/MakeGameTileSets.java trunk/18xx/tools/XmlUtils.java Added Paths: ----------- trunk/18xx/rails/common/LocalText.java trunk/18xx/rails/common/MoneyFormatter.java trunk/18xx/rails/common/ResourceLoader.java trunk/18xx/rails/common/parser/ trunk/18xx/rails/common/parser/Config.java trunk/18xx/rails/common/parser/ConfigItem.java trunk/18xx/rails/common/parser/ConfigurationException.java trunk/18xx/rails/common/parser/GameInfo.java trunk/18xx/rails/common/parser/GameInfoParser.java trunk/18xx/rails/common/parser/GameOption.java trunk/18xx/rails/common/parser/Tag.java trunk/18xx/rails/common/parser/XMLParser.java trunk/18xx/rails/common/parser/XMLTags.java Removed Paths: ------------- trunk/18xx/rails/game/ConfigurationException.java trunk/18xx/rails/game/GameOption.java trunk/18xx/rails/game/GamesInfo.java trunk/18xx/rails/util/Config.java trunk/18xx/rails/util/ConfigItem.java trunk/18xx/rails/util/Format.java trunk/18xx/rails/util/LocalText.java trunk/18xx/rails/util/ResourceLoader.java trunk/18xx/rails/util/Tag.java Modified: trunk/18xx/data/GamesList.xml =================================================================== --- trunk/18xx/data/GamesList.xml 2011-06-30 18:56:00 UTC (rev 1597) +++ trunk/18xx/data/GamesList.xml 2011-07-03 20:06:38 UTC (rev 1598) @@ -318,17 +318,33 @@ </Game> - <Credits>Rails is a computer implementation of a number of railroad board games, -that are collectively known as the "18xx" railway game system. -Rails is a Sourceforge project. -Project founder: Brett Lentz. -Developers: Erik Vos, Stefan Frey and Brett Lentz. + <Credits>Rails is a computer implementation of a number of board games. + These games all have a railroad theme. They are collectively known as "18xx" + games due to the naming scheme used by many games in the genre. -The 18xx railway game system was originated by Francis Tresham and Hartland Trefoil Ltd. +Contributors: + Erik Vos + Stefan Frey + Freek Dijkstra + Scott Peterson + Adam Badura + Phil Davies + Bill Rosgen + Martin Brumm + Chris Shaffer + Brett Lentz All rights reserved by the respective owners of the original games (see the Game Notes per game for specific acknowledgements). -No challenge to their status is intended. + +No challenge to the original author's or publisher's rights, licensing, or status is intended. + Rails is intended as a play aid for owners of each respective boardgame. + +The Rails application and source code are distributed under +version 2 of the GNU Public License (GPL). + +A copy of the GPL should have been shipped with the game files and is also available here: + http://www.gnu.org/licenses/old-licenses/gpl-2.0.html </Credits> </GamesList> Modified: trunk/18xx/rails/algorithms/RevenueAdapter.java =================================================================== --- trunk/18xx/rails/algorithms/RevenueAdapter.java 2011-06-30 18:56:00 UTC (rev 1597) +++ trunk/18xx/rails/algorithms/RevenueAdapter.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -17,6 +17,7 @@ import org.jgrapht.Graphs; import org.jgrapht.graph.SimpleGraph; +import rails.common.LocalText; import rails.game.GameManagerI; import rails.game.MapHex; import rails.game.PhaseI; @@ -24,7 +25,6 @@ import rails.game.TrainI; import rails.game.TrainType; import rails.ui.swing.hexmap.HexMap; -import rails.util.LocalText; public final class RevenueAdapter implements Runnable { Modified: trunk/18xx/rails/algorithms/RevenueBonusTemplate.java =================================================================== --- trunk/18xx/rails/algorithms/RevenueBonusTemplate.java 2011-06-30 18:56:00 UTC (rev 1597) +++ trunk/18xx/rails/algorithms/RevenueBonusTemplate.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -5,15 +5,15 @@ import org.apache.log4j.Logger; +import rails.common.parser.ConfigurationException; +import rails.common.parser.Tag; import rails.game.ConfigurableComponentI; -import rails.game.ConfigurationException; import rails.game.GameManagerI; import rails.game.MapHex; import rails.game.PhaseI; import rails.game.PhaseManager; import rails.game.TrainManager; import rails.game.TrainType; -import rails.util.Tag; /** * defines a template for a revenue bonus at creation time of rails objects Modified: trunk/18xx/rails/algorithms/RevenueManager.java =================================================================== --- trunk/18xx/rails/algorithms/RevenueManager.java 2011-06-30 18:56:00 UTC (rev 1597) +++ trunk/18xx/rails/algorithms/RevenueManager.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -7,12 +7,12 @@ import org.apache.log4j.Logger; +import rails.common.LocalText; +import rails.common.parser.ConfigurationException; +import rails.common.parser.Tag; import rails.game.ConfigurableComponentI; -import rails.game.ConfigurationException; import rails.game.GameManagerI; import rails.game.state.ArrayListState; -import rails.util.LocalText; -import rails.util.Tag; /** * Coordinates and stores all elements related to revenue calulcation, Modified: trunk/18xx/rails/algorithms/RevenueTrainRun.java =================================================================== --- trunk/18xx/rails/algorithms/RevenueTrainRun.java 2011-06-30 18:56:00 UTC (rev 1597) +++ trunk/18xx/rails/algorithms/RevenueTrainRun.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -12,8 +12,8 @@ import rails.algorithms.NetworkVertex.StationType; import rails.algorithms.NetworkVertex.VertexType; +import rails.common.LocalText; import rails.ui.swing.hexmap.HexMap; -import rails.util.LocalText; /** * Links the results from the revenue calculator to the rails program Copied: trunk/18xx/rails/common/LocalText.java (from rev 1597, trunk/18xx/rails/util/LocalText.java) =================================================================== --- trunk/18xx/rails/common/LocalText.java (rev 0) +++ trunk/18xx/rails/common/LocalText.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -0,0 +1,153 @@ +/* $Header: /Users/blentz/rails_rcs/cvs/18xx/rails/util/LocalText.java,v 1.7 2010/03/23 18:45:16 stefanfrey Exp $*/ +package rails.common; + +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +import org.apache.log4j.Logger; + +import rails.common.parser.Config; +import rails.util.Util; + +public class LocalText extends ResourceBundle { + + private static final String TEST_LOCALE = "te_ST"; + + protected static String language = "en"; + + protected static String country = ""; + + protected static String localeCode = language; + + protected static Locale locale; + + protected static ResourceBundle localisedText; + + protected static Logger log = + Logger.getLogger(LocalText.class.getPackage().getName()); + + public static String getText(String key) { + return getText(key, (Object[]) null); + } + + public static String getText(String key, Object parameter) { + return getText(key, new Object[] { parameter }); + } + + public static String getText(String key, Object... parameters) { + /* If the text is not found, return the key in brackets */ + return getTextExecute(key, "<" + key + ">", true, parameters); + } + + public static String getTextWithDefault(String key, String defaultText) { + return getTextExecute(key, defaultText, false, (Object[]) null); + } + + // actual procedure to retrieve the local text + private static String getTextExecute(String key, String defaultText, boolean errorOnMissing, Object... parameters) { + String result = ""; + + if (key == null || key.length() == 0) return ""; + + /* Load the texts */ + if (localisedText == null) { + /* + * Check what locale has been configured, if any. If not, we use the + * default assigned above. + */ + String item; + if (Util.hasValue(item = Config.get("language"))) { + language = item.toLowerCase(); + } + if (Util.hasValue(item = Config.get("country"))) { + country = item.toUpperCase(); + localeCode = language + "_" + country; + } + if (Util.hasValue(item = Config.get("locale"))) { + localeCode = item; + if (localeCode.length() >= 2) + language = localeCode.substring(0, 2); + if (localeCode.length() >= 5) + country = localeCode.substring(3, 5); + } + log.debug("Language=" + language + ", country=" + country + + ", locale=" + localeCode); + + /* Create the locale and get the resource bundle. */ + locale = new Locale(language, country); + + try { + localisedText = + ResourceBundle.getBundle("LocalisedText", locale); + } catch (MissingResourceException e) { + System.err.println("Unable to locate LocalisedText resource: " + + e); + } + } + + /* If the key contains a space, something is wrong, check who did that! */ + if (key.indexOf(" ") > -1) { + try { + throw new Exception("Invalid resource key '" + key + "'"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // special treatment for test locale + if (localeCode.equals(TEST_LOCALE)) { + StringBuffer s = new StringBuffer(key); + if (parameters != null) + for (Object o:parameters) + s.append("," + o.toString()); + return s.toString(); + } + + /* Find the text */ + try { + result = localisedText.getString(key); + } catch (Exception e) { + if (errorOnMissing) { + System.out.println("Missing text for key " + key + " in locale " + + locale.getDisplayName() + " (" + localeCode + + ")"); + } + return defaultText; + } + + if (parameters != null) { + result = MessageFormat.format(result, parameters); + } + + return result; + + } + + public static void setLocale(String localeCode) { + + LocalText.localeCode = localeCode; + String[] codes = localeCode.split("_"); + if (codes.length > 0) language = codes[0]; + if (codes.length > 1) country = codes[1]; + + // reset localised text + localisedText = null; + } + + public Enumeration<String> getKeys() { + // TODO Auto-generated method stub + return null; + } + + public Locale getLocale() { + return locale; + } + + protected Object handleGetObject(String arg0) { + // TODO Auto-generated method stub + return null; + } +} Copied: trunk/18xx/rails/common/MoneyFormatter.java (from rev 1597, trunk/18xx/rails/util/Format.java) =================================================================== --- trunk/18xx/rails/common/MoneyFormatter.java (rev 0) +++ trunk/18xx/rails/common/MoneyFormatter.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -0,0 +1,30 @@ +/* $Header: /Users/blentz/rails_rcs/cvs/18xx/rails/util/Format.java,v 1.3 2008/06/04 19:00:39 evos Exp $*/ +package rails.common; + +import rails.common.parser.Config; +import rails.util.Util; + +public class MoneyFormatter { + + private static final String DEFAULT_MONEY_FORMAT = "$@"; + private static String moneyFormat = null; + static { + String configFormat = Config.get("money_format"); + if (Util.hasValue(configFormat) && configFormat.matches(".*@.*")) { + moneyFormat = configFormat; + } + } + + /* This class is never instantiated */ + private MoneyFormatter() {} + + public static String money(int amount) { + if (moneyFormat == null) moneyFormat = DEFAULT_MONEY_FORMAT; + return moneyFormat.replaceFirst("@", String.valueOf(amount)); + } + + public static void setMoneyFormat(String format) { + moneyFormat = format; + } + +} Copied: trunk/18xx/rails/common/ResourceLoader.java (from rev 1597, trunk/18xx/rails/util/ResourceLoader.java) =================================================================== --- trunk/18xx/rails/common/ResourceLoader.java (rev 0) +++ trunk/18xx/rails/common/ResourceLoader.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -0,0 +1,581 @@ +/* $Header: /Users/blentz/rails_rcs/cvs/18xx/rails/util/ResourceLoader.java,v 1.5 2009/01/15 20:53:28 evos Exp $*/ +package rails.common; + +import java.awt.Font; +import java.io.*; +import java.lang.reflect.Constructor; +import java.net.Socket; +import java.util.*; + +import javax.swing.text.*; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; + +import org.apache.log4j.Logger; + +/** + * Class ResourceLoader is an utility class to load a resource from a filename + * and a list of directory. + * + * @version $Id: ResourceLoader.java,v 1.5 2009/01/15 20:53:28 evos Exp $ + * @author Romain Dolbeau + * @author David Ripton + */ + +public final class ResourceLoader { + + /** + * Class ColossusClassLoader allows for class loading outside the CLASSPATH, + * i.e. from the various variant directories. + * + * @version $Id: ResourceLoader.java,v 1.5 2009/01/15 20:53:28 evos Exp $ + * @author Romain Dolbeau + */ + private static class RailsClassLoader extends ClassLoader { + List<String> directories = null; + + protected static Logger log = + Logger.getLogger(RailsClassLoader.class.getPackage().getName()); + + RailsClassLoader(ClassLoader parent) { + super(parent); + } + + RailsClassLoader() { + super(); + } + + @Override + public Class<?> findClass(String className) + throws ClassNotFoundException { + try { + int index = className.lastIndexOf("."); + String shortClassName = className.substring(index + 1); + if (index == -1) { + log.error("Loading of class \"" + className + + "\" failed (no dot in class name)"); + return null; + } + InputStream classDataIS = + getInputStream(shortClassName + ".class", directories); + if (classDataIS == null) { + log.error("Couldn't find the class file anywhere ! (" + + shortClassName + ".class)"); + throw new FileNotFoundException("missing " + shortClassName + + ".class"); + } + byte[] classDataBytes = new byte[classDataIS.available()]; + classDataIS.read(classDataBytes); + return defineClass(className, classDataBytes, 0, + classDataBytes.length); + } catch (Exception e) { + return super.findClass(className); + } + } + + void setDirectories(List<String> d) { + directories = d; + } + } + + public static final String keyContentType = "ResourceLoaderContentType"; + public static final String defaultFontName = "Lucida Sans Bold"; + public static final int defaultFontStyle = Font.PLAIN; + public static final int defaultFontSize = 12; + public static final Font defaultFont = + new Font(defaultFontName, defaultFontStyle, defaultFontSize); + + // File.separator does not work in jar files, except in Unix. + // A hardcoded '/' works in Unix, Windows, MacOS X, and jar files. + private static final String pathSeparator = "/"; + private static final ClassLoader baseCL = + rails.common.ResourceLoader.class.getClassLoader(); + private static final RailsClassLoader cl = new RailsClassLoader(baseCL); + + private static final Map<String, Object> fileCache = + Collections.synchronizedMap(new HashMap<String, Object>()); + + private final static String sep = "~"; + + protected static Logger log = + Logger.getLogger(ResourceLoader.class.getPackage().getName()); + + private static String server = null; + private static int serverPort = 0; + + public static void setDataServer(String server, int port) { + ResourceLoader.server = server; + ResourceLoader.serverPort = port; + } + + /** + * Give the String to mark directories. + * + * @return The String to mark directories. + */ + public static String getPathSeparator() { + return pathSeparator; + } + + /** empty the cache so that all files have to be reloaded */ + public synchronized static void purgeFileCache() { + log.debug("Purging File Cache."); + fileCache.clear(); + } + + /** + * Return the first InputStream from file of name filename in the list of + * directories, tell the getInputStream not to complain if not found. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @return The InputStream, or null if it was not found. + */ + public static InputStream getInputStreamIgnoreFail(String filename, + List<String> directories) { + return getInputStream(filename, directories, server != null, false, + true); + } + + /** + * Return the first InputStream from file of name filename in the list of + * directories. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @return The InputStream, or null if it was not found. + */ + public static InputStream getInputStream(String filename, List<String> directories) { + return getInputStream(filename, directories, server != null, false, + false); + } + + /** + * Return the first InputStream from file of name filename in the list of + * directories. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @param remote Ask the server for the stream. + * @param cachedOnly Only look in the cache file, do not try to load the + * file from permanent storage. + * @param ignoreFail (=don't complain) if file not found + * @return The InputStream, or null if it was not found. + */ + public static InputStream getInputStream(String filename, List<String> directories, + boolean remote, boolean cachedOnly, boolean ignoreFail) { + String mapKey = getMapKey(filename, directories); + Object cached = fileCache.get(mapKey); + byte[] data = null; + + if ((cached == null) && cachedOnly) { + if (!ignoreFail) { + log.warn("Requested file " + filename + + " is requested cached-only but is not is cache."); + } + return null; + } + + if ((cached == null) && ((!remote) || (server == null))) { + synchronized (fileCache) { + InputStream stream = null; + java.util.Iterator<String> it = directories.iterator(); + while (it.hasNext() && (stream == null)) { + Object o = it.next(); + if (o instanceof String) { + String path = (String) o; + String fullPath = + path + pathSeparator + fixFilename(filename); + + log.debug("Trying to locate InputStream: " + path + + pathSeparator + filename); + try { + File tempFile = new File(fullPath); + stream = new FileInputStream(tempFile); + } catch (Exception e) { + stream = cl.getResourceAsStream(fullPath); + } + } + } + if (stream == null) { + if (!remote && ignoreFail) { + // If someone locally requests it as ignoreFail, + // let's assume a remote requester later sees it the + // same way. + // Right now, the remote-requesting is not able to + // submit the "ignore-fail" property... + // @TODO: submit that properly? + // fileCacheIgnoreFail.put(mapKey, new Boolean(true)); + } + if (!ignoreFail) { + log.warn("getInputStream:: " + + " Couldn't get InputStream for file " + + filename + " in " + directories + + (cachedOnly ? " (cached only)" : "")); + // @TODO this sounds more serious than just a warning in + // the logs + // Anyway now at least MarkersLoader does not complain + // any more... + } + } else { + data = getBytesFromInputStream(stream); + fileCache.put(mapKey, data); + } + } + } else { + synchronized (fileCache) { + if (cached != null) { + data = (byte[]) cached; + } else { + try { + Socket fileSocket = new Socket(server, serverPort); + InputStream is = fileSocket.getInputStream(); + + if (is == null) { + log.warn("getInputStream:: " + + " Couldn't get InputStream from socket" + + " for file " + filename + " in " + + directories + + (cachedOnly ? " (cached only)" : "")); + // @TODO this sounds more serious than just a + // warning in the logs + } else { + PrintWriter out = + new PrintWriter( + fileSocket.getOutputStream(), true); + + if (ignoreFail) { + // Not in this version yet (05/2007). + // New clients could not talk with old server. + // Take this into full use somewhat later. + // out.print( + // Constants.fileServerIgnoreFailSignal + sep); + } + out.print(filename); + java.util.Iterator<String> it = directories.iterator(); + while (it.hasNext()) { + out.print(sep + it.next()); + } + out.println(); + data = getBytesFromInputStream(is); + if (data != null && data.length == 0 && !ignoreFail) { + log.warn("Got empty contents for file " + + filename + " directories " + + directories.toString()); + } + fileSocket.close(); + fileCache.put(mapKey, data); + } + } catch (Exception e) { + log.error("ResourceLoader::getInputStream() : " + e); + } + } + + } + } + return (data == null ? null : getInputStreamFromBytes(data)); + } + + /** + * Return the content of the specified file as an array of byte. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @param cachedOnly Only look in the cache file, do not try to load the + * file from permanent storage. + * @return An array of byte representing the content of the file, or null if + * it fails. + */ + public static byte[] getBytesFromFile(String filename, List<String> directories, + boolean cachedOnly, boolean ignoreFail) { + InputStream is = + getInputStream(filename, directories, server != null, + cachedOnly, ignoreFail); + if (is == null) { + // right now only FileServerThread is using this method at all. + if (!ignoreFail) { + log.warn("getBytesFromFile:: " + + " Couldn't get InputStream for file " + filename + + " in " + directories + + (cachedOnly ? " (cached only)" : "")); + } + return null; + } + return getBytesFromInputStream(is); + } + + /** + * Return the content of the specified InputStream as an array of byte. + * + * @param InputStream The InputStream to use. + * @return An array of byte representing the content of the InputStream, or + * null if it fails. + */ + private static byte[] getBytesFromInputStream(InputStream is) { + byte[] all = new byte[0]; + + try { + byte[] data = new byte[1024 * 64]; + int r = is.read(data); + while (r > 0) { + byte[] temp = new byte[all.length + r]; + for (int i = 0; i < all.length; i++) { + temp[i] = all[i]; + } + for (int i = 0; i < r; i++) { + temp[i + all.length] = data[i]; + } + all = temp; + r = is.read(data); + } + } catch (Exception e) { + log.error("Can't Stringify stream " + is + " (" + e + ")"); + } + return all; + } + + /** + * Return the content of the specified byte array as an InputStream. + * + * @param data The byte array to convert. + * @return An InputStream whose content is the data byte array. + */ + private static InputStream getInputStreamFromBytes(byte[] data) { + if (data == null) { + log.warn("getInputStreamFromBytes:: " + + " Can't create InputStream from null byte array"); + return null; + } + return new ByteArrayInputStream(data); + } + + /** + * Return the first OutputStream from file of name filename in the list of + * directories. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @return The OutputStream, or null if it was not found. + */ + public static OutputStream getOutputStream(String filename, List<String> directories) { + OutputStream stream = null; + java.util.Iterator<String> it = directories.iterator(); + while (it.hasNext() && (stream == null)) { + Object o = it.next(); + if (o instanceof String) { + String path = (String) o; + String fullPath = path + pathSeparator + fixFilename(filename); + try { + stream = new FileOutputStream(fullPath); + } catch (Exception e) { + log.debug("getOutputStream:: " + + " Couldn't get OutputStream for file " + + filename + " in " + directories + "(" + + e.getMessage() + ")"); + } + } + } + return (stream); + } + + /** + * Return the first Document from file of name filename in the list of + * directories. It also add a property of key keyContentType and of type + * String describing the content type of the Document. This can currently + * load HTML and pure text. + * + * @param filename Name of the file to load. + * @param directories List of directories to search (in order). + * @return The Document, or null if it was not found. + */ + public static Document getDocument(String filename, List<String> directories) { + InputStream htmlIS = + getInputStreamIgnoreFail(filename + ".html", directories); + if (htmlIS != null) { + try { + HTMLEditorKit htedk = new HTMLEditorKit(); + HTMLDocument htdoc = new HTMLDocument(htedk.getStyleSheet()); + htdoc.putProperty(keyContentType, "text/html"); + htedk.read(htmlIS, htdoc, 0); + return htdoc; + } catch (Exception e) { + log.error("html document exists, but cannot be loaded (" + + filename + "): " + e); + } + return null; + } + InputStream textIS = + getInputStreamIgnoreFail(filename + ".txt", directories); + if (textIS == null) { + textIS = getInputStreamIgnoreFail(filename, directories); + } + if (textIS != null) { + try { + // Must be a StyledDocument not a PlainDocument for + // JEditorPane.setDocument() + StyledDocument txtdoc = new DefaultStyledDocument(); + char[] buffer = new char[128]; + InputStreamReader textISR = new InputStreamReader(textIS); + int read = 0; + int offset = 0; + while (read != -1) { + read = textISR.read(buffer, 0, 128); + if (read != -1) { + txtdoc.insertString(offset, + new String(buffer, 0, read), null); + offset += read; + } + } + txtdoc.putProperty(keyContentType, "text/plain"); + return txtdoc; + } catch (Exception e) { + log.error("text document exists, but cannot be loaded (" + + filename + "): " + e); + } + return null; + } + log.error("No document for basename " + filename + " found " + + "(neither .html, .txt nor without extention)!"); + return null; + } + + /** + * Return the key to use in the image and file caches. + * + * @param filename Name of the file. + * @param directories List of directories. + * @return A String to use as a key when storing/loading in a cache the + * specified file from the specified list of directories. + */ + private static String getMapKey(String filename, List<String> directories) { + String[] filenames = new String[1]; + filenames[0] = filename; + return getMapKey(filenames, directories); + } + + /** + * Return the key to use in the image cache. + * + * @param filenames Array of name of files. + * @param directories List of directories. + * @return A String to use as a key when storing/loading in a cache the + * specified array of name of files from the specified list of directories. + */ + private static String getMapKey(String[] filenames, List<String> directories) { + StringBuffer buf = new StringBuffer(filenames[0]); + for (int i = 1; i < filenames.length; i++) { + buf.append(","); + buf.append(filenames[i]); + } + Iterator<String> it = directories.iterator(); + while (it.hasNext()) { + Object o = it.next(); + if (o instanceof String) { + buf.append(","); + buf.append(o); + } + } + return buf.toString(); + } + + /** + * Fix a filename by replacing space with underscore. + * + * @param filename Filename to fix. + * @return The fixed filename. + */ + private static String fixFilename(String filename) { + return filename.replace(' ', '_'); + } + + /** + * Create an instance of the class whose name is in parameter. + * + * @param className The name of the class to use. + * @param directories List of directories to search (in order). + * @return A new object, instance from the given class. + */ + public static Object getNewObject(String className, List<String> directories) { + return getNewObject(className, directories, null); + } + + /** + * Create an instance of the class whose name is in parameter, using + * parameters. + * + * If no parameters are given, the default constructor is used. + * + * @TODO this is full of catch(Exception) blocks, which all return null. + * Esp. returning null seems a rather bad idea, since it will most likely + * turn out to be NPEs somewhere later. + * + * @param className The name of the class to use, must not be null. + * @param directories List of directories to search (in order), must not be + * null. + * @param parameter Array of parameters to pass to the constructor, can be + * null. + * @return A new object, instance from the given class or null if + * instantiation failed. + */ + public static Object getNewObject(String className, List<String> directories, + Object[] parameter) { + Class<?> theClass = null; + cl.setDirectories(directories); + try { + theClass = cl.loadClass(className); + } catch (Exception e) { + log.error("Loading of class \"" + className + "\" failed (" + e + + ")"); + return null; + } + if (parameter != null) { + Class<?>[] paramClasses = new Class[parameter.length]; + for (int i = 0; i < parameter.length; i++) { + paramClasses[i] = parameter[i].getClass(); + } + try { + Constructor<?> c = theClass.getConstructor(paramClasses); + return c.newInstance(parameter); + } catch (Exception e) { + log.error("Loading or instantiating class' constructor for \"" + + className + "\" failed (" + e + ")"); + return null; + } + } else { + try { + return theClass.newInstance(); + } catch (Exception e) { + log.error("Instantiating \"" + className + "\" failed (" + e + + ")"); + return null; + } + } + } + + /** + * Force adding the given data as belonging to the given filename in the + * file cache. + * + * @param filename Name of the Image file to add. + * @param directories List of directories to search (in order). + * @param data File content to add. + */ + public static void putIntoFileCache(String filename, List<String> directories, + byte[] data) { + String mapKey = getMapKey(filename, directories); + fileCache.put(mapKey, data); + } + + /** + * Force adding the given data as belonging to the given key in the file + * cache. + * + * @see #getMapKey(String, List) + * @see #getMapKey(String[], List) + * @param mapKey Key to use in the cache. + * @param data File content to add. + */ + public static void putIntoFileCache(String mapKey, byte[] data) { + fileCache.put(mapKey, data); + } +} Copied: trunk/18xx/rails/common/parser/Config.java (from rev 1597, trunk/18xx/rails/util/Config.java) =================================================================== --- trunk/18xx/rails/common/parser/Config.java (rev 0) +++ trunk/18xx/rails/common/parser/Config.java 2011-07-03 20:06:38 UTC (rev 1598) @@ -0,0 +1,584 @@ +/* $Header: /Users/blentz/rails_rcs/cvs/18xx/rails/util/Config.java,v 1.13 2010/06/24 21:48:08 stefanfrey Exp $*/ +package rails.common.parser; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.log4j.Logger; + +import rails.game.GameManager; +import rails.util.Util; + +/** + * This is a simple utility class with a collection of static functions to load + * a property object from a property file, to retrieve a particular value from + * the property file etc. + * + * @author Ramiah Bala, + * @author Erik Vos + * @author Stefan Frey + * @version 2.0 + */ +public final class Config { + + protected static Logger log; + + /** Commandline options */ + private static final String CONFIGFILE_CMDLINE = "configfile"; + private static final String PROFILE_CMDLINE = "profile"; + + /** XML setup */ + private static final String CONFIG_XML_DIR = "data"; + private static final String CONFIG_XML_FILE = "Properties.xml"; + private static final String CONFIG_TAG = "Properties"; + private static final String SECTION_TAG = "Section"; + private static final String ITEM_TAG = "Property"; + + /** Log 4j configuration */ + private static final String LOG4J_CONFIG_FILE = "log4j.properties"; + + /** Rails profile configurations */ + private static String defaultProfilesFile = "data/profiles/default.profiles"; + private static Properties defaultProfiles = new Properties(); + private static String userProfilesFile = "user.profiles"; + private static Properties userProfiles = new Properties(); + private static boolean profilesLoaded = false; + private static String DEFAULT_PROFILE_SELECTION = "default"; // can be overwritten + private static final String TEST_PROFILE_SELECTION = ".test"; // used as default profile for integration tests + private static final String STANDARD_PROFILE_SELECTION = "user"; + private static final String DEFAULTPROFILE_PROPERTY = "default.profile"; + private static final String PROFILENAME_PROPERTY = "profile.name"; + + /** selected profile */ + private static String selectedProfile; + private static boolean legacyConfigFile; + + /** properties storage. */ + private static Properties defaultProperties = new Properties(); + private static Properties userProperties = new Properties(); + private static boolean propertiesLoaded = false; + + /** Map that holds the panel, which contains config items */ + private static Map<String, List<ConfigItem>> configSections = null; + + /** + * Hidden constructor, the class is never instantiated, everything is static + */ + private Config() {} + + /** + * Reads the config.xml file that defines all config items + */ + public static void readConfigSetupXML() { + List<String> directories = new ArrayList<String>(); + directories.add(CONFIG_XML_DIR); + try { + // Find the config tag inside the the config xml file + Tag configTag = + Tag.findTopTagInFile(CONFIG_XML_FILE, directories, CONFIG_TAG); + log.debug("Opened config xml, filename = " + CONFIG_XML_FILE); + + // define sections + configSections = new LinkedHashMap<String, List<ConfigItem>>(); + + // find sections + List<Tag> sectionTags = configTag.getChildren(SECTION_TAG); + if (sectionTags != null) { + for (Tag sectionTag:sectionTags) { + // find name attribute + String sectionName = sectionTag.getAttributeAsString("name"); + if (!Util.hasValue(sectionName)) continue; + + // find items + List<Tag> itemTags = sectionTag.getChildren(ITEM_TAG); + if (itemTags == null || itemTags.size() == 0) continue; + List<ConfigItem> sectionItems = new ArrayList<ConfigItem>(); + for (Tag itemTag:itemTags) { + sectionItems.add(new ConfigItem(itemTag)); + } + configSections.put(sectionName, sectionItems); + } + } + + } catch (ConfigurationException e) { + log.error("Configuration error in setup of " + CONFIG_XML_FILE + ", exception = " + e); + } + } + + public static Map<String, List<ConfigItem>> getConfigSections() { + if (configSections == null) { + readConfigSetupXML(); + } + log.debug("Configuration setup = " + configSections); + return configSections; + } + + public static int getMaxElementsInPanels() { + int maxElements = 0; + for (List<ConfigItem> panel:configSections.values()) { + maxElements = Math.max(maxElements, panel.size()); + } + log.debug("Configuration sections with maximum elements of " + maxElements); + return maxElements; + } + + /** + * updates the profile according to the changes in configitems + */ + public static void updateProfile(boolean applyInitMethods) { + for (List<ConfigItem> items:configSections.values()) { + for (ConfigItem item:items) { + if (!item.hasNewValue()) continue; + if (item.getNewValue().equals(defaultProperties.get(item.name))) { + userProperties.remove(item.name); + continue; + } + userProperties.setProperty(item.name, item.getNewValue()); + if (applyInitMethods) item.callInitMethod(); + log.debug("Changed property name = " + item.name + " to value = " + item.getNewValue()); + item.setNewValue(null); + } + } + } + + /** + * reverts all changes in configitems + */ + public static void revertProfile() { + for (List<ConfigItem> items:configSections.values()) { + for (ConfigItem item:items) { + item.setNewValue(null); + } + } + } + + /** + * First tries to return {key}.{gameName}, if undefined returns {key} + */ + public static String getGameSpecific(String key) { + return Config.getSpecific(key, GameManager.getInstance().getGameName()); + } + + /** + * First tries to return {key}.{appendix}, if undefined returns {key} + */ + public static String getSpecific(String key, String appendix) { + String value = Config.get(key + "." + appendix); + if (value == "") { + value = Config.get(key); + } + return value; + } + + public static String get(String key) { + return get(key, ""); + } + + public static String get(String key, String defaultValue) { + if (defaultProperties.isEmpty() || !propertiesLoaded) { + initialLoad(); + } + if (userProperties.containsKey(key)) return userProperties.getProperty(key).trim(); + if (defaultProperties.containsKey(key)) return defaultProperties.getProperty(key).trim(); + + return defaultValue; + } + + + /** + * save active Profile + */ + public static boolean saveActiveProfile() { + String filepath = userProfiles.getProperty(selectedProfile); + if (Util.hasValue(filepath)) { + return storePropertyFile(userProperties, filepath); + } else { + return false; + } + } + + /** + * change active Profile + */ + public static boolean changeActiveProfile(String profileName) { + readConfigSetupXML(); + loadProfile(profileName); + selectedProfile = profileName; + return true; + } + + /** + * create new profile + */ + public static boolean createUserProfile(String profileName, String defaultProfile) { + userProperties = new Properties(); + defaultProperties = new Properties(); + + // add to list of user profiles + userProfiles.setProperty(profileName, ""); + + // define and load default profile + String defaultConfigFile = defaultProfiles.getProperty(defaultProfile); + userProperties.setProperty(PROFILENAME_PROPERTY, profileName); + userProperties.setProperty(DEFAULTPROFILE_PROPERTY, defaultProfile); + loadPropertyFile(defaultProperties, defaultConfigFile, true); + setSaveDirDefaults(); + + selectedProfile = profileName; + return true; + } + + + private static Map<String, String> convertProperties(Properties properties, boolean visibleOnly) { + Map<String, String> converted = new HashMap<String, String>(); + for (Object key:properties.keySet()) { + if (visibleOnly && ((String)key).substring(0,1).equals(".")) continue; + converted.put((String) key, (String) properties.get(key)); + } + return converted; + } + + /** + * get all default profiles + */ + public static List<String> getDefaultProfiles(boolean visibleOnly) { + List<String> profiles = new ArrayList<String>(convertProperties(defaultProfiles, visibleOnly).keySet()); + Collections.sort(profiles); + return profiles; + } + + public static String getDefaultProfileSelection() { + return DEFAULT_PROFILE_SELECTION; + } + + /** + * get all user profiles + */ + public static List<String> getUserProfiles() { + List<String> profiles = new ArrayList<String>(convertProperties(userProfiles, true).keySet()); + Collections.sort(profiles); + return profiles; + } + + /** + * get all (visible default + user) profiles + */ + public static List<String> getAllProfiles() { + List<String> profiles = getDefaultProfiles(true); + profiles.addAll(getUserProfiles()); + return profiles; + } + + /** + * checks if profile is default profile + */ + public static boolean isDefaultProfile(String profileName) { + return !(defaultProfiles.get(profileName) == null); + } + + /** + * returns name of (active) default profile + */ + public static String getDefaultProfileName() { + return userProperties.getProperty(DEFAULTPROFILE_PROPERTY); + } + + /** + * returns name of active profile + */ + public static String getActiveProfileName() { + return selectedProfile; + } + + /** + * returns true if legacy configfile is used + */ + public static boolean isLegacyConfigFile() { + return legacyConfigFile; + } + + /** + * sets filename for an active profile (and store list of profiles) + * @return false if list of profiles cannot be stored + */ + public static boolean setActiveFilepath(String filepath) { + userProfiles.setProperty(selectedProfile, filepath); + return storePropertyFile(userProfiles, userProfilesFile); + } + + /** + * returns filename of active profile, (null if undefined or default profile) + */ + public static String getActiveFilepath() { + return userProfiles.getProperty(selectedProfile); + } + + /** + * @return if user location is defined + */ + public static boolean isFilePathDefined() { + return Util.hasValue(userProfiles.getProperty(selectedProfile)); + } + + + /** + * activates settings used for testing + */ + public static void setConfigTest() { + /* + * Set the system property that tells log4j to use this file. (Note: + * this MUST be done before updating Config) + */ + String log4jSelection = System.getProperty("log4j.configuration"); + if (!Util.hasValue(log4jSelection)) { + log4jSelection = LOG4J_CONFIG_FILE; + } + System.setProperty("log4j.configuration", log4jSelection); + System.out.println("log4j.configuration = " + log4jSelection); + + // delayed setting of logger + log = Logger.getLogger(Config.class.getPackage().getName()); + + // define settings for testing + legacyConfigFile = false; + DEFAULT_PROFILE_SELECTION = TEST_PROFILE_SELECTION; + selectedProfile = null; + + initialLoad(); + } + + + /** + * activates configuration settings based on default settings + */ + public static void setConfigSelection() { + /* + * Set the system property that tells log4j to use this file. (Note: + * this MUST be done before updating Config) + */ + String log4jSelection = System.getProperty("log4j.configuration"); + if (!Util.hasValue(log4jSelection)) { + log4jSelection = LOG4J_CONFIG_FILE; + } + System.setProperty("log4j.configuration", log4jSelection); + System.out.println("log4j.configuration = " + log4jSelection); + + // delayed setting of logger + log = Logger.getLogger(Config.class.getPackage().getName()); + + /* + * Check if the profile has been set from the command line + * to do this is adding an option to the java command: -Dprofile=<profile-name> + */ + String configSelection = System.getProperty(PROFILE_CMDLINE); + System.out.println("Cmdline profile selection = " + configSelection); + + legacyConfigFile = false; + if (configSelection == null) { + /* + * Check if the property file has been set on the command line. The way + * to do this is adding an option to the java command: -Dconfigfile=<property-filename> + * + * This is for legacy reasons only + */ + configSelection = System.getProperty(CONFIGFILE_CMDLINE); + + if (Util.hasValue(configSelection)) { + System.out.println("Cmdline configfile selection (legacy!) = " + configSelection); + legacyConfigFile = true; + } + } + + /* if nothing has selected so far, choose standardProfile */ + if (!Util.hasValue(configSelection)) { + configSelection = STANDARD_PROFILE_SELECTION; + } + + selectedProfile = configSelection; + if (!legacyConfigFile) { + System.out.println("Profile selection = " + selectedProfile); + } + + initialLoad(); + } + + + private static void initialLoad() { + if (legacyConfigFile) { + if (!propertiesLoaded) { + loadPropertyFile(defaultProperties, selectedProfile, false); + propertiesLoaded = true; + setSaveDirDefaults(); + } + return; + } + + if (!profilesLoaded) { + loadPropertyFile(defaultProfiles, defaultProfilesFile, true); + loadPropertyFile(userProfiles, userProfilesFile, false); + profilesLoaded = true; + } + + /* Tell the properties loader to read this file. */ + log.info("Selected profile = " + selectedProfile); + + if (!propertiesLoaded) { + loadProfile(selectedProfile); + propertiesLoaded = true; + } + } + + + /** + * loads an external user profile + * defined by the filepath + */ + public static boolean loadProfileFromFile(File file) { + String filepath = file.getPath(); + if (loadPropertyFile(userProperties, filepath, false)) { + String profile = userProperties.getProperty(PROFILENAME_PROPERTY); + if (!Util.hasValue(profile)) { + profile = STANDARD_PROFILE_SELECTION; + } + selectedProfile = profile; + setActiveFilepath(filepath); // do not set filepath on import + loadDefaultProfile(); + setSaveDirDefaults(); + return true; + } else { + return false; + } + } + + /** + * imports an external user profile into an existing profile + * defined by the filepath + */ + public static boolean importProfileFromFile(File file) { + String filepath = file.getPath(); + Properties importProperties = new Properties(); + if (loadPropertyFile(importProperties, filepath, false)) { + userProperties.putAll(importProperties); + setSaveDirDefaults(); + return true; + } else { + return false; + } + } + + + /** + * loads a user profile + * if not defined or loadable, creates a default user profile + */ + private static void loadProfile(String userProfile) { + // reset properties + userProperties = new Properties(); + defaultProperties = new Properties(); + + String userConfigFile = null; + if (Util.hasValue(userProfile)) { + // check if the profile is already defined under userProfiles + userConfigFile = userProfiles.getProperty(userProfile); + if (Util.hasValue(userConfigFile) && // load user profile + loadPropertyFile(userProperties, userConfigFile, fa... [truncated message content] |