package games.strategy.engine.data.properties; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import games.strategy.debug.ClientLogger; import games.strategy.engine.data.GameData; import games.strategy.engine.data.GameDataComponent; /** * Properties of the current game. <br> * Maps string -> Object <br> * Set through changeFactory.setProperty. */ public class GameProperties extends GameDataComponent { private static final long serialVersionUID = -1448163357090677564L; // keep this in sync with the corresponding property, this name is used by reflection public static final String CONSTANT_PROPERTIES_FIELD_NAME = "constantProperties"; private final Map<String, Object> constantProperties = new HashMap<>(); // keep this in sync with the corresponding property, this name is used by reflection public static final String EDITABLE_PROPERTIES_FIELD_NAME = "editableProperties"; private final Map<String, IEditableProperty> editableProperties = new HashMap<>(); // This list is used to keep track of order properties were // added. private final List<String> ordering = new ArrayList<>(); /** * Creates a new instance of GameProperties. * * @param data * game data */ public GameProperties(final GameData data) { super(data); } /** * Setting a property to null has the effect of unbinding the key. * package access to prevent outsiders from setting properties * * @param key * key of property * @param value * property */ public void set(final String key, final Object value) { // TODO should only accept serializable, not object if (value == null) { constantProperties.remove(key); ordering.remove(key); } else { constantProperties.put(key, value); ordering.add(key); } } /** * @param key * referring key * @return property with key or null if property is not contained in the list * (The object returned should not be modified, as modifications will not appear globally.) */ public Object get(final String key) { if (editableProperties.containsKey(key)) { return editableProperties.get(key).getValue(); } return constantProperties.get(key); } public boolean get(final String key, final boolean defaultValue) { final Object value = get(key); if (value == null) { return defaultValue; } return (Boolean) value; } public int get(final String key, final int defaultValue) { final Object value = get(key); if (value == null) { return defaultValue; } return (Integer) value; } public String get(final String key, final String defaultValue) { final Object value = get(key); if (value == null) { return defaultValue; } return (String) value; } public Object get(final String key, final Object defaultValue) { final Object value = get(key); if (value == null) { return defaultValue; } return value; } public void addEditableProperty(final IEditableProperty property) { // add to the editable properties editableProperties.put(property.getName(), property); ordering.add(property.getName()); } /** * Return list of editable properties in the order they were added. * * @return a list of IEditableProperty */ public List<IEditableProperty> getEditableProperties() { final List<IEditableProperty> properties = new ArrayList<>(); for (final String propertyName : ordering) { if (editableProperties.containsKey(propertyName)) { properties.add(editableProperties.get(propertyName)); } } return properties; } public static void toOutputStream(final OutputStream sink, final List<IEditableProperty> editableProperties) throws IOException { // write internally first in case of error try (ByteArrayOutputStream bos = new ByteArrayOutputStream(5000); ObjectOutputStream outStream = new ObjectOutputStream(bos); GZIPOutputStream zippedOut = new GZIPOutputStream(sink)) { outStream.writeObject(editableProperties); zippedOut.write(bos.toByteArray()); zippedOut.flush(); } } @SuppressWarnings("unchecked") public static List<IEditableProperty> streamToIEditablePropertiesList(final byte[] byteArray) throws IOException, ClassNotFoundException, ClassCastException { List<IEditableProperty> editableProperties = null; try (ByteArrayInputStream byteStream = new ByteArrayInputStream(byteArray); InputStream inputStream = new BufferedInputStream(byteStream); GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream); ObjectInputStream objectStream = new ObjectInputStream(gzipInputStream)) { editableProperties = (List<IEditableProperty>) objectStream.readObject(); } return editableProperties; } public static void applyByteMapToChangeProperties(final byte[] byteArray, final GameProperties gamePropertiesToBeChanged) { List<IEditableProperty> editableProperties = null; try { editableProperties = streamToIEditablePropertiesList(byteArray); } catch (final ClassNotFoundException | ClassCastException | IOException e) { ClientLogger.logError( "An Error occured whilst trying to apply a Byte Map to Property. Bytes: " + Arrays.toString(byteArray), e); } applyListToChangeProperties(editableProperties, gamePropertiesToBeChanged); } private static void applyListToChangeProperties(final List<IEditableProperty> editableProperties, final GameProperties gamePropertiesToBeChanged) { if (editableProperties == null || editableProperties.isEmpty()) { return; } for (final IEditableProperty prop : editableProperties) { if (prop == null || prop.getName() == null) { continue; } final IEditableProperty p = gamePropertiesToBeChanged.editableProperties.get(prop.getName()); if (p != null && prop.getName().equals(p.getName()) && p.validate(prop.getValue())) { p.setValue(prop.getValue()); } } } }