changeset 384:b21a85c079f5 default-army-name

Re #153: Default name for armies * Add explicit default value in schema * Swap adding of extra constructor parameter (which wasn't stored at any point) to using property * Clean up line endings
author IBBoard <dev@ibboard.co.uk>
date Tue, 07 Sep 2010 20:02:44 +0000
parents 8981fc45fe17
children 638c8b91ba76
files api/AbstractWarFoundryLoader.cs api/Factories/Xml/WarFoundryXmlRaceFactory.cs api/Objects/Race.cs schemas/race.xsd
diffstat 4 files changed, 732 insertions(+), 740 deletions(-) [+]
line diff
     1.1 --- a/api/AbstractWarFoundryLoader.cs	Tue Sep 07 11:53:22 2010 +0000
     1.2 +++ b/api/AbstractWarFoundryLoader.cs	Tue Sep 07 20:02:44 2010 +0000
     1.3 @@ -1,685 +1,685 @@
     1.4 -// This file (AbstractWarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
     1.5 -// 
     1.6 -// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
     1.7 -
     1.8 -using System;
     1.9 -using System.Collections.Generic;
    1.10 -using System.IO;
    1.11 -using IBBoard.Collections;
    1.12 -using IBBoard.IO;
    1.13 -using IBBoard.Logging;
    1.14 -using IBBoard.WarFoundry.API.Factories;
    1.15 -using IBBoard.WarFoundry.API.Objects;
    1.16 -
    1.17 -namespace IBBoard.WarFoundry.API
    1.18 -{
    1.19 -	/// <summary>
    1.20 -	/// The base abstract implementation of a WarFoundry file loader
    1.21 -	/// </summary>
    1.22 -	public abstract class AbstractWarFoundryLoader
    1.23 -	{
    1.24 -		private ICollection<DirectoryInfo> directories;
    1.25 -		private ICollection<INativeWarFoundryFactory> factories;
    1.26 -		private ICollection<INonNativeWarFoundryFactory> nonNativeFactories;
    1.27 -		private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects;
    1.28 -		public delegate void FileLoadingCompleteDelegate(List<FileLoadFailure> failures);
    1.29 -		public event MethodInvoker FileLoadingStarted;
    1.30 -		public event FileLoadingCompleteDelegate FileLoadingFinished;
    1.31 -		
    1.32 -		protected AbstractWarFoundryLoader()
    1.33 -		{
    1.34 -			directories = new List<DirectoryInfo>();
    1.35 -			factories = new List<INativeWarFoundryFactory>();
    1.36 -			nonNativeFactories = new List<INonNativeWarFoundryFactory>();
    1.37 -			loadedObjects = new Dictionary<IWarFoundryFactory,SimpleSet<IWarFoundryObject>>();
    1.38 -		}
    1.39 -		
    1.40 -		/// <summary>
    1.41 -		/// Adds a directory to the collection of directories that will be searched for WarFoundry data files.
    1.42 -		/// </summary>
    1.43 -		/// <param name="directory">
    1.44 -		/// The <see cref="DirectoryInfo"/> to add to the list for searching for data files
    1.45 -		/// </param>
    1.46 -		public void AddLoadDirectory(DirectoryInfo directory)
    1.47 -		{
    1.48 -			if (!directories.Contains(directory))
    1.49 -			{
    1.50 -				directories.Add(directory);
    1.51 -			}
    1.52 -		}
    1.53 -		
    1.54 -		/// <summary>
    1.55 -		/// Removes a directory from the collection of directories that will be searched for WarFoundry data files.
    1.56 -		/// </summary>
    1.57 -		/// <param name="directory">
    1.58 -		/// A <see cref="DirectoryInfo"/>
    1.59 -		/// </param>
    1.60 -		public void RemoveLoadDirectory(DirectoryInfo directory)
    1.61 -		{
    1.62 -			if (directories.Contains(directory))
    1.63 -			{
    1.64 -				directories.Remove(directory);
    1.65 -			}
    1.66 -		}
    1.67 -		
    1.68 -		/// <summary>
    1.69 -		/// Registers a <see cref="INativeWarFoundryFactory"/> as a factory that can parse native data files.
    1.70 -		/// </summary>
    1.71 -		/// <param name="factory">
    1.72 -		/// The <see cref="INativeWarFoundryFactory"/> to register to parse native data files.
    1.73 -		/// </param>
    1.74 -		public void RegisterFactory(INativeWarFoundryFactory factory)
    1.75 -		{
    1.76 -			if (!factories.Contains(factory))
    1.77 -			{
    1.78 -				factories.Add(factory);
    1.79 -			}
    1.80 -		}
    1.81 -		
    1.82 -		/// <summary>
    1.83 -		/// Unregisters a <see cref="INativeWarFoundryFactory"/> so that it will no longer be used to try to parse native data files.
    1.84 -		/// </summary>
    1.85 -		/// <param name="factory">
    1.86 -		/// The <see cref="INativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse native data files.
    1.87 -		/// </param>
    1.88 -		public void UnregisterFactory(INativeWarFoundryFactory factory)
    1.89 -		{
    1.90 -			if (factories.Contains(factory))
    1.91 -			{
    1.92 -				factories.Remove(factory);
    1.93 -			}
    1.94 -		}
    1.95 -		
    1.96 -		/// <summary>
    1.97 -		/// Registers a <see cref="INonNativeWarFoundryFactory"/> so that it will be used to try to parse non-native data files from other applications.
    1.98 -		/// </summary>
    1.99 -		/// <param name="factory">
   1.100 -		/// The <see cref="INonNativeWarFoundryFactory"/> to register to parse non-native data files.
   1.101 -		/// </param>
   1.102 -		public void RegisterNonNativeFactory(INonNativeWarFoundryFactory factory)
   1.103 -		{
   1.104 -			if (!nonNativeFactories.Contains(factory))
   1.105 -			{
   1.106 -				nonNativeFactories.Add(factory);
   1.107 -			}
   1.108 -		}
   1.109 -		
   1.110 -		/// <summary>
   1.111 -		/// Unregisters a <see cref="INonNativeWarFoundryFactory"/> so that it will no longer be used to try to parse non-native data files from other applications.
   1.112 -		/// </summary>
   1.113 -		/// <param name="factory">
   1.114 -		/// The <see cref="INonNativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse non-native data files.
   1.115 -		/// </param>
   1.116 -		public void UnregisterNonNativeFactory(INonNativeWarFoundryFactory factory)
   1.117 -		{
   1.118 -			if (nonNativeFactories.Contains(factory))
   1.119 -			{
   1.120 -				nonNativeFactories.Remove(factory);
   1.121 -			}
   1.122 -		}
   1.123 -		
   1.124 -		/// <summary>
   1.125 -		/// Loads all of the data files in the registered directories.
   1.126 -		/// </summary>
   1.127 -		/// <returns>
   1.128 -		/// A <see cref="List"/> of <see cref="FileLoadFailure"/> for files that failed to load
   1.129 -		/// </returns>
   1.130 -		public List<FileLoadFailure> LoadFiles()
   1.131 -		{
   1.132 -			PrepareForFileLoad();
   1.133 -			Dictionary<FileInfo, IWarFoundryFactory> loadableRaces = new Dictionary<FileInfo, IWarFoundryFactory>();
   1.134 -			Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems = new Dictionary<FileInfo, IWarFoundryFactory>();
   1.135 -			List<FileLoadFailure> failedLoads = FillLoadableFiles(loadableRaces, loadableGameSystems);
   1.136 -			failedLoads.AddRange(LoadGameSystems(loadableGameSystems));
   1.137 -			failedLoads.AddRange(LoadRaces(loadableRaces));
   1.138 -			OnFileLoadingFinished(failedLoads);
   1.139 -			return failedLoads;
   1.140 -		}
   1.141 -		
   1.142 -		private void OnFileLoadingFinished(List<FileLoadFailure> failures)
   1.143 -		{
   1.144 -			if (FileLoadingFinished!=null)
   1.145 -			{
   1.146 -				FileLoadingFinished(failures);
   1.147 -			}
   1.148 -		}
   1.149 -		
   1.150 -		protected abstract void PrepareForFileLoad();
   1.151 -
   1.152 -		private List<FileLoadFailure> FillLoadableFiles(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems)
   1.153 -		{			
   1.154 -			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.155 -			
   1.156 -			foreach (DirectoryInfo directory in directories)
   1.157 -			{
   1.158 -				directory.Refresh();
   1.159 -
   1.160 -				if (directory.Exists)
   1.161 -				{
   1.162 -					List<FileLoadFailure> directoryFails = FillLoadableFilesForDirectory(loadableRaces, loadableGameSystems, directory);
   1.163 -					fails.AddRange(directoryFails);
   1.164 -				}
   1.165 -				else
   1.166 -				{
   1.167 -					LogNotifier.WarnFormat(GetType(), "Load for {0} failed because directory didn't exist", directory.FullName);
   1.168 -				}
   1.169 -			}
   1.170 -			
   1.171 -			return fails;
   1.172 -		}
   1.173 -
   1.174 -		private List<FileLoadFailure> FillLoadableFilesForDirectory(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems, DirectoryInfo directory)
   1.175 -		{
   1.176 -			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.177 -			LogNotifier.Debug(GetType(), "Load from "+directory.FullName);
   1.178 -		
   1.179 -			foreach (FileInfo file in directory.GetFiles())
   1.180 -			{
   1.181 -				IWarFoundryFactory factory = GetGameSystemLoadingFactoryForFile(file);
   1.182 -				
   1.183 -				if (factory != null)
   1.184 -				{
   1.185 -					loadableGameSystems.Add(file, factory);
   1.186 -				}
   1.187 -				else
   1.188 -				{
   1.189 -					factory = GetRaceLoadingFactoryForFile(file);
   1.190 -	
   1.191 -					if (factory!=null)
   1.192 -					{
   1.193 -						loadableRaces.Add(file, factory);
   1.194 -					}
   1.195 -					else
   1.196 -					{
   1.197 -						FileLoadFailure failure = new FileLoadFailure(file, "File not handled as a Race or Game System definition: {0}", "FileNotHandled");
   1.198 -						fails.Add(failure);
   1.199 -						LogNotifier.Info(GetType(), failure.Message);
   1.200 -					}
   1.201 -				}
   1.202 -			}
   1.203 -
   1.204 -			return fails;
   1.205 -		}
   1.206 -
   1.207 -		private IWarFoundryFactory GetGameSystemLoadingFactoryForFile(FileInfo file)
   1.208 -		{
   1.209 -			IWarFoundryFactory loadingFactory = null;
   1.210 -			
   1.211 -			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.212 -			{
   1.213 -				if (factory.CanHandleFileAsGameSystem(file))
   1.214 -				{
   1.215 -					loadingFactory = factory;
   1.216 -					break;
   1.217 -				}
   1.218 -			}
   1.219 -
   1.220 -			if (loadingFactory == null)
   1.221 -			{
   1.222 -				foreach (INativeWarFoundryFactory factory in factories)
   1.223 -				{
   1.224 -					if (factory.CanHandleFileAsGameSystem(file))
   1.225 -					{
   1.226 -						loadingFactory = factory;
   1.227 -						break;
   1.228 -					}
   1.229 -				}
   1.230 -			}
   1.231 -
   1.232 -			return loadingFactory;
   1.233 -		}
   1.234 -
   1.235 -		private IWarFoundryFactory GetRaceLoadingFactoryForFile(FileInfo file)
   1.236 -		{
   1.237 -			IWarFoundryFactory loadingFactory = null;
   1.238 -			
   1.239 -			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.240 -			{
   1.241 -				if (factory.CanHandleFileAsRace(file))
   1.242 -				{
   1.243 -					loadingFactory = factory;
   1.244 -					break;
   1.245 -				}
   1.246 -			}
   1.247 -
   1.248 -			if (loadingFactory == null)
   1.249 -			{
   1.250 -				foreach (INativeWarFoundryFactory factory in factories)
   1.251 -				{
   1.252 -					if (factory.CanHandleFileAsRace(file))
   1.253 -					{
   1.254 -						loadingFactory = factory;
   1.255 -						break;
   1.256 -					}
   1.257 -				}
   1.258 -			}
   1.259 -
   1.260 -			return loadingFactory;
   1.261 -		}
   1.262 -
   1.263 -		private List<FileLoadFailure> LoadGameSystems(Dictionary<FileInfo, IWarFoundryFactory> gameSystemFiles)
   1.264 -		{
   1.265 -			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.266 -
   1.267 -			
   1.268 -			foreach (FileInfo file in gameSystemFiles.Keys)
   1.269 -			{
   1.270 -				FileLoadFailure failure = null;
   1.271 -				
   1.272 -				try
   1.273 -				{
   1.274 -					bool loaded = LoadObject(file, gameSystemFiles[file]);
   1.275 -	
   1.276 -					if (!loaded)
   1.277 -					{
   1.278 -						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as GameSystem using {1}");
   1.279 -					}
   1.280 -				}
   1.281 -				catch (Exception ex)
   1.282 -				{
   1.283 -					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
   1.284 -				}
   1.285 -						
   1.286 -				if (failure!=null)
   1.287 -				{
   1.288 -					fails.Add(failure);
   1.289 -					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
   1.290 -				}
   1.291 -			}
   1.292 -			
   1.293 -			return fails;
   1.294 -		}
   1.295 -
   1.296 -		private List<FileLoadFailure> LoadRaces(Dictionary<FileInfo, IWarFoundryFactory> raceFiles)
   1.297 -		{
   1.298 -			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.299 -			
   1.300 -			foreach (FileInfo file in raceFiles.Keys)
   1.301 -			{
   1.302 -				FileLoadFailure failure = null;
   1.303 -				
   1.304 -				try
   1.305 -				{
   1.306 -					bool loaded = LoadObject(file, raceFiles[file]);
   1.307 -	
   1.308 -					if (!loaded)
   1.309 -					{
   1.310 -						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}");
   1.311 -					}
   1.312 -				}
   1.313 -				catch (Exception ex)
   1.314 -				{
   1.315 -					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
   1.316 -				}
   1.317 -						
   1.318 -				if (failure!=null)
   1.319 -				{
   1.320 -					fails.Add(failure);
   1.321 -					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
   1.322 -				}
   1.323 -			}
   1.324 -			
   1.325 -			return fails;
   1.326 -		}
   1.327 -
   1.328 -		private bool LoadObject(FileInfo file, IWarFoundryFactory factory)
   1.329 -		{
   1.330 -			bool loaded = false;
   1.331 -			
   1.332 -			LogNotifier.DebugFormat(GetType(), "Loading {0} using {1}", file.FullName, factory.GetType().Name);
   1.333 -			ICollection<IWarFoundryObject> objects = factory.CreateObjectsFromFile(file);
   1.334 -			
   1.335 -			if (objects.Count > 0)
   1.336 -			{
   1.337 -				AddLoadedObjects(objects, factory);
   1.338 -				loaded = true;
   1.339 -			}
   1.340 -
   1.341 -			return loaded;
   1.342 -		}
   1.343 -		
   1.344 -		
   1.345 -		/// <summary>
   1.346 -		/// Loads a single file through the registered WarFoundryFactories, if a factory exists that supports the file format.
   1.347 -		/// </summary>
   1.348 -		/// <param name="file">
   1.349 -		/// A <see cref="FileInfo"/> for the file to attempt to load
   1.350 -		/// </param>
   1.351 -		/// <returns>
   1.352 -		/// An ICollection of IWarFoundryObjects loaded from <code>file</code>
   1.353 -		/// </returns>
   1.354 -		public ICollection<IWarFoundryObject> LoadFile(FileInfo file)
   1.355 -		{
   1.356 -			ICollection<IWarFoundryObject> objs = null;
   1.357 -			IWarFoundryFactory loadFactory = null;
   1.358 -			
   1.359 -			try
   1.360 -			{
   1.361 -				objs = LoadFileWithNonNativeFactories(file, out loadFactory);
   1.362 -				
   1.363 -				if (objs == null)
   1.364 -				{
   1.365 -					objs = LoadFileWithNativeFactories(file, out loadFactory);
   1.366 -				}
   1.367 -			}
   1.368 -			catch (InvalidFileException ex)
   1.369 -			{
   1.370 -				LogNotifier.Error(GetType(), file.FullName+" failed to load", ex);
   1.371 -			}
   1.372 -				
   1.373 -			if (objs!=null)
   1.374 -			{
   1.375 -				AddLoadedObjects(objs, loadFactory);
   1.376 -			}
   1.377 -			else
   1.378 -			{
   1.379 -				objs = new List<IWarFoundryObject>();
   1.380 -			}
   1.381 -
   1.382 -			return objs;
   1.383 -		}
   1.384 -		
   1.385 -		private ICollection<IWarFoundryObject> LoadFileWithNonNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
   1.386 -		{
   1.387 -			ICollection<IWarFoundryObject> objs = null;
   1.388 -			loadFactory = null;
   1.389 -			
   1.390 -			if (nonNativeFactories.Count > 0)
   1.391 -			{
   1.392 -				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file");
   1.393 -				
   1.394 -				foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.395 -				{
   1.396 -					bool canLoad = factory.CanHandleFileFormat(file);
   1.397 -					LogNotifier.Debug(GetType(), "Load using "+factory.GetType().FullName+"? " + (canLoad ? "yes" : "no"));
   1.398 -					
   1.399 -					if (canLoad)
   1.400 -					{
   1.401 -						objs = factory.CreateObjectsFromFile(file);
   1.402 -						
   1.403 -						if (objs!=null)
   1.404 -						{
   1.405 -							loadFactory = factory;
   1.406 -							break;
   1.407 -						}
   1.408 -					}			         
   1.409 -				}
   1.410 -			}
   1.411 -			
   1.412 -			return objs;
   1.413 -		}
   1.414 -		
   1.415 -		private ICollection<IWarFoundryObject> LoadFileWithNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
   1.416 -		{
   1.417 -			ICollection<IWarFoundryObject> objs = null;
   1.418 -			loadFactory = null;
   1.419 -			
   1.420 -			if (factories.Count > 0)
   1.421 -			{
   1.422 -				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as native file");
   1.423 -						
   1.424 -				foreach (INativeWarFoundryFactory factory in factories)
   1.425 -				{
   1.426 -					if (factory.CanHandleFileFormat(file))
   1.427 -					{
   1.428 -						objs = factory.CreateObjectsFromFile(file);
   1.429 -						
   1.430 -						if (objs!=null)
   1.431 -						{
   1.432 -							loadFactory = factory;
   1.433 -							break;
   1.434 -						}
   1.435 -					}
   1.436 -				}
   1.437 -			}
   1.438 -			
   1.439 -			return objs;
   1.440 -		}
   1.441 -		
   1.442 -		private void AddLoadedObjects(ICollection<IWarFoundryObject> loadedObjs, IWarFoundryFactory factory)
   1.443 -		{
   1.444 -			SimpleSet<IWarFoundryObject> objs;
   1.445 -			loadedObjects.TryGetValue(factory, out objs);
   1.446 -			
   1.447 -			if (objs == null)
   1.448 -			{
   1.449 -				objs = new SimpleSet<IWarFoundryObject>();
   1.450 -				loadedObjects.Add(factory, objs);
   1.451 -			}
   1.452 -				
   1.453 -			objs.AddRange(loadedObjs);
   1.454 -			StoreObjects(loadedObjs);
   1.455 -		}
   1.456 -
   1.457 -		private void StoreObjects(ICollection<IWarFoundryObject> loadedObjects)
   1.458 -		{
   1.459 -			foreach (IWarFoundryObject loadedObject in loadedObjects)
   1.460 -			{
   1.461 -				if (loadedObject is GameSystem)
   1.462 -				{
   1.463 -					StoreGameSystem((GameSystem)loadedObject);
   1.464 -				}
   1.465 -				else if (loadedObject is Race)
   1.466 -				{
   1.467 -					StoreRace((Race)loadedObject);
   1.468 -				}
   1.469 -			}
   1.470 -		}
   1.471 -		
   1.472 -		protected void StoreGameSystem(GameSystem system)
   1.473 -		{
   1.474 -			GameSystem existingSystem = GetExistingSystemForSystem(system);
   1.475 -			
   1.476 -			if (existingSystem!=null)
   1.477 -			{				
   1.478 -				if (!system.Equals(existingSystem))
   1.479 -				{
   1.480 -					//TODO: Raise an event to say we got a different duplicate
   1.481 -					//We can't just fail, because failing is for completely unhandled files, not for objects in a file
   1.482 -				}
   1.483 -			}
   1.484 -			else
   1.485 -			{
   1.486 -				DoStoreGameSystem(system);
   1.487 -			}
   1.488 -		}
   1.489 -		
   1.490 -		/// <summary>
   1.491 -		/// Gets a game system that has already been loaded that duplicates the supplied game system's ID, if one exists.
   1.492 -		/// </summary>
   1.493 -		/// <param name="system">
   1.494 -		/// The <see cref="GameSystem"/> to find pre-existing duplicates of
   1.495 -		/// </param>
   1.496 -		/// <returns>
   1.497 -		/// <code>null</code> if no existing duplicate exists, else the duplicate <see cref="GameSystem"/>
   1.498 -		/// </returns>
   1.499 -		protected abstract GameSystem GetExistingSystemForSystem(GameSystem system);
   1.500 -		
   1.501 -		/// <summary>
   1.502 -		/// Stores a GameSystem in the loader's relevant storage structure
   1.503 -		/// </summary>
   1.504 -		/// <param name="system">
   1.505 -		/// The loaded <see cref="GameSystem"/> to store
   1.506 -		/// </param>
   1.507 -		protected abstract void DoStoreGameSystem(GameSystem system);
   1.508 -		
   1.509 -		protected void StoreRace(Race race)
   1.510 -		{
   1.511 -			if (race.GameSystem == null)
   1.512 -			{
   1.513 -				throw new InvalidOperationException("Race cannot have null game system. Game system should be loaded before race.");
   1.514 -			}
   1.515 -			
   1.516 -			DoStoreRace(race);
   1.517 -		}
   1.518 -		
   1.519 -		/// <summary>
   1.520 -		/// Performs the implementation specific storage of a race
   1.521 -		/// </summary>
   1.522 -		/// <param name="race">
   1.523 -		/// The <see cref="Race"/> to store
   1.524 -		/// </param>
   1.525 -		protected abstract void DoStoreRace(Race race);
   1.526 -		
   1.527 -		/// <summary>
   1.528 -		/// Gets all <see cref="GameSystem"/>s that are currently available, determined by those that can be loaded with the current <see cref="IWarFoundryFactory"/>s. 
   1.529 -		/// </summary>
   1.530 -		/// <returns>
   1.531 -		/// An array of <see cref="GameSystem"/>s that are currently available.
   1.532 -		/// </returns>
   1.533 -		public abstract GameSystem[] GetGameSystems();
   1.534 -
   1.535 -		/// <summary>
   1.536 -		/// Gets a single <see cref="GameSystem"/> with a given ID. 
   1.537 -		/// </summary>
   1.538 -		/// <param name="systemID">
   1.539 -		/// The ID of the <see cref="GameSystem"/> to get, as a <see cref="System.String"/>.
   1.540 -		/// </param>
   1.541 -		/// <returns>
   1.542 -		/// The <see cref="GameSystem"/> with the given ID, or <code>null</code> if one doesn't exist.
   1.543 -		/// </returns>
   1.544 -		public abstract GameSystem GetGameSystem(string systemID);
   1.545 -
   1.546 -		/// <summary>
   1.547 -		/// Removes a loaded <see cref="GameSystem"/>. Used when a GameSystem fails to complete loading
   1.548 -		/// </summary>
   1.549 -		/// <param name="system">The GameSystem to remove</param>
   1.550 -		protected internal abstract void RemoveGameSystem(GameSystem system);
   1.551 -
   1.552 -		/// <summary>
   1.553 -		/// Gets an array of the races for the specified <see cref="GameSystem"/>.
   1.554 -		/// </summary>
   1.555 -		/// <param name="system">
   1.556 -		/// The <see cref="GameSystem"/> to get the available races for.
   1.557 -		/// </param>
   1.558 -		/// <returns>
   1.559 -		/// An array of <see cref="Race"/>s for the <see cref="GameSystem"/>
   1.560 -		/// </returns>
   1.561 -		public abstract Race[] GetRaces(GameSystem system);
   1.562 -
   1.563 -		/// <summary>
   1.564 -		/// Gets a single race for a given <see cref="GameSystem"/> by ID of the race.
   1.565 -		/// </summary>
   1.566 -		/// <param name="system">
   1.567 -		/// The <see cref="GameSystem"/> that the race is part of.
   1.568 -		/// </param>
   1.569 -		/// <param name="raceID">
   1.570 -		/// A <see cref="System.String"/> ID for the race to load.
   1.571 -		/// </param>
   1.572 -		/// <returns>
   1.573 -		/// A <see cref="Race"/> with the specified ID from the <see cref="GameSystem"/>, or <code>null</code> if one doesn't exist.
   1.574 -		/// </returns>
   1.575 -		public abstract Race GetRace(GameSystem system, string raceID);
   1.576 -
   1.577 -		/// <summary>
   1.578 -		/// Gets a single race for a given <see cref="GameSystem"/> by the race's ID and sub-race ID.
   1.579 -		/// </summary>
   1.580 -		/// <param name="system">
   1.581 -		/// The <see cref="GameSystem"/> that the race is part of.
   1.582 -		/// </param>
   1.583 -		/// <param name="raceID">
   1.584 -		/// The <see cref="System.String"/> ID for the race to load.
   1.585 -		/// </param>
   1.586 -		/// <param name="raceSubID">
   1.587 -		/// A <see cref="System.String"/>
   1.588 -		/// </param>
   1.589 -		/// <returns>
   1.590 -		/// A <see cref="Race"/>
   1.591 -		/// </returns>
   1.592 -		public abstract Race GetRace(GameSystem system, string raceID, string raceSubID);
   1.593 -
   1.594 -		protected internal abstract void RemoveRace(Race race);
   1.595 -
   1.596 -		/// <summary>
   1.597 -		/// Gets the IDs of all of the game systems currently available.
   1.598 -		/// </summary>
   1.599 -		/// <returns>
   1.600 -		/// An array of <see cref="System.String"/>s representing the IDs of the game systems.
   1.601 -		/// </returns>
   1.602 -		public virtual string[] GetGameSystemIDs()
   1.603 -		{
   1.604 -			GameSystem[] systems = GetGameSystems();
   1.605 -			return GetWarFoundryObjectIDs(systems);
   1.606 -		}
   1.607 -		
   1.608 -		protected string[] GetWarFoundryObjectIDs(WarFoundryObject[] objs)
   1.609 -		{
   1.610 -			int objCount = objs.Length;
   1.611 -			string[] keys = new string[objCount];
   1.612 -
   1.613 -			for (int i = 0; i < objCount; i++)
   1.614 -			{
   1.615 -				keys[i] = objs[i].ID;
   1.616 -			}
   1.617 -
   1.618 -			return keys;
   1.619 -		}
   1.620 -		
   1.621 -		/// <summary>
   1.622 -		/// Gets the IDs of all of the races of a specified game system.
   1.623 -		/// </summary>
   1.624 -		/// <param name="system">
   1.625 -		/// The <see cref="GameSystem"/> to get the available races for.
   1.626 -		/// </param>
   1.627 -		/// <returns>
   1.628 -		/// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system.
   1.629 -		/// </returns>
   1.630 -		public virtual string[] GetSystemRaceIDs(GameSystem system)
   1.631 -		{
   1.632 -			Race[] races = GetRaces(system);
   1.633 -			return GetWarFoundryObjectIDs(races);
   1.634 -		}
   1.635 -		
   1.636 -		public Army LoadArmy(FileInfo file)
   1.637 -		{
   1.638 -			IWarFoundryFactory factory = GetArmyLoadingFactoryForFile(file);			
   1.639 -			Army loadedArmy = null;
   1.640 -			
   1.641 -			if (factory != null)
   1.642 -			{
   1.643 -				ICollection<IWarFoundryObject> objs = factory.CreateObjectsFromFile(file);
   1.644 -								
   1.645 -				if (objs.Count == 1)
   1.646 -				{
   1.647 -					foreach (IWarFoundryObject systemCount in objs)
   1.648 -					{
   1.649 -						if (systemCount is Army)
   1.650 -						{
   1.651 -							loadedArmy = (Army) systemCount;
   1.652 -						}
   1.653 -					}
   1.654 -				}
   1.655 -			}
   1.656 -			
   1.657 -			return loadedArmy;
   1.658 -		}
   1.659 -
   1.660 -		private IWarFoundryFactory GetArmyLoadingFactoryForFile(FileInfo file)
   1.661 -		{
   1.662 -			IWarFoundryFactory loadingFactory = null;
   1.663 -			
   1.664 -			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.665 -			{
   1.666 -				if (factory.CanHandleFileAsArmy(file))
   1.667 -				{
   1.668 -					loadingFactory = factory;
   1.669 -					break;
   1.670 -				}
   1.671 -			}
   1.672 -
   1.673 -			if (loadingFactory == null)
   1.674 -			{
   1.675 -				foreach (INativeWarFoundryFactory factory in factories)
   1.676 -				{
   1.677 -					if (factory.CanHandleFileAsArmy(file))
   1.678 -					{
   1.679 -						loadingFactory = factory;
   1.680 -						break;
   1.681 -					}
   1.682 -				}
   1.683 -			}
   1.684 -
   1.685 -			return loadingFactory;
   1.686 -		}
   1.687 -	}
   1.688 -}
   1.689 +// This file (AbstractWarFoundryLoader.cs) is a part of the IBBoard.WarFoundry.API project and is copyright 2009 IBBoard
   1.690 +// 
   1.691 +// The file and the library/program it is in are licensed and distributed, without warranty, under the GNU Affero GPL license, either version 3 of the License or (at your option) any later version. Please see COPYING for more information and the full license.
   1.692 +
   1.693 +using System;
   1.694 +using System.Collections.Generic;
   1.695 +using System.IO;
   1.696 +using IBBoard.Collections;
   1.697 +using IBBoard.IO;
   1.698 +using IBBoard.Logging;
   1.699 +using IBBoard.WarFoundry.API.Factories;
   1.700 +using IBBoard.WarFoundry.API.Objects;
   1.701 +
   1.702 +namespace IBBoard.WarFoundry.API
   1.703 +{
   1.704 +	/// <summary>
   1.705 +	/// The base abstract implementation of a WarFoundry file loader
   1.706 +	/// </summary>
   1.707 +	public abstract class AbstractWarFoundryLoader
   1.708 +	{
   1.709 +		private ICollection<DirectoryInfo> directories;
   1.710 +		private ICollection<INativeWarFoundryFactory> factories;
   1.711 +		private ICollection<INonNativeWarFoundryFactory> nonNativeFactories;
   1.712 +		private Dictionary<IWarFoundryFactory, SimpleSet<IWarFoundryObject>> loadedObjects;
   1.713 +		public delegate void FileLoadingCompleteDelegate(List<FileLoadFailure> failures);
   1.714 +		public event MethodInvoker FileLoadingStarted;
   1.715 +		public event FileLoadingCompleteDelegate FileLoadingFinished;
   1.716 +		
   1.717 +		protected AbstractWarFoundryLoader()
   1.718 +		{
   1.719 +			directories = new List<DirectoryInfo>();
   1.720 +			factories = new List<INativeWarFoundryFactory>();
   1.721 +			nonNativeFactories = new List<INonNativeWarFoundryFactory>();
   1.722 +			loadedObjects = new Dictionary<IWarFoundryFactory,SimpleSet<IWarFoundryObject>>();
   1.723 +		}
   1.724 +		
   1.725 +		/// <summary>
   1.726 +		/// Adds a directory to the collection of directories that will be searched for WarFoundry data files.
   1.727 +		/// </summary>
   1.728 +		/// <param name="directory">
   1.729 +		/// The <see cref="DirectoryInfo"/> to add to the list for searching for data files
   1.730 +		/// </param>
   1.731 +		public void AddLoadDirectory(DirectoryInfo directory)
   1.732 +		{
   1.733 +			if (!directories.Contains(directory))
   1.734 +			{
   1.735 +				directories.Add(directory);
   1.736 +			}
   1.737 +		}
   1.738 +		
   1.739 +		/// <summary>
   1.740 +		/// Removes a directory from the collection of directories that will be searched for WarFoundry data files.
   1.741 +		/// </summary>
   1.742 +		/// <param name="directory">
   1.743 +		/// A <see cref="DirectoryInfo"/>
   1.744 +		/// </param>
   1.745 +		public void RemoveLoadDirectory(DirectoryInfo directory)
   1.746 +		{
   1.747 +			if (directories.Contains(directory))
   1.748 +			{
   1.749 +				directories.Remove(directory);
   1.750 +			}
   1.751 +		}
   1.752 +		
   1.753 +		/// <summary>
   1.754 +		/// Registers a <see cref="INativeWarFoundryFactory"/> as a factory that can parse native data files.
   1.755 +		/// </summary>
   1.756 +		/// <param name="factory">
   1.757 +		/// The <see cref="INativeWarFoundryFactory"/> to register to parse native data files.
   1.758 +		/// </param>
   1.759 +		public void RegisterFactory(INativeWarFoundryFactory factory)
   1.760 +		{
   1.761 +			if (!factories.Contains(factory))
   1.762 +			{
   1.763 +				factories.Add(factory);
   1.764 +			}
   1.765 +		}
   1.766 +		
   1.767 +		/// <summary>
   1.768 +		/// Unregisters a <see cref="INativeWarFoundryFactory"/> so that it will no longer be used to try to parse native data files.
   1.769 +		/// </summary>
   1.770 +		/// <param name="factory">
   1.771 +		/// The <see cref="INativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse native data files.
   1.772 +		/// </param>
   1.773 +		public void UnregisterFactory(INativeWarFoundryFactory factory)
   1.774 +		{
   1.775 +			if (factories.Contains(factory))
   1.776 +			{
   1.777 +				factories.Remove(factory);
   1.778 +			}
   1.779 +		}
   1.780 +		
   1.781 +		/// <summary>
   1.782 +		/// Registers a <see cref="INonNativeWarFoundryFactory"/> so that it will be used to try to parse non-native data files from other applications.
   1.783 +		/// </summary>
   1.784 +		/// <param name="factory">
   1.785 +		/// The <see cref="INonNativeWarFoundryFactory"/> to register to parse non-native data files.
   1.786 +		/// </param>
   1.787 +		public void RegisterNonNativeFactory(INonNativeWarFoundryFactory factory)
   1.788 +		{
   1.789 +			if (!nonNativeFactories.Contains(factory))
   1.790 +			{
   1.791 +				nonNativeFactories.Add(factory);
   1.792 +			}
   1.793 +		}
   1.794 +		
   1.795 +		/// <summary>
   1.796 +		/// Unregisters a <see cref="INonNativeWarFoundryFactory"/> so that it will no longer be used to try to parse non-native data files from other applications.
   1.797 +		/// </summary>
   1.798 +		/// <param name="factory">
   1.799 +		/// The <see cref="INonNativeWarFoundryFactory"/> to remove from the collection of factories that are used to try to parse non-native data files.
   1.800 +		/// </param>
   1.801 +		public void UnregisterNonNativeFactory(INonNativeWarFoundryFactory factory)
   1.802 +		{
   1.803 +			if (nonNativeFactories.Contains(factory))
   1.804 +			{
   1.805 +				nonNativeFactories.Remove(factory);
   1.806 +			}
   1.807 +		}
   1.808 +		
   1.809 +		/// <summary>
   1.810 +		/// Loads all of the data files in the registered directories.
   1.811 +		/// </summary>
   1.812 +		/// <returns>
   1.813 +		/// A <see cref="List"/> of <see cref="FileLoadFailure"/> for files that failed to load
   1.814 +		/// </returns>
   1.815 +		public List<FileLoadFailure> LoadFiles()
   1.816 +		{
   1.817 +			PrepareForFileLoad();
   1.818 +			Dictionary<FileInfo, IWarFoundryFactory> loadableRaces = new Dictionary<FileInfo, IWarFoundryFactory>();
   1.819 +			Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems = new Dictionary<FileInfo, IWarFoundryFactory>();
   1.820 +			List<FileLoadFailure> failedLoads = FillLoadableFiles(loadableRaces, loadableGameSystems);
   1.821 +			failedLoads.AddRange(LoadGameSystems(loadableGameSystems));
   1.822 +			failedLoads.AddRange(LoadRaces(loadableRaces));
   1.823 +			OnFileLoadingFinished(failedLoads);
   1.824 +			return failedLoads;
   1.825 +		}
   1.826 +		
   1.827 +		private void OnFileLoadingFinished(List<FileLoadFailure> failures)
   1.828 +		{
   1.829 +			if (FileLoadingFinished!=null)
   1.830 +			{
   1.831 +				FileLoadingFinished(failures);
   1.832 +			}
   1.833 +		}
   1.834 +		
   1.835 +		protected abstract void PrepareForFileLoad();
   1.836 +
   1.837 +		private List<FileLoadFailure> FillLoadableFiles(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems)
   1.838 +		{			
   1.839 +			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.840 +			
   1.841 +			foreach (DirectoryInfo directory in directories)
   1.842 +			{
   1.843 +				directory.Refresh();
   1.844 +
   1.845 +				if (directory.Exists)
   1.846 +				{
   1.847 +					List<FileLoadFailure> directoryFails = FillLoadableFilesForDirectory(loadableRaces, loadableGameSystems, directory);
   1.848 +					fails.AddRange(directoryFails);
   1.849 +				}
   1.850 +				else
   1.851 +				{
   1.852 +					LogNotifier.WarnFormat(GetType(), "Load for {0} failed because directory didn't exist", directory.FullName);
   1.853 +				}
   1.854 +			}
   1.855 +			
   1.856 +			return fails;
   1.857 +		}
   1.858 +
   1.859 +		private List<FileLoadFailure> FillLoadableFilesForDirectory(Dictionary<FileInfo, IWarFoundryFactory> loadableRaces, Dictionary<FileInfo, IWarFoundryFactory> loadableGameSystems, DirectoryInfo directory)
   1.860 +		{
   1.861 +			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.862 +			LogNotifier.Debug(GetType(), "Load from "+directory.FullName);
   1.863 +		
   1.864 +			foreach (FileInfo file in directory.GetFiles())
   1.865 +			{
   1.866 +				IWarFoundryFactory factory = GetGameSystemLoadingFactoryForFile(file);
   1.867 +				
   1.868 +				if (factory != null)
   1.869 +				{
   1.870 +					loadableGameSystems.Add(file, factory);
   1.871 +				}
   1.872 +				else
   1.873 +				{
   1.874 +					factory = GetRaceLoadingFactoryForFile(file);
   1.875 +	
   1.876 +					if (factory!=null)
   1.877 +					{
   1.878 +						loadableRaces.Add(file, factory);
   1.879 +					}
   1.880 +					else
   1.881 +					{
   1.882 +						FileLoadFailure failure = new FileLoadFailure(file, "File not handled as a Race or Game System definition: {0}", "FileNotHandled");
   1.883 +						fails.Add(failure);
   1.884 +						LogNotifier.Info(GetType(), failure.Message);
   1.885 +					}
   1.886 +				}
   1.887 +			}
   1.888 +
   1.889 +			return fails;
   1.890 +		}
   1.891 +
   1.892 +		private IWarFoundryFactory GetGameSystemLoadingFactoryForFile(FileInfo file)
   1.893 +		{
   1.894 +			IWarFoundryFactory loadingFactory = null;
   1.895 +			
   1.896 +			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.897 +			{
   1.898 +				if (factory.CanHandleFileAsGameSystem(file))
   1.899 +				{
   1.900 +					loadingFactory = factory;
   1.901 +					break;
   1.902 +				}
   1.903 +			}
   1.904 +
   1.905 +			if (loadingFactory == null)
   1.906 +			{
   1.907 +				foreach (INativeWarFoundryFactory factory in factories)
   1.908 +				{
   1.909 +					if (factory.CanHandleFileAsGameSystem(file))
   1.910 +					{
   1.911 +						loadingFactory = factory;
   1.912 +						break;
   1.913 +					}
   1.914 +				}
   1.915 +			}
   1.916 +
   1.917 +			return loadingFactory;
   1.918 +		}
   1.919 +
   1.920 +		private IWarFoundryFactory GetRaceLoadingFactoryForFile(FileInfo file)
   1.921 +		{
   1.922 +			IWarFoundryFactory loadingFactory = null;
   1.923 +			
   1.924 +			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
   1.925 +			{
   1.926 +				if (factory.CanHandleFileAsRace(file))
   1.927 +				{
   1.928 +					loadingFactory = factory;
   1.929 +					break;
   1.930 +				}
   1.931 +			}
   1.932 +
   1.933 +			if (loadingFactory == null)
   1.934 +			{
   1.935 +				foreach (INativeWarFoundryFactory factory in factories)
   1.936 +				{
   1.937 +					if (factory.CanHandleFileAsRace(file))
   1.938 +					{
   1.939 +						loadingFactory = factory;
   1.940 +						break;
   1.941 +					}
   1.942 +				}
   1.943 +			}
   1.944 +
   1.945 +			return loadingFactory;
   1.946 +		}
   1.947 +
   1.948 +		private List<FileLoadFailure> LoadGameSystems(Dictionary<FileInfo, IWarFoundryFactory> gameSystemFiles)
   1.949 +		{
   1.950 +			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.951 +
   1.952 +			
   1.953 +			foreach (FileInfo file in gameSystemFiles.Keys)
   1.954 +			{
   1.955 +				FileLoadFailure failure = null;
   1.956 +				
   1.957 +				try
   1.958 +				{
   1.959 +					bool loaded = LoadObject(file, gameSystemFiles[file]);
   1.960 +	
   1.961 +					if (!loaded)
   1.962 +					{
   1.963 +						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as GameSystem using {1}");
   1.964 +					}
   1.965 +				}
   1.966 +				catch (Exception ex)
   1.967 +				{
   1.968 +					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
   1.969 +				}
   1.970 +						
   1.971 +				if (failure!=null)
   1.972 +				{
   1.973 +					fails.Add(failure);
   1.974 +					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
   1.975 +				}
   1.976 +			}
   1.977 +			
   1.978 +			return fails;
   1.979 +		}
   1.980 +
   1.981 +		private List<FileLoadFailure> LoadRaces(Dictionary<FileInfo, IWarFoundryFactory> raceFiles)
   1.982 +		{
   1.983 +			List<FileLoadFailure> fails = new List<FileLoadFailure>();
   1.984 +			
   1.985 +			foreach (FileInfo file in raceFiles.Keys)
   1.986 +			{
   1.987 +				FileLoadFailure failure = null;
   1.988 +				
   1.989 +				try
   1.990 +				{
   1.991 +					bool loaded = LoadObject(file, raceFiles[file]);
   1.992 +	
   1.993 +					if (!loaded)
   1.994 +					{
   1.995 +						failure = new FileLoadFailure(file, "FileLoadFailed", "Failed to load {0} as Race using {1}");
   1.996 +					}
   1.997 +				}
   1.998 +				catch (Exception ex)
   1.999 +				{
  1.1000 +					failure = new FileLoadFailure(file, null, ex.Message, null, ex);
  1.1001 +				}
  1.1002 +						
  1.1003 +				if (failure!=null)
  1.1004 +				{
  1.1005 +					fails.Add(failure);
  1.1006 +					LogNotifier.Warn(GetType(), failure.Message, failure.Exception);
  1.1007 +				}
  1.1008 +			}
  1.1009 +			
  1.1010 +			return fails;
  1.1011 +		}
  1.1012 +
  1.1013 +		private bool LoadObject(FileInfo file, IWarFoundryFactory factory)
  1.1014 +		{
  1.1015 +			bool loaded = false;
  1.1016 +			
  1.1017 +			LogNotifier.DebugFormat(GetType(), "Loading {0} using {1}", file.FullName, factory.GetType().Name);
  1.1018 +			ICollection<IWarFoundryObject> objects = factory.CreateObjectsFromFile(file);
  1.1019 +			
  1.1020 +			if (objects.Count > 0)
  1.1021 +			{
  1.1022 +				AddLoadedObjects(objects, factory);
  1.1023 +				loaded = true;
  1.1024 +			}
  1.1025 +
  1.1026 +			return loaded;
  1.1027 +		}
  1.1028 +		
  1.1029 +		
  1.1030 +		/// <summary>
  1.1031 +		/// Loads a single file through the registered WarFoundryFactories, if a factory exists that supports the file format.
  1.1032 +		/// </summary>
  1.1033 +		/// <param name="file">
  1.1034 +		/// A <see cref="FileInfo"/> for the file to attempt to load
  1.1035 +		/// </param>
  1.1036 +		/// <returns>
  1.1037 +		/// An ICollection of IWarFoundryObjects loaded from <code>file</code>
  1.1038 +		/// </returns>
  1.1039 +		public ICollection<IWarFoundryObject> LoadFile(FileInfo file)
  1.1040 +		{
  1.1041 +			ICollection<IWarFoundryObject> objs = null;
  1.1042 +			IWarFoundryFactory loadFactory = null;
  1.1043 +			
  1.1044 +			try
  1.1045 +			{
  1.1046 +				objs = LoadFileWithNonNativeFactories(file, out loadFactory);
  1.1047 +				
  1.1048 +				if (objs == null)
  1.1049 +				{
  1.1050 +					objs = LoadFileWithNativeFactories(file, out loadFactory);
  1.1051 +				}
  1.1052 +			}
  1.1053 +			catch (InvalidFileException ex)
  1.1054 +			{
  1.1055 +				LogNotifier.Error(GetType(), file.FullName+" failed to load", ex);
  1.1056 +			}
  1.1057 +				
  1.1058 +			if (objs!=null)
  1.1059 +			{
  1.1060 +				AddLoadedObjects(objs, loadFactory);
  1.1061 +			}
  1.1062 +			else
  1.1063 +			{
  1.1064 +				objs = new List<IWarFoundryObject>();
  1.1065 +			}
  1.1066 +
  1.1067 +			return objs;
  1.1068 +		}
  1.1069 +		
  1.1070 +		private ICollection<IWarFoundryObject> LoadFileWithNonNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
  1.1071 +		{
  1.1072 +			ICollection<IWarFoundryObject> objs = null;
  1.1073 +			loadFactory = null;
  1.1074 +			
  1.1075 +			if (nonNativeFactories.Count > 0)
  1.1076 +			{
  1.1077 +				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as a non-native file");
  1.1078 +				
  1.1079 +				foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
  1.1080 +				{
  1.1081 +					bool canLoad = factory.CanHandleFileFormat(file);
  1.1082 +					LogNotifier.Debug(GetType(), "Load using "+factory.GetType().FullName+"? " + (canLoad ? "yes" : "no"));
  1.1083 +					
  1.1084 +					if (canLoad)
  1.1085 +					{
  1.1086 +						objs = factory.CreateObjectsFromFile(file);
  1.1087 +						
  1.1088 +						if (objs!=null)
  1.1089 +						{
  1.1090 +							loadFactory = factory;
  1.1091 +							break;
  1.1092 +						}
  1.1093 +					}			         
  1.1094 +				}
  1.1095 +			}
  1.1096 +			
  1.1097 +			return objs;
  1.1098 +		}
  1.1099 +		
  1.1100 +		private ICollection<IWarFoundryObject> LoadFileWithNativeFactories(FileInfo file, out IWarFoundryFactory loadFactory)
  1.1101 +		{
  1.1102 +			ICollection<IWarFoundryObject> objs = null;
  1.1103 +			loadFactory = null;
  1.1104 +			
  1.1105 +			if (factories.Count > 0)
  1.1106 +			{
  1.1107 +				LogNotifier.Debug(GetType(), "Attempting to load "+file.FullName+" as native file");
  1.1108 +						
  1.1109 +				foreach (INativeWarFoundryFactory factory in factories)
  1.1110 +				{
  1.1111 +					if (factory.CanHandleFileFormat(file))
  1.1112 +					{
  1.1113 +						objs = factory.CreateObjectsFromFile(file);
  1.1114 +						
  1.1115 +						if (objs!=null)
  1.1116 +						{
  1.1117 +							loadFactory = factory;
  1.1118 +							break;
  1.1119 +						}
  1.1120 +					}
  1.1121 +				}
  1.1122 +			}
  1.1123 +			
  1.1124 +			return objs;
  1.1125 +		}
  1.1126 +		
  1.1127 +		private void AddLoadedObjects(ICollection<IWarFoundryObject> loadedObjs, IWarFoundryFactory factory)
  1.1128 +		{
  1.1129 +			SimpleSet<IWarFoundryObject> objs;
  1.1130 +			loadedObjects.TryGetValue(factory, out objs);
  1.1131 +			
  1.1132 +			if (objs == null)
  1.1133 +			{
  1.1134 +				objs = new SimpleSet<IWarFoundryObject>();
  1.1135 +				loadedObjects.Add(factory, objs);
  1.1136 +			}
  1.1137 +				
  1.1138 +			objs.AddRange(loadedObjs);
  1.1139 +			StoreObjects(loadedObjs);
  1.1140 +		}
  1.1141 +
  1.1142 +		private void StoreObjects(ICollection<IWarFoundryObject> loadedObjects)
  1.1143 +		{
  1.1144 +			foreach (IWarFoundryObject loadedObject in loadedObjects)
  1.1145 +			{
  1.1146 +				if (loadedObject is GameSystem)
  1.1147 +				{
  1.1148 +					StoreGameSystem((GameSystem)loadedObject);
  1.1149 +				}
  1.1150 +				else if (loadedObject is Race)
  1.1151 +				{
  1.1152 +					StoreRace((Race)loadedObject);
  1.1153 +				}
  1.1154 +			}
  1.1155 +		}
  1.1156 +		
  1.1157 +		protected void StoreGameSystem(GameSystem system)
  1.1158 +		{
  1.1159 +			GameSystem existingSystem = GetExistingSystemForSystem(system);
  1.1160 +			
  1.1161 +			if (existingSystem!=null)
  1.1162 +			{				
  1.1163 +				if (!system.Equals(existingSystem))
  1.1164 +				{
  1.1165 +					//TODO: Raise an event to say we got a different duplicate
  1.1166 +					//We can't just fail, because failing is for completely unhandled files, not for objects in a file
  1.1167 +				}
  1.1168 +			}
  1.1169 +			else
  1.1170 +			{
  1.1171 +				DoStoreGameSystem(system);
  1.1172 +			}
  1.1173 +		}
  1.1174 +		
  1.1175 +		/// <summary>
  1.1176 +		/// Gets a game system that has already been loaded that duplicates the supplied game system's ID, if one exists.
  1.1177 +		/// </summary>
  1.1178 +		/// <param name="system">
  1.1179 +		/// The <see cref="GameSystem"/> to find pre-existing duplicates of
  1.1180 +		/// </param>
  1.1181 +		/// <returns>
  1.1182 +		/// <code>null</code> if no existing duplicate exists, else the duplicate <see cref="GameSystem"/>
  1.1183 +		/// </returns>
  1.1184 +		protected abstract GameSystem GetExistingSystemForSystem(GameSystem system);
  1.1185 +		
  1.1186 +		/// <summary>
  1.1187 +		/// Stores a GameSystem in the loader's relevant storage structure
  1.1188 +		/// </summary>
  1.1189 +		/// <param name="system">
  1.1190 +		/// The loaded <see cref="GameSystem"/> to store
  1.1191 +		/// </param>
  1.1192 +		protected abstract void DoStoreGameSystem(GameSystem system);
  1.1193 +		
  1.1194 +		protected void StoreRace(Race race)
  1.1195 +		{
  1.1196 +			if (race.GameSystem == null)
  1.1197 +			{
  1.1198 +				throw new InvalidOperationException("Race cannot have null game system. Game system should be loaded before race.");
  1.1199 +			}
  1.1200 +			
  1.1201 +			DoStoreRace(race);
  1.1202 +		}
  1.1203 +		
  1.1204 +		/// <summary>
  1.1205 +		/// Performs the implementation specific storage of a race
  1.1206 +		/// </summary>
  1.1207 +		/// <param name="race">
  1.1208 +		/// The <see cref="Race"/> to store
  1.1209 +		/// </param>
  1.1210 +		protected abstract void DoStoreRace(Race race);
  1.1211 +		
  1.1212 +		/// <summary>
  1.1213 +		/// Gets all <see cref="GameSystem"/>s that are currently available, determined by those that can be loaded with the current <see cref="IWarFoundryFactory"/>s. 
  1.1214 +		/// </summary>
  1.1215 +		/// <returns>
  1.1216 +		/// An array of <see cref="GameSystem"/>s that are currently available.
  1.1217 +		/// </returns>
  1.1218 +		public abstract GameSystem[] GetGameSystems();
  1.1219 +
  1.1220 +		/// <summary>
  1.1221 +		/// Gets a single <see cref="GameSystem"/> with a given ID. 
  1.1222 +		/// </summary>
  1.1223 +		/// <param name="systemID">
  1.1224 +		/// The ID of the <see cref="GameSystem"/> to get, as a <see cref="System.String"/>.
  1.1225 +		/// </param>
  1.1226 +		/// <returns>
  1.1227 +		/// The <see cref="GameSystem"/> with the given ID, or <code>null</code> if one doesn't exist.
  1.1228 +		/// </returns>
  1.1229 +		public abstract GameSystem GetGameSystem(string systemID);
  1.1230 +
  1.1231 +		/// <summary>
  1.1232 +		/// Removes a loaded <see cref="GameSystem"/>. Used when a GameSystem fails to complete loading
  1.1233 +		/// </summary>
  1.1234 +		/// <param name="system">The GameSystem to remove</param>
  1.1235 +		protected internal abstract void RemoveGameSystem(GameSystem system);
  1.1236 +
  1.1237 +		/// <summary>
  1.1238 +		/// Gets an array of the races for the specified <see cref="GameSystem"/>.
  1.1239 +		/// </summary>
  1.1240 +		/// <param name="system">
  1.1241 +		/// The <see cref="GameSystem"/> to get the available races for.
  1.1242 +		/// </param>
  1.1243 +		/// <returns>
  1.1244 +		/// An array of <see cref="Race"/>s for the <see cref="GameSystem"/>
  1.1245 +		/// </returns>
  1.1246 +		public abstract Race[] GetRaces(GameSystem system);
  1.1247 +
  1.1248 +		/// <summary>
  1.1249 +		/// Gets a single race for a given <see cref="GameSystem"/> by ID of the race.
  1.1250 +		/// </summary>
  1.1251 +		/// <param name="system">
  1.1252 +		/// The <see cref="GameSystem"/> that the race is part of.
  1.1253 +		/// </param>
  1.1254 +		/// <param name="raceID">
  1.1255 +		/// A <see cref="System.String"/> ID for the race to load.
  1.1256 +		/// </param>
  1.1257 +		/// <returns>
  1.1258 +		/// A <see cref="Race"/> with the specified ID from the <see cref="GameSystem"/>, or <code>null</code> if one doesn't exist.
  1.1259 +		/// </returns>
  1.1260 +		public abstract Race GetRace(GameSystem system, string raceID);
  1.1261 +
  1.1262 +		/// <summary>
  1.1263 +		/// Gets a single race for a given <see cref="GameSystem"/> by the race's ID and sub-race ID.
  1.1264 +		/// </summary>
  1.1265 +		/// <param name="system">
  1.1266 +		/// The <see cref="GameSystem"/> that the race is part of.
  1.1267 +		/// </param>
  1.1268 +		/// <param name="raceID">
  1.1269 +		/// The <see cref="System.String"/> ID for the race to load.
  1.1270 +		/// </param>
  1.1271 +		/// <param name="raceSubID">
  1.1272 +		/// A <see cref="System.String"/>
  1.1273 +		/// </param>
  1.1274 +		/// <returns>
  1.1275 +		/// A <see cref="Race"/>
  1.1276 +		/// </returns>
  1.1277 +		public abstract Race GetRace(GameSystem system, string raceID, string raceSubID);
  1.1278 +
  1.1279 +		protected internal abstract void RemoveRace(Race race);
  1.1280 +
  1.1281 +		/// <summary>
  1.1282 +		/// Gets the IDs of all of the game systems currently available.
  1.1283 +		/// </summary>
  1.1284 +		/// <returns>
  1.1285 +		/// An array of <see cref="System.String"/>s representing the IDs of the game systems.
  1.1286 +		/// </returns>
  1.1287 +		public virtual string[] GetGameSystemIDs()
  1.1288 +		{
  1.1289 +			GameSystem[] systems = GetGameSystems();
  1.1290 +			return GetWarFoundryObjectIDs(systems);
  1.1291 +		}
  1.1292 +		
  1.1293 +		protected string[] GetWarFoundryObjectIDs(WarFoundryObject[] objs)
  1.1294 +		{
  1.1295 +			int objCount = objs.Length;
  1.1296 +			string[] keys = new string[objCount];
  1.1297 +
  1.1298 +			for (int i = 0; i < objCount; i++)
  1.1299 +			{
  1.1300 +				keys[i] = objs[i].ID;
  1.1301 +			}
  1.1302 +
  1.1303 +			return keys;
  1.1304 +		}
  1.1305 +		
  1.1306 +		/// <summary>
  1.1307 +		/// Gets the IDs of all of the races of a specified game system.
  1.1308 +		/// </summary>
  1.1309 +		/// <param name="system">
  1.1310 +		/// The <see cref="GameSystem"/> to get the available races for.
  1.1311 +		/// </param>
  1.1312 +		/// <returns>
  1.1313 +		/// An array of <see cref="System.String"/>s representing the IDs of the races of the specified game system.
  1.1314 +		/// </returns>
  1.1315 +		public virtual string[] GetSystemRaceIDs(GameSystem system)
  1.1316 +		{
  1.1317 +			Race[] races = GetRaces(system);
  1.1318 +			return GetWarFoundryObjectIDs(races);
  1.1319 +		}
  1.1320 +		
  1.1321 +		public Army LoadArmy(FileInfo file)
  1.1322 +		{
  1.1323 +			IWarFoundryFactory factory = GetArmyLoadingFactoryForFile(file);			
  1.1324 +			Army loadedArmy = null;
  1.1325 +			
  1.1326 +			if (factory != null)
  1.1327 +			{
  1.1328 +				ICollection<IWarFoundryObject> objs = factory.CreateObjectsFromFile(file);
  1.1329 +								
  1.1330 +				if (objs.Count == 1)
  1.1331 +				{
  1.1332 +					foreach (IWarFoundryObject systemCount in objs)
  1.1333 +					{
  1.1334 +						if (systemCount is Army)
  1.1335 +						{
  1.1336 +							loadedArmy = (Army) systemCount;
  1.1337 +						}
  1.1338 +					}
  1.1339 +				}
  1.1340 +			}
  1.1341 +			
  1.1342 +			return loadedArmy;
  1.1343 +		}
  1.1344 +
  1.1345 +		private IWarFoundryFactory GetArmyLoadingFactoryForFile(FileInfo file)
  1.1346 +		{
  1.1347 +			IWarFoundryFactory loadingFactory = null;
  1.1348 +			
  1.1349 +			foreach (INonNativeWarFoundryFactory factory in nonNativeFactories)
  1.1350 +			{
  1.1351 +				if (factory.CanHandleFileAsArmy(file))
  1.1352 +				{
  1.1353 +					loadingFactory = factory;
  1.1354 +					break;
  1.1355 +				}
  1.1356 +			}
  1.1357 +
  1.1358 +			if (loadingFactory == null)
  1.1359 +			{
  1.1360 +				foreach (INativeWarFoundryFactory factory in factories)
  1.1361 +				{
  1.1362 +					if (factory.CanHandleFileAsArmy(file))
  1.1363 +					{
  1.1364 +						loadingFactory = factory;
  1.1365 +						break;
  1.1366 +					}
  1.1367 +				}
  1.1368 +			}
  1.1369 +
  1.1370 +			return loadingFactory;
  1.1371 +		}
  1.1372 +	}
  1.1373 +}
     2.1 Binary file api/Factories/Xml/WarFoundryXmlRaceFactory.cs has changed
     3.1 --- a/api/Objects/Race.cs	Tue Sep 07 11:53:22 2010 +0000
     3.2 +++ b/api/Objects/Race.cs	Tue Sep 07 20:02:44 2010 +0000
     3.3 @@ -12,12 +12,12 @@
     3.4  namespace IBBoard.WarFoundry.API.Objects
     3.5  {
     3.6  	public class Race : WarFoundryStagedLoadingObject
     3.7 -	{		
     3.8 -		public static string SYSTEM_DEFAULT_RACE_ID = "GameDefault"; 
     3.9 -		
    3.10 +	{
    3.11 +		public static string SYSTEM_DEFAULT_RACE_ID = "GameDefault";
    3.12 +
    3.13  		private string subID;
    3.14  		private GameSystem system;
    3.15 -        private string defaultArmyName;
    3.16 +		private string defaultArmyName = "";
    3.17  		private Dictionary<Category, Dictionary<string, UnitType>> unitTypesByCat;
    3.18  		private Dictionary<string, UnitType> unitTypes = new Dictionary<string,UnitType>();
    3.19  		private Dictionary<string, EquipmentItem> equipment = new Dictionary<string,EquipmentItem>();
    3.20 @@ -25,13 +25,11 @@
    3.21  		private Dictionary<string, Category> categories = new Dictionary<string,Category>();
    3.22  		private Dictionary<string, UnitMemberType> memberTypes = new Dictionary<string, UnitMemberType>();
    3.23  
    3.24 -        public Race(string raceID, string raceName, string raceArmyName, GameSystem gameSystem, IWarFoundryFactory creatingFactory)
    3.25 -            : this(raceID, "", raceName, raceArmyName, gameSystem, creatingFactory)
    3.26 +		public Race(string raceID, string raceName, GameSystem gameSystem, IWarFoundryFactory creatingFactory) : this(raceID, "", raceName, gameSystem, creatingFactory)
    3.27  		{
    3.28  		}
    3.29  
    3.30 -        public Race(string raceID, string raceSubID, string raceName, string raceArmyName, GameSystem gameSystem, IWarFoundryFactory creatingFactory)
    3.31 -            : base(raceID + (raceSubID != "" ? "_" + raceSubID : ""), raceName, creatingFactory)
    3.32 +		public Race(string raceID, string raceSubID, string raceName, GameSystem gameSystem, IWarFoundryFactory creatingFactory) : base(raceID + (raceSubID != "" ? "_" + raceSubID : ""), raceName, creatingFactory)
    3.33  		{
    3.34  			subID = (raceSubID == null ? "" : raceSubID);
    3.35  			system = gameSystem;
    3.36 @@ -52,31 +50,25 @@
    3.37  				{
    3.38  					throw new ArgumentException("Game system for a race cannot be null");
    3.39  				}
    3.40 -				
    3.41 +
    3.42  				system = value;
    3.43  			}
    3.44  		}
    3.45 -        
    3.46 +
    3.47          public string ArmyDefaultName
    3.48          {
    3.49 -            get {
    3.50 -                
    3.51 -                    //throw new ArgumentException("No default army name");
    3.52 -                    System.Diagnostics.Debug.WriteLine(defaultArmyName + "&No default army name");
    3.53 -
    3.54 -                    return defaultArmyName = "test";
    3.55 -            }
    3.56 +            get { return defaultArmyName; }
    3.57              set
    3.58              {
    3.59                  if (value == null)
    3.60                  {
    3.61 -                    //throw new ArgumentException("No default army name");
    3.62 -                    System.Diagnostics.Debug.WriteLine(defaultArmyName + "&No default army name");
    3.63 +                    throw new ArgumentException("No default army name");
    3.64                  }
    3.65  
    3.66 -                defaultArmyName = "testSet";
    3.67 +                defaultArmyName = value;
    3.68              }
    3.69 -        }		
    3.70 +        }
    3.71 +
    3.72  		public void AddCategory(Category cat)
    3.73  		{
    3.74  			categories[cat.ID] = cat;
    3.75 @@ -89,29 +81,29 @@
    3.76  		/// The ID of the category to get
    3.77  		/// </param>
    3.78  		/// <returns>
    3.79 -		/// The <code>Category</code> with the specified ID, or null if one doesn't exist. 
    3.80 +		/// The <code>Category</code> with the specified ID, or null if one doesn't exist.
    3.81  		/// </returns>
    3.82  		public Category GetCategory(string id)
    3.83  		{
    3.84  			EnsureFullyLoaded();
    3.85  			Category cat = null;
    3.86  			categories.TryGetValue(id, out cat);
    3.87 -			
    3.88 +
    3.89  			if (cat == null)
    3.90  			{
    3.91  				cat = GameSystem.GetCategory(id);
    3.92  			}
    3.93 -						
    3.94 +
    3.95  			return cat;
    3.96  		}
    3.97  
    3.98  		public Category[] Categories
    3.99  		{
   3.100 -			get 
   3.101 -			{ 
   3.102 +			get
   3.103 +			{
   3.104  				EnsureFullyLoaded();
   3.105  				Category[] cats;
   3.106 -				
   3.107 +
   3.108  				if (!HasCategoryOverrides())
   3.109  				{
   3.110  					cats = GameSystem.Categories;
   3.111 @@ -120,7 +112,7 @@
   3.112  				{
   3.113  					cats = DictionaryUtils.ToArray<string, Category>(categories);
   3.114  				}
   3.115 -				
   3.116 +
   3.117  				return cats;
   3.118  			}
   3.119  		}
   3.120 @@ -142,17 +134,17 @@
   3.121  			EnsureFullyLoaded();
   3.122  			return DictionaryUtils.GetValue(equipment, id);
   3.123  		}
   3.124 -		
   3.125 +
   3.126  		public List<EquipmentItem> GetEquipmentList()
   3.127  		{
   3.128  			EnsureFullyLoaded();
   3.129  			List<EquipmentItem> items = new List<EquipmentItem>();
   3.130 -			
   3.131 +
   3.132  			foreach (EquipmentItem item in equipment.Values)
   3.133  			{
   3.134  				items.Add(item);
   3.135  			}
   3.136 -			
   3.137 +
   3.138  			return items;
   3.139  		}
   3.140  
   3.141 @@ -163,13 +155,13 @@
   3.142  		}
   3.143  
   3.144  		public UnitType[] GetUnitTypes(Category cat)
   3.145 -		{		
   3.146 +		{
   3.147  			EnsureFullyLoaded();
   3.148  			BuildUnitTypesByCategoryCache();
   3.149  			Dictionary<string, UnitType> unitTypesDictionary;
   3.150  			unitTypesByCat.TryGetValue(cat, out unitTypesDictionary);
   3.151  			UnitType[] unitTypesArray;
   3.152 -			
   3.153 +
   3.154  			if (unitTypesDictionary == null)
   3.155  			{
   3.156  				unitTypesArray = new UnitType[0];
   3.157 @@ -178,18 +170,18 @@
   3.158  			{
   3.159  				unitTypesArray = DictionaryUtils.ToArray<string, UnitType>(unitTypesDictionary);
   3.160  			}
   3.161 -			
   3.162 +
   3.163  			return unitTypesArray;
   3.164  		}
   3.165  
   3.166  		private void CacheUnitType(UnitType unit)
   3.167  		{
   3.168  			BuildUnitTypesByCategoryCache();
   3.169 -			
   3.170 +
   3.171  			foreach (Category cat in unit.Categories)
   3.172  			{
   3.173  				Dictionary<string, UnitType> catUnitTypes = DictionaryUtils.GetValue(unitTypesByCat, cat);
   3.174 -	
   3.175 +
   3.176  				if (catUnitTypes == null)
   3.177  				{
   3.178  					throw new InvalidFileException(String.Format("Unit type {0} with name {1} is a unit of an undefined category ({2})", unit.ID, unit.Name, cat.ID));
   3.179 @@ -198,7 +190,7 @@
   3.180  				catUnitTypes.Add(unit.ID, unit);
   3.181  			}
   3.182  		}
   3.183 -		
   3.184 +
   3.185  		private void BuildUnitTypesByCategoryCache()
   3.186  		{
   3.187  			if (unitTypesByCat == null)
   3.188 @@ -210,12 +202,12 @@
   3.189  		private void DoBuildUnitTypesByCategoryCache()
   3.190  		{
   3.191  			unitTypesByCat = new Dictionary<Category,Dictionary<string,UnitType>>();
   3.192 -				
   3.193 +
   3.194  			foreach (Category category in Categories)
   3.195 -			{ 
   3.196 +			{
   3.197  				unitTypesByCat.Add(category, new Dictionary<string, UnitType>());
   3.198  			}
   3.199 -			
   3.200 +
   3.201  			foreach (UnitType unit in unitTypes.Values)
   3.202  			{
   3.203  				CacheUnitType(unit);
   3.204 @@ -227,15 +219,15 @@
   3.205  			EnsureFullyLoaded();
   3.206  			return DictionaryUtils.GetValue(unitTypes, id);
   3.207  		}
   3.208 -		
   3.209 +
   3.210  		public List<Ability> GetAbilityList()
   3.211  		{
   3.212  			EnsureFullyLoaded();
   3.213  			List<Ability> items = new List<Ability>();
   3.214 -			items.AddRange(abilities.Values);			
   3.215 +			items.AddRange(abilities.Values);
   3.216  			return items;
   3.217  		}
   3.218 -		
   3.219 +
   3.220  		public void AddAbility(Ability newAbility)
   3.221  		{
   3.222  			//TODO: Throw DuplicateItemException
   3.223 @@ -250,49 +242,49 @@
   3.224  				AddAbility(ability);
   3.225  			}
   3.226  		}
   3.227 -				
   3.228 +
   3.229  		public Ability GetAbility(string id)
   3.230  		{
   3.231  			EnsureFullyLoaded();
   3.232  			return DictionaryUtils.GetValue(abilities, id);
   3.233  		}
   3.234 -		
   3.235 +
   3.236  		protected virtual Dictionary<string, UnitType> RaceUnitTypes
   3.237  		{
   3.238  			get { return RaceRawUnitTypes; }
   3.239  			set	{ RaceRawUnitTypes = value; }
   3.240  		}
   3.241 -		
   3.242 +
   3.243  		protected virtual Dictionary<string, EquipmentItem> RaceEquipment
   3.244  		{
   3.245  			get { return RaceRawEquipment; }
   3.246  			set { RaceRawEquipment = value; }
   3.247  		}
   3.248 -		
   3.249 +
   3.250  		protected virtual Dictionary<string, Ability> RaceAbilities
   3.251  		{
   3.252  			get { return RaceRawAbilities; }
   3.253  			set { RaceRawAbilities = value; }
   3.254  		}
   3.255 -		
   3.256 +
   3.257  		protected Dictionary<string, UnitType> RaceRawUnitTypes
   3.258  		{
   3.259  			get { return unitTypes; }
   3.260  			set	{ unitTypes = value; }
   3.261  		}
   3.262 -		
   3.263 +
   3.264  		protected Dictionary<string, EquipmentItem> RaceRawEquipment
   3.265  		{
   3.266  			get { return equipment; }
   3.267  			set { equipment = value; }
   3.268  		}
   3.269 -		
   3.270 +
   3.271  		protected Dictionary<string, Ability> RaceRawAbilities
   3.272  		{
   3.273  			get { return abilities; }
   3.274  			set { abilities = value; }
   3.275  		}
   3.276 -		
   3.277 +
   3.278  		public void AddUnitMemberType(UnitMemberType memberType)
   3.279  		{
   3.280  			memberTypes[memberType.ID] = memberType;
   3.281 @@ -305,7 +297,7 @@
   3.282  		/// The ID of the unit member type to get
   3.283  		/// </param>
   3.284  		/// <returns>
   3.285 -		/// The <code>UnitMemberType</code> with the specified ID, or null if one doesn't exist. 
   3.286 +		/// The <code>UnitMemberType</code> with the specified ID, or null if one doesn't exist.
   3.287  		/// </returns>
   3.288  		public UnitMemberType GetUnitMemberType(string id)
   3.289  		{
   3.290 @@ -315,8 +307,8 @@
   3.291  
   3.292  		public UnitMemberType[] UnitMemberTypes
   3.293  		{
   3.294 -			get 
   3.295 -			{ 
   3.296 +			get
   3.297 +			{
   3.298  				EnsureFullyLoaded();
   3.299  				return DictionaryUtils.ToArray(memberTypes);
   3.300  			}
     4.1 --- a/schemas/race.xsd	Tue Sep 07 11:53:22 2010 +0000
     4.2 +++ b/schemas/race.xsd	Tue Sep 07 20:02:44 2010 +0000
     4.3 @@ -67,7 +67,7 @@
     4.4  		<xs:attribute name="id" type="xs:string" use="required" />
     4.5  		<xs:attribute name="subid" type="xs:string" default=""/>
     4.6  		<xs:attribute name="name" type="xs:string" use="required"/>
     4.7 -    <xs:attribute name="defaultArmyName" type="xs:string" />
     4.8 +		<xs:attribute name="defaultArmyName" type="xs:string" default="" />
     4.9  		<xs:attribute name="system" type="xs:string" use="required"/>
    4.10  		<xs:anyAttribute processContents="lax"/>
    4.11  	</xs:complexType>