/* * Aphelion * Copyright (c) 2013 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.shared.gameconfig; import aphelion.shared.resource.ResourceDB; import java.lang.ref.WeakReference; import java.util.*; import java.util.logging.Logger; /** * * @author Joris */ public class ConfigSelection { private static final Logger log = Logger.getLogger("aphelion.config"); private final GameConfig config; public final Selector selection = new Selector(); // This map contains a list so that adopted values continue to be updated. // otherwise only the first entry is returned private final HashMap<String, List<WeakReference<WrappedValueAbstract>>> usedValues = new HashMap<>(); ConfigSelection(GameConfig config) { this.config = config; } /** Adopt values from another config selection. * The other config selection will have all its values removed. * Make sure to call resolveAllValues() afterwards. */ public void adoptAllValues(ConfigSelection other) { if (other == null) { return; } if (other.usedValues.entrySet() == null) // wtf java { return; } Iterator <Map.Entry<String,List<WeakReference<WrappedValueAbstract>>>> it = other.usedValues.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, List<WeakReference<WrappedValueAbstract>>> entry = it.next(); List<WeakReference<WrappedValueAbstract>> myList = usedValues.get(entry.getKey()); if (myList == null) { usedValues.put(entry.getKey(), entry.getValue()); } else { myList.addAll(entry.getValue()); } } other.usedValues.clear(); cleanup(); } private List<WeakReference<WrappedValueAbstract>> getListOrNew(String name) { List<WeakReference<WrappedValueAbstract>> list = usedValues.get(name); if (list == null) { list = new LinkedList<>(); usedValues.put(name, list); } return list; } private WrappedValueAbstract getValue(String name, Class klass, WrappedValueAbstract.Factory factory) { List<WeakReference<WrappedValueAbstract>> list = getListOrNew(name); WrappedValueAbstract existingValue = list.isEmpty() ? null : list.get(0).get(); if (existingValue == null) { WrappedValueAbstract value = factory.create(this, name); assert klass.isInstance(value); list.add(new WeakReference<>(value)); resolveValue(value); return value; } else { if (klass.isInstance(existingValue)) { return existingValue; } else { // mixing getInteger, getString etc for the same value... // this is not supported at the moment throw new IllegalStateException(); } } } public GCInteger getInteger(String name) { return (GCInteger) getValue(name, GCInteger.class, GCInteger.factory); } public GCIntegerList getIntegerList(String name) { return (GCIntegerList) getValue(name, GCIntegerList.class, GCIntegerList.factory); } public GCString getString(String name) { return (GCString) getValue(name, GCString.class, GCString.factory); } public GCStringList getStringList(String name) { return (GCStringList) getValue(name, GCStringList.class, GCStringList.factory); } public GCBoolean getBoolean(String name) { return (GCBoolean) getValue(name, GCBoolean.class, GCBoolean.factory); } public GCBooleanList getBooleanList(String name) { return (GCBooleanList) getValue(name, GCBooleanList.class, GCBooleanList.factory); } public GCImage getImage(String name, ResourceDB db) { List<WeakReference<WrappedValueAbstract>> list = getListOrNew(name); WrappedValueAbstract existingValue = list.isEmpty() ? null : list.get(0).get(); if (existingValue == null) { GCImage value = new GCImage(this, name, db); list.add(new WeakReference<WrappedValueAbstract>(value)); resolveValue(value); return value; } else { if (existingValue instanceof GCImage) { assert ((GCImage) existingValue).db == db; return (GCImage) existingValue; } else { // mixing getInteger, getString etc for the same value... // this is not supported at the moment throw new IllegalStateException(); } } } public GCColour getColour(String name) { List<WeakReference<WrappedValueAbstract>> list = getListOrNew(name); WrappedValueAbstract existingValue = list.isEmpty() ? null : list.get(0).get(); if (existingValue == null) { GCColour value = new GCColour(this, name); list.add(new WeakReference<WrappedValueAbstract>(value)); resolveValue(value); return value; } else { if (existingValue instanceof GCColour) { return (GCColour) existingValue; } else { // mixing getInteger, getString etc for the same value... // this is not supported at the moment throw new IllegalStateException(); } } } public <T> GCEnum<T> getEnum(EnumResolver<T> resolver, String name) { return (GCEnum<T>) getValue(name, GCEnum.class, new GCEnum.Factory(resolver)); } public <T> GCEnumList<T> getEnumList(EnumResolver<T> resolver, String name) { return (GCEnumList<T>) getValue(name, GCEnumList.class, new GCEnumList.Factory(resolver)); } /** Update the value for a single wrapped value. * Called by resolveValues() * @param value */ public void resolveValue(WrappedValueAbstract value) { resolveValue(value, 0); } public void resolveValue(WrappedValueAbstract value, int n) { try { // "rules" is ordered by highest specificity first for (Rule rule : config.rules) { if (rule.selector.selectorAppliesToSelection(this.selection)) { Object yamlValue = rule.attributes.get(value.key); if (yamlValue != null) { value.newValue(yamlValue); // might fire an event listener! return; } } } value.newValue(null); } catch (ConcurrentModificationException ex) { // An event listener might have called GameConfig.addRule() // try again. // Note: because we are not dealing with threads in this code, // it is safe to rely ConcurrentModificationException if (n == 100000) { throw new Error("Too many concurrent modifications. Probably caused by an infinite callback loop"); } resolveValue(value, n + 1); } } /** This method is called whenever the values returned from this object need updating. * If you change this.selection, this method must be called afterwards. In other cases * this method is called by GameConfig when needed. */ public void resolveAllValues() { resolveAllValues(0); } private void resolveAllValues(int n) { try { Iterator <List<WeakReference<WrappedValueAbstract>>> it = usedValues.values().iterator(); while (it.hasNext()) { List<WeakReference<WrappedValueAbstract>> list = it.next(); Iterator<WeakReference<WrappedValueAbstract>> listIt = list.iterator(); while (listIt.hasNext()) { WrappedValueAbstract value = listIt.next().get(); if (value == null) { listIt.remove(); continue; } resolveValue(value); } if (list.isEmpty()) { it.remove(); } } } catch (ConcurrentModificationException ex) { // An event listener might have called getValue() // try again. // Note: because we are not dealing with threads in this code, // it is safe to rely ConcurrentModificationException if (n == 100000) { throw new Error("Too many concurrent modifications. Probably caused by an infinite callback loop"); } resolveAllValues(n + 1); } } public void cleanup() { Iterator<List<WeakReference<WrappedValueAbstract>>> it = usedValues.values().iterator(); while (it.hasNext()) { List<WeakReference<WrappedValueAbstract>> list = it.next(); Iterator<WeakReference<WrappedValueAbstract>> listIt = list.iterator(); while (listIt.hasNext()) { WrappedValueAbstract value = listIt.next().get(); if (value == null) { listIt.remove(); continue; } } if (list.isEmpty()) { it.remove(); } } } }