/*******************************************************************************
 * Copyright 2013 Alexander Casall
 *

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *

 * http://www.apache.org/licenses/LICENSE-2.0
 *

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package de.saxsys.mvvmfx.utils.notifications;

import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Default implementation of {@link NotificationCenter}.
 *
 * @author sialcasa
 *
 */
public class DefaultNotificationCenter implements NotificationCenter {

	private static final Logger LOG = LoggerFactory.getLogger(DefaultNotificationCenter.class);

	private final ObserverMap globalObservers = new ObserverMap();
	private final ChannelObserverMap channelObserverMap = new ChannelObserverMap();

	@Override
	public void subscribe(String messageName, NotificationObserver observer) {
		if(observer==null) {
			throw new IllegalArgumentException("The observer must not be null.");
		}
		addObserver(messageName, observer, globalObservers);
	}

	@Override
	public void unsubscribe(String messageName, NotificationObserver observer) {
		removeObserversForMessageName(messageName, observer, globalObservers);
	}

	@Override
	public void unsubscribe(NotificationObserver observer) {
		removeObserverFromObserverMap(observer, globalObservers);
	}

	@Override
	public void publish(String messageName, Object... payload) {
		publish(messageName, payload, globalObservers);
	}

	/**
	 *  This notification will be send to the UI-Thread (if the UI-toolkit was bootstrapped).
	 *  If no UI-Toolkit is available the notification will be directly published. This is typically the case in unit tests.
	 *
	 * @param channel    the channel
	 * @param messageName    the message to sent
	 * @param payload        additional arguments to the message
	 */
	@Override
	public void publish(Object channel, String messageName, Object[] payload) {
		if (channelObserverMap.containsKey(channel)) {
			final ObserverMap observerMap = channelObserverMap.get(channel);

			if (shouldPublishInThisThread()) {
				publish(messageName, payload, observerMap);
			} else {
				try {
					Platform.runLater(() -> publish(messageName, payload, observerMap));
				} catch (IllegalStateException e) {

					// If the toolkit isn't initialized yet we will publish the notification directly.
					// In most cases this means that we are in a unit test and not JavaFX application is running.
					if (e.getMessage().equals("Toolkit not initialized")) {
						publish(messageName, payload, observerMap);
					} else {
						throw e;
					}
				}
			}
		}
	}

	private boolean shouldPublishInThisThread() {
		try {
			return Platform.isFxApplicationThread();
		} catch (final RuntimeException e) {
			if (e.getMessage().equals("No toolkit found")) {
				// If the toolkit is not even available, we publish the notification directly.
				// In most cases this means that we are in an environment where no JavaFX
				// application is running (probably also in a JUnit test).
				return true;
			} else {
				throw e;
			}
		}
	}

	@Override
	public void subscribe(Object channel, String messageName, NotificationObserver observer) {
		if (!channelObserverMap.containsKey(channel)) {
			channelObserverMap.put(channel, new ObserverMap());
		}

		final ObserverMap observerMap = channelObserverMap.get(channel);
		addObserver(messageName, observer, observerMap);
	}

	@Override
	public void unsubscribe(Object channel, String messageName, NotificationObserver observer) {
		if (channelObserverMap.containsKey(channel)) {
			final ObserverMap observerMap = channelObserverMap.get(channel);
			removeObserversForMessageName(messageName, observer, observerMap);
		}
	}

	@Override
	public void unsubscribe(Object channel, NotificationObserver observer) {
		if (channelObserverMap.containsKey(channel)) {
			ObserverMap observerMap = channelObserverMap.get(channel);
			removeObserverFromObserverMap(observer, observerMap);
		}
	}

	@Override
	public void clear() {
		this.globalObservers.clear();
		this.channelObserverMap.clear();
	}

	/*
	 * Helper
	 */

	private static void publish(String messageName, Object[] payload, ObserverMap observerMap) {
		Collection<NotificationObserver> notificationReceivers = observerMap.get(messageName);
		if (notificationReceivers != null) {

			// make a copy to prevent ConcurrentModificationException if inside of an observer a new observer is subscribed.

			for (NotificationObserver observer : notificationReceivers) {
				observer.receivedNotification(messageName, payload);
			}
		}
	}

	private static void addObserver(String messageName, NotificationObserver observer, ObserverMap observerMap) {
		if (!observerMap.containsKey(messageName)) {
			// use CopyOnWriteArrayList to prevent ConcurrentModificationException if inside of an observer a new observer is subscribed.
			observerMap.put(messageName, new CopyOnWriteArrayList<>());
		}

		final List<NotificationObserver> observers = observerMap.get(messageName);

		if (observers.contains(observer)) {
			LOG.warn("Subscribe the observer [" + observer + "] for the message [" + messageName +
					"], but the same observer was already added for this message in the past.");
		}
		observers.add(observer);
	}

	private static void removeObserverFromObserverMap(NotificationObserver observer, ObserverMap observerMap) {
		for (String key : observerMap.keySet()) {
			final List<NotificationObserver> observers = observerMap.get(key);

			removeObserverFromObserverList(observer, observers);
		}
	}

	private static void removeObserverFromObserverList(NotificationObserver observer, List<NotificationObserver> observerList) {
		observerList.removeIf(actualObserver -> actualObserver.equals(observer));

		observerList.removeIf(actualObserver -> {
			if(actualObserver instanceof WeakNotificationObserver) {
				WeakNotificationObserver weakObserver = (WeakNotificationObserver) actualObserver;

				NotificationObserver wrappedObserver = weakObserver.getWrappedObserver();

				if(wrappedObserver == null) { // if reference was GCed we can remove the weakObserver
					return true;
				} else {
					return wrappedObserver.equals(observer);
				}
			}

			return false;
		});
	}

	private static void removeObserversForMessageName(String messageName, NotificationObserver observer,
											   ObserverMap observerMap) {

		if (observerMap.containsKey(messageName)) {
			final List<NotificationObserver> observers = observerMap.get(messageName);
			removeObserverFromObserverList(observer, observers);

			if (observers.size() == 0) {
				observerMap.remove(messageName);
			}
		}
	}

	@SuppressWarnings("serial")
	private class ObserverMap extends HashMap<String, List<NotificationObserver>> {
	}

	@SuppressWarnings("serial")
	private class ChannelObserverMap extends HashMap<Object, ObserverMap> {
	}
}


package de.saxsys.mvvmfx.spring;

import de.saxsys.mvvmfx.MvvmFX;
import de.saxsys.mvvmfx.internal.MvvmfxApplication;
import de.saxsys.mvvmfx.utils.notifications.NotificationCenter;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

/**
 * This class has to be extended by the user to build a javafx application powered by Spring-Boot and mvvmFX.
 *
 * @author manuel.mauky
 */
public abstract class MvvmfxSpringApplication extends Application implements MvvmfxApplication {

	private ConfigurableApplicationContext ctx;

	/**
	 * This method is overridden to initialize the mvvmFX framework.
	 * Override {@link #startMvvmfx(Stage)} for your application entry point and startup code.
	 */
	@Override
	public final void start(Stage primaryStage) throws Exception {
		startMvvmfx(primaryStage);
	}

	@Bean
	public HostServices hostServices() {
		return this.getHostServices();
	}

	@Bean
	public NotificationCenter notificationCenter() {
		return MvvmFX.getNotificationCenter();
	}

	/**
	 * This method is invoked when the javafx application is initialized. See {@link javafx.application.Application#init()} for more details.
	 *

	 * Unlike the original init method in {@link javafx.application.Application} this method contains logic to initialize
	 * the Spring-Boot container. For this reason this method is now final to prevent unintended overriding.
	 * Please use {@link #initMvvmfx()} for you own initialization logic.
	 *
	 * @throws Exception
	 */
	@Override
	public final void init() throws Exception {
		ctx = SpringApplication.run(this.getClass());

		MvvmFX.setCustomDependencyInjector(ctx::getBean);

		ctx.getBeanFactory().autowireBean(this);

		initMvvmfx();
	}

	/**
	 * This method is called when the application should stop. See {@link javafx.application.Application#stop()} for
	 * more details.
	 *

	 * Unlike the original stop method in {@link javafx.application.Application} this method contains logic to release
	 * resources managed by the Spring Boot container.  For this reason this method is now final to prevent unintended overriding.
	 * * Please use {@link #stopMvvmfx()} ()} for you own initialization logic.
	 *
	 * @throws Exception
	 */
	@Override
	public final void stop() throws Exception {
		stopMvvmfx();

		ctx.close();
	}
}


/*******************************************************************************
 * Copyright 2015 Alexander Casall, Manuel Mauky
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package de.saxsys.mvvmfx.utils.mapping;

import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.DoubleGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.DoubleImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.DoublePropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.DoubleSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.FloatGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.FloatImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.FloatPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.FloatSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.IntGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.IntImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.IntPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.IntSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ListGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ListImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ListPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ListSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.LongGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.LongImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.LongPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.LongSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.MapGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.MapImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.MapPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.MapSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ObjectGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ObjectImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ObjectPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.ObjectSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.SetGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.SetImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.SetPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.SetSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringGetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringImmutableSetter;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringPropertyAccessor;
import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringSetter;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import eu.lestard.doc.Beta;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SetProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleFloatProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleSetProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;

/**
 * A helper class that can be used to simplify the mapping between the ViewModel and the Model for use cases where a
 * typical CRUD functionality is needed and there is no big difference between the structure of the model class and the
 * view.
 *

 * A typical workflow would be:
 *

 *
load an existing model instance from the backend and copy all values from the model to the properties of the
 * ViewModel

 *
the user changes the values of the viewModel properties (via the UI). The state of the underlying model instance
 * may not be changed at this point in time!

 *
when the user clicks an Apply button (and validation is successful) copy all values from the ViewModel fields
 * into the model instance.

 *

 *

 * Additional requirements:
 *

 *
click "reset" so that the old values are back in the UI. In this case all UI fields should get the current values
 * of the model

 *
if we are creating a new model instance and the user clicks "reset" we want all UI fields to be reset to a
 * meaningful default value

 *

 *
 *

 *
 * These requirements are quite common but there is a lot of code needed to copy between the model and the viewModel.
 * Additionally we have a tight coupling because every time the structure of the model changes (for example a field is
 * removed) we have several places in the viewModel that need to be adjusted.
 *

 * This component can be used to simplify use cases like the described one and minimize the coupling between the model
 * and the viewModel. See the following code example. First without and afterwards with the {@link ModelWrapper}.
 *

 * The model class:
 *

 *
 *

 * public class Person {
 * 	private String name;
 * 	private String familyName;
 * 	private int age;
 *
 * 	public String getName() {
 * 		return name;
 * 	}
 *
 * 	public void setName(String name) {
 * 		this.name = name;
 * 	}
 *
 * 	public String getFamilyName() {
 * 		return familyName;
 * 	}
 *
 * 	public void setFamilyName(String familyName) {
 * 		this.familyName = familyName;
 * 	}
 *
 * 	public int getAge() {
 * 		return age;
 * 	}
 *
 * 	public void setAge(int age) {
 * 		this.age = age;
 * 	}
 * }
 *

 *
 * Without {@link ModelWrapper}:
 *

 *
 *

 * public class PersonViewModel implements ViewModel {
 *
 * 	private StringProperty name = new SimpleStringProperty();
 * 	private StringProperty familyName = new SimpleStringProperty();
 * 	private IntegerProperty age = new SimpleIntegerProperty();
 *
 * 	private Person person;
 *
 * 	public void init(Person person) {
 * 		this.person = person;
 * 		reloadFromModel();
 * 	}
 *
 * 	public void reset() {
 * 		this.name.setValue("");
 * 		this.familyName.setValue("");
 * 		this.age.setValue(0);
 * 	}
 *
 * 	public void reloadFromModel() {
 * 		this.name.setValue(person.getName());
 * 		this.familyName.setValue(person.getFamilyName());
 * 		this.age.setValue(person.getAge());
 * 	}
 *
 * 	public void save() {
 * 		if (someValidation() && person != null) {
 * 			person.setName(name.getValue());
 * 			person.setFamilyName(familyName.getValue());
 * 			person.setAge(age.getValue());
 * 		}
 * 	}
 *
 * 	public StringProperty nameProperty() {
 * 		return name;
 * 	}
 *
 * 	public StringProperty familyNameProperty() {
 * 		return familyName;
 * 	}
 *
 * 	public IntegerProperty ageProperty() {
 * 		return age;
 * 	}
 * }
 *

 *
 * With {@link ModelWrapper}:
 *

 *
 *

 *         public class PersonViewModel implements ViewModel {
 *              private ModelWrapper{@code<Person>} wrapper = new ModelWrapper{@code<>}();
 *
 *             public void init(Person person) {
 *                  wrapper.set(person);
 *                  wrapper.reload();
 *             }
 *
 *             public void reset() {
 *                 wrapper.reset();
 *             }
 *
 *             public void reloadFromModel(){
 *                 wrapper.reload();
 *             }
 *
 *             public void save() {
 *                 if (someValidation()) {
 *                     wrapper.commit();
 *                 }
 *             }
 *
 *             public StringProperty nameProperty(){
 *                 return wrapper.field("name", Person::getName, Person::setName, "");
 *             }
 *
 *             public StringProperty familyNameProperty(){
 *                 return wrapper.field("familyName", Person::getFamilyName, Person::setFamilyName, "");
 *             }
 *
 *             public IntegerProperty ageProperty() {
 *                 return wrapper.field("age", Person::getAge, Person::setAge, 0);
 *             }
 *         }
 *

 *
 * In the first example without the {@link ModelWrapper} we have several lines of code that are specific for each field
 * of the model. If we would add a new field to the model (for example "email") then we would have to update several
 * pieces of code in the ViewModel.
 *

 * On the other hand in the example with the {@link ModelWrapper} there is only the definition of the Property accessors
 * in the bottom of the class that is specific to the fields of the Model. For each field we have only one place in the
 * ViewModel that would need an update when the structure of the model changes.
 *
 *
 *
 * @param <M>
 *            the type of the model class.
 */
@Beta
public class ModelWrapper<M> {

	private final ReadOnlyBooleanWrapper dirtyFlag = new ReadOnlyBooleanWrapper();
	private final ReadOnlyBooleanWrapper diffFlag = new ReadOnlyBooleanWrapper();

	private final Set<PropertyField<?, M, ?>> fields = new LinkedHashSet<>();
	private final Map<String, PropertyField<?, M, ?>> identifiedFields = new HashMap<>();

	private final Set<ImmutablePropertyField<?, M, ?>> immutableFields = new LinkedHashSet<>();

	private final ObjectProperty<M> model;

	/**
	 * This flag is needed to support immutable fields. Without immutables when {@link #commit()} is invoked,
	 * the fields of the model instance are changed. With immutables however on commit the instance of the model itself is
	 * replaced. By default when the model instance is changed a {@link #reload()} is executed.
	 * This is ok when the user changes the model instance. It is not ok when we replace the model instance because of immutable fields.
	 * For this reason we need to distinguish between a change of the model instance due to a commit with immutable fields#
	 * and when the user changes the model instance. Therefore durring commit this flag will switch to true
	 * to indicate that we are currently executing a commit.
	 */
	private boolean inCommitPhase = false;

	/**
	 * Create a new instance of {@link ModelWrapper} that wraps the instance of the Model class wrapped by the property.
	 * Updates all data when the model instance changes.
	 *
	 * @param model
	 *            the property of the model element that will be wrapped.
	 */
	public ModelWrapper(ObjectProperty<M> model) {
		this.model = model;
		reload();
		this.model.addListener((observable, oldValue, newValue) -> {
			/*
			 * Only reload the values from the new model instance when it was changed by the user and not when it was changed
			 * during the commit phase.
			 */
			if(!inCommitPhase) {
				reload();
			}
		});
	}

	/**
	 * Create a new instance of {@link ModelWrapper} that wraps the given instance of the Model class.
	 *
	 * @param model
	 *            the element of the model that will be wrapped.
	 */
	public ModelWrapper(M model) {
		this(new SimpleObjectProperty<>(model));
	}

	/**
	 * Create a new instance of {@link ModelWrapper} that is empty at the moment. You have to define the model element
	 * that should be wrapped afterwards with the {@link #set(Object)} method.
	 */
	public ModelWrapper() {
		this(new SimpleObjectProperty<>());
	}

	/**
	 * Define the model element that will be wrapped by this {@link ModelWrapper} instance.
	 *
	 * @param model
	 *            the element of the model that will be wrapped.
	 */
	public void set(M model) {
		this.model.set(model);
	}

	/**
	 * @return the wrapped model element if one was defined, otherwise null.
	 */
	public M get() {
		return model.get();
	}

	/**
	 * @return property holding the model instance wrapped by this model wrapper instance.
   */
	public ObjectProperty<M> modelProperty() {
		return model;
	}

	/**
	 * Resets all defined fields to their default values.
	 *

	 * Default values can be defined as last argument of the overloaded "field" methods
	 * (see {@link #field(StringGetter, StringSetter, String)})
	 * or by using the {@link #useCurrentValuesAsDefaults()} method.
	 *
	 *

	 *
	 * If no special default value was defined for a field the default value of the actual Property type will be used
	 * (e.g. 0 for {@link IntegerProperty}, null for {@link StringProperty} and {@link ObjectProperty} ...).
	 *
	 *
	 *

	 * Note: This method has no effects on the wrapped model element but will only change the values of the
	 * defined property fields.
	 */
	public void reset() {
		fields.forEach(PropertyField::resetToDefault);
		immutableFields.forEach(PropertyField::resetToDefault);

		calculateDifferenceFlag();
	}

	/**
	 * Use all values that are currently present in the wrapped model object as new default values for respective fields.
	 * This overrides/updates the values that were set during the initialization of the field mappings.
	 *

	 * Subsequent calls to {@link #reset()} will reset the values to this new default values.
	 *

	 * Usage example:
	 *

	 * ModelWrapper{@code<Person>} wrapper = new ModelWrapper{@code<>}();
	 *
	 * wrapper.field(Person::getName, Person::setName, "oldDefault");
	 *
	 * Person p = new Person();
	 * wrapper.set(p);
	 *
	 *
	 * p.setName("Luise");
	 *
	 * wrapper.useCurrentValuesAsDefaults(); // now "Luise" is the default value for the name field.
	 *
	 *
	 * name.set("Hugo");
	 * wrapper.commit();
	 *
	 * name.get(); // Hugo
	 * p.getName(); // Hugo
	 *
	 *
	 * wrapper.reset(); // reset to the new defaults
	 * name.get(); // Luise
	 *
	 * wrapper.commit(); // put values from properties to the wrapped model object
	 * p.getName(); // Luise
	 *
	 *
	 *

	 *
	 *
	 * If no model instance is set to be wrapped by the ModelWrapper, nothing will happen when this method is invoked.
	 * Instead the old default values will still be available.
	 *
	 */
	public void useCurrentValuesAsDefaults() {
		M wrappedModelInstance = model.get();
		if(wrappedModelInstance != null) {
			for (final PropertyField<?, M, ?> field : fields) {
				field.updateDefault(wrappedModelInstance);
			}

			for (final ImmutablePropertyField<?, M, ?> field : immutableFields) {
				field.updateDefault(wrappedModelInstance);
			}
		}
	}

	/**
	 * Take the current value of each property field and write it into the wrapped model element.
	 *

	 * If no model element is defined then nothing will happen.
	 *

	 * Note: This method has no effects on the values of the defined property fields but will only change the
	 * state of the wrapped model element.
	 */
	public void commit() {
		if (model.get() != null) {

			inCommitPhase = true;

			fields.forEach(field -> field.commit(model.get()));

			if(! immutableFields.isEmpty()) {

				M tmp = model.get();

				for (ImmutablePropertyField<?, M, ?> immutableField : immutableFields) {
					tmp = immutableField.commitImmutable(tmp);
				}

				model.set(tmp);

			}

			inCommitPhase = false;

			dirtyFlag.set(false);

			calculateDifferenceFlag();
		}
	}

	/**
	 * Take the current values from the wrapped model element and put them in the corresponding property fields.
	 *

	 * If no model element is defined then nothing will happen.
	 *

	 * Note: This method has no effects on the wrapped model element but will only change the values of the
	 * defined property fields.
	 */
	public void reload() {
		M wrappedModelInstance = model.get();
		if (wrappedModelInstance != null) {
			fields.forEach(field -> field.reload(wrappedModelInstance));

			immutableFields.forEach(field -> field.reload(wrappedModelInstance));

			dirtyFlag.set(false);
			calculateDifferenceFlag();
		}
	}

	/**
	 * This method can be used to copy all values of this {@link ModelWrapper} instance
	 * to the model instance provided as argument.
	 * Existing values in the provided model instance will be overwritten.
	 *

	 * This method doesn't change the state of this modelWrapper or the wrapped model instance.
	 *
	 * @param model a non-null instance of a model.
	 */
	public void copyValuesTo(M model) {
		Objects.requireNonNull(model);
		fields.forEach(field -> field.commit(model));
	}

	private void propertyWasChanged() {
		dirtyFlag.set(true);
		calculateDifferenceFlag();
	}

	private void calculateDifferenceFlag() {
		M wrappedModelInstance = model.get();

		if (wrappedModelInstance != null) {
			for (final PropertyField<?, M, ?> field : fields) {
				if (field.isDifferent(wrappedModelInstance)) {
					diffFlag.set(true);
					return;
				}
			}

			for (final ImmutablePropertyField<?, M, ?> field : immutableFields) {
				if(field.isDifferent(wrappedModelInstance)) {
					diffFlag.set(true);
					return;
				}
			}

			diffFlag.set(false);
		}
	}

	private <T, R extends Property<T>> R add(PropertyField<T, M, R> field) {
		fields.add(field);
		if (model.get() != null) {
			field.reload(model.get());
		}
		return field.getProperty();
	}

	@SuppressWarnings("unchecked")
	private <T, R extends Property<T>> R addIdentified(String fieldName, PropertyField<T, M, R> field) {
		if (identifiedFields.containsKey(fieldName)) {
			final Property<?> property = identifiedFields.get(fieldName).getProperty();
			return (R) property;
		} else {
			identifiedFields.put(fieldName, field);
			return add(field);
		}
	}

	private <T, R extends Property<T>> R addImmutable(ImmutablePropertyField<T, M, R> field) {
		immutableFields.add(field);

		if(model.get() != null) {
			field.reload(model.get());
		}

		return field.getProperty();
	}

	private <T, R extends Property<T>> R addIdentifiedImmutable(String fieldName, ImmutablePropertyField<T, M, R> field) {
		if (identifiedFields.containsKey(fieldName)) {
			final Property<?> property = identifiedFields.get(fieldName).getProperty();
			return (R) property;
		} else {
			identifiedFields.put(fieldName, field);
			return addImmutable(field);
		}
	}

	/**
	 * This boolean flag indicates whether there is a difference of the data between the wrapped model object and the
	 * properties provided by this wrapper.
	 *

	 * Note the difference to {@link #dirtyProperty()}: This property will be true if the data of the
	 * wrapped model is different to the properties of this wrapper. If you change the data back to the initial state so
	 * that the data is equal again, this property will change back to false while the
	 * {@link #dirtyProperty()} will still be true.
	 *
	 * Simply speaking: This property indicates whether there is a difference in data between the model and the wrapper.
	 * The {@link #dirtyProperty()} indicates whether there was a change done.
	 *
	 *
	 * Note: Only those changes are observed that are done through the wrapped property fields of this wrapper. If you
	 * change the data of the model instance directly, this property won't turn to true.
	 *
	 *
	 * @return a read-only property indicating a difference between model and wrapper.
	 */
	public ReadOnlyBooleanProperty differentProperty() {
		return diffFlag.getReadOnlyProperty();
	}

	/**
	 * See {@link #differentProperty()}.
	 */
	public boolean isDifferent() {
		return diffFlag.get();
	}

	/**
	 * This boolean flag indicates whether there was a change to at least one wrapped property.
	 *

	 * Note the difference to {@link #differentProperty()}: This property will turn to true when the value
	 * of one of the wrapped properties is changed. It will only change back to false when either the
	 * {@link #commit()} or {@link #reload()} method is called. This property will stay true even if
	 * afterwards another change is done so that the data is equal again. In this case the {@link #differentProperty()}
	 * will switch back to false.
	 *

	 *
	 * Simply speaking: This property indicates whether there was a change done to the wrapped properties or not. The
	 * {@link #differentProperty()} indicates whether there is a difference in data at the moment.
	 *
	 * @return a read only boolean property indicating if there was a change done.
	 */
	public ReadOnlyBooleanProperty dirtyProperty() {
		return dirtyFlag.getReadOnlyProperty();
	}

	/**
	 * See {@link #dirtyProperty()}.
	 */
	public boolean isDirty() {
		return dirtyFlag.get();
	}

	/* Field type String */

	/**
	 * Add a new field of type String to this instance of the wrapper. This method is used for model elements that are
	 * following the normal Java-Beans-standard i.e. the model fields are only available via getter and setter methods
	 * and not as JavaFX Properties.
	 *
	 *

	 *
	 * Example:
	 *

	 *
	 *

	 * ModelWrapper{@code<Person>} personWrapper = new ModelWrapper{@code<>}();
	 *
	 * StringProperty wrappedNameProperty = personWrapper.field(person -> person.getName(), (person, value)
	 * 	 -> person.setName(value));
	 *
	 * // or with a method reference
	 * StringProperty wrappedNameProperty = personWrapper.field(Person::getName, Person::setName);
	 *
	 *

	 *
	 *
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param setter
	 *            a function that sets the given value to the given model element. Typically you will use a method
	 *            reference to the setter method of the model element.
	 *
	 * @return The wrapped property instance.
	 */
	public StringProperty field(StringGetter<M> getter, StringSetter<M> setter) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, SimpleStringProperty::new));
	}

	/**
	 * Add a new immutable field of type String to this instance of the wrapper. This method is used for immutable
	 * model elements that have getters to get values for it's fields but not setters.
	 * Instead, immutables have methods that take a new value for a field and return a new cloned instance of the
	 * model element with only this field updated to the new value. The old model instance isn't changed.
	 *
	 *

	 *
	 * Example:
	 *

	 *
	 *

	 * ModelWrapper{@code<Person>} personWrapper = new ModelWrapper{@code<>}();
	 *
	 * StringProperty wrappedNameProperty = personWrapper.field(person -> person.getName(), (person, value)
	 * 	 -> {
	 * 	     Person clone = person.withName(value);
	 * 	     return clone;
	 * 	 });
	 *
	 * // or with a method reference
	 * StringProperty wrappedNameProperty = personWrapper.field(Person::getName, Person::withName);
	 *
	 *

	 *
	 *
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param immutableSetter
	 *            a function that returns a clone of this the given model element that has the given value set. Typically you will use a method
	 *            reference to the immutable setter method of the model element.
	 *
	 * @return The wrapped property instance.
	 */
	public StringProperty immutableField(StringGetter<M> getter, StringImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, SimpleStringProperty::new));
	}

	/**
	 * Add a new field of type String to this instance of the wrapper. See {@link #field(StringGetter, StringSetter)}.
	 * This method additionally has a parameter to define the default value that is used when the {@link #reset()}
	 * method is used.
	 *
	 *
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param setter
	 *            a function that sets the given value to the given model element. Typically you will use a method
	 *            reference to the setter method of the model element.
	 * @param defaultValue
	 *            the default value that is used when {@link #reset()} is invoked.
	 *
	 * @return The wrapped property instance.
	 */
	public StringProperty field(StringGetter<M> getter, StringSetter<M> setter, String defaultValue) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				SimpleStringProperty::new));
	}

	/**
	 * Ad a new immutable field of type String to this instance of the wrapper. See {@link #immutableField(StringGetter, StringImmutableSetter)}.
	 * This method additionally has a parameter to define the default value that is used when the {@link #reset()}
	 * method is used.
	 *
	 *
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param immutableSetter
	 *            a function that returns a clone of this the given model element that has the given value set. Typically you will use a method
	 *            reference to the immutable setter method of the model element.
	 * @param defaultValue
	 *            the default value that is used when {@link #reset()} is invoked.
	 *
	 * @return The wrapped property instance.
	 */
	public StringProperty immutableField(StringGetter<M> getter, StringImmutableSetter<M> immutableSetter, String defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, defaultValue, SimpleStringProperty::new));
	}

	/**
	 * Add a new field of type {@link String} to this instance of the wrapper. This method is used for model elements
	 * that are following the enhanced JavaFX-Beans-standard i.e. the model fields are available as JavaFX Properties.
	 *

	 *
	 * Example:
	 *

	 *
	 *

	 * ModelWrapper{@code<Person>} personWrapper = new ModelWrapper{@code<>}();
	 *
	 * StringProperty wrappedNameProperty = personWrapper.field(person -> person.nameProperty());
	 *
	 * // or with a method reference
	 * StringProperty wrappedNameProperty = personWrapper.field(Person::nameProperty);
	 *
	 *

	 *
	 * @param accessor
	 *            a function that returns the property for a given model instance. Typically you will use a method
	 *            reference to the javafx-property accessor method.
	 *
	 * @return The wrapped property instance.
	 */
	public StringProperty field(StringPropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, SimpleStringProperty::new));
	}

	/**
	 * Add a new field of type String to this instance of the wrapper. See {@link #field(StringGetter, StringSetter)}.
	 * This method additionally has a parameter to define the default value that is used when the {@link #reset()}
	 * method is used.
	 *
	 * @param accessor
	 *            a function that returns the property for a given model instance. Typically you will use a method
	 *            reference to the javafx-property accessor method.
	 * @param defaultValue
	 *            the default value that is used when {@link #reset()} is invoked.
	 * @return The wrapped property instance.
	 */
	public StringProperty field(StringPropertyAccessor<M> accessor, String defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue,
				SimpleStringProperty::new));
	}

	/**
	 * Add a new field of type String to this instance of the wrapper. See {@link #field(StringGetter, StringSetter)}.
	 * This method additionally takes a string identifier as first parameter.
	 *

	 * This identifier is used to return the same property instance even when the method is invoked multiple times.
	 *
	 * @param identifier
	 *            an identifier for the field.
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param setter
	 *            a function that sets the given value to the given model element. Typically you will use a method
	 *            reference to the setter method of the model element.
	 * @return The wrapped property instance.
	 */
	public StringProperty field(String identifier, StringGetter<M> getter, StringSetter<M> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter,
				() -> new SimpleStringProperty(null, identifier)));
	}

	public StringProperty field(String identifier, StringGetter<M> getter, StringSetter<M> setter,
			String defaultValue) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				() -> new SimpleStringProperty(null, identifier)));
	}

	/**
	 * Add a new immutable field of type String to this instance of the wrapper. See {@link #immutableField(StringGetter, StringImmutableSetter}).
	 * This method additionally takes a string identifier as first parameter.
	 *

	 * This identifier is used to return the same property instance even when the method is invoked multiple times.
	 *
	 * @param identifier
	 *            an identifier for the field.
	 * @param getter
	 *            a function that returns the current value of the field for a given model element. Typically you will
	 *            use a method reference to the getter method of the model element.
	 * @param immutableSetter
	 *            a function that returns a clone of this the given model element that has the given value set. Typically you will use a method
	 *            reference to the immutable setter method of the model element.
	 * @return The wrapped property instance.
	 */
	public StringProperty immutableField(String identifier, StringGetter<M> getter, StringImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, () -> new SimpleStringProperty(null, identifier)));
	}

	public StringProperty immutableField(String identifier, StringGetter<M> getter, StringImmutableSetter<M> immutableSetter, String defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, defaultValue, () -> new SimpleStringProperty(null, identifier)));
	}

	/**
	 * Add a new field of type String to this instance of the wrapper. See {@link #field(StringPropertyAccessor)}. This
	 * method additionally takes a string identifier as first parameter.
	 *
	 * This identifier is used to return the same property instance even when the method is invoked multiple times.
	 *
	 * @param identifier
	 *            an identifier for the field.
	 *
	 * @param accessor
	 *            a function that returns the property for a given model instance. Typically you will use a method
	 *            reference to the javafx-property accessor method.
	 * @return The wrapped property instance.
	 */
	public StringProperty field(String identifier, StringPropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleStringProperty(null, identifier)));
	}

	public StringProperty field(String identifier, StringPropertyAccessor<M> accessor, String defaultValue) {
		return addIdentified(identifier,
				new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue,
						() -> new SimpleStringProperty(null, identifier)));
	}

	/* Field type Boolean */

	public BooleanProperty field(BooleanGetter<M> getter, BooleanSetter<M> setter) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, SimpleBooleanProperty::new));
	}

	public BooleanProperty immutableField(BooleanGetter<M> getter, BooleanImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, SimpleBooleanProperty::new));
	}

	public BooleanProperty field(BooleanGetter<M> getter, BooleanSetter<M> setter, boolean defaultValue) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				SimpleBooleanProperty::new));
	}

	public BooleanProperty immutableField(BooleanGetter<M> getter, BooleanImmutableSetter<M> immutableSetter, boolean defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, defaultValue, SimpleBooleanProperty::new));
	}

	public BooleanProperty field(BooleanPropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, SimpleBooleanProperty::new));
	}

	public BooleanProperty field(BooleanPropertyAccessor<M> accessor, boolean defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue, SimpleBooleanProperty::new));
	}

	public BooleanProperty field(String identifier, BooleanGetter<M> getter, BooleanSetter<M> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter,
				() -> new SimpleBooleanProperty(null, identifier)));
	}

	public BooleanProperty field(String identifier, BooleanGetter<M> getter, BooleanSetter<M> setter,
			boolean defaultValue) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				() -> new SimpleBooleanProperty(null, identifier)));
	}

	public BooleanProperty immutableField(String identifier, BooleanGetter<M> getter, BooleanImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, () -> new SimpleBooleanProperty(null, identifier)));
	}

	public BooleanProperty immutableField(String identifier, BooleanGetter<M> getter, BooleanImmutableSetter<M> immutableSetter, boolean defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(this::propertyWasChanged, getter, immutableSetter, defaultValue, () -> new SimpleBooleanProperty(null, identifier)));
	}

	public BooleanProperty field(String identifier, BooleanPropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleBooleanProperty(null, identifier)));
	}

	public BooleanProperty field(String identifier, BooleanPropertyAccessor<M> accessor, boolean defaultValue) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue,
				() -> new SimpleBooleanProperty(null, identifier)));
	}

	/* Field type Double */

	public DoubleProperty field(DoubleGetter<M> getter, DoubleSetter<M> setter) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.doubleValue()),
				SimpleDoubleProperty::new));
	}

	public DoubleProperty immutableField(DoubleGetter<M> getter, DoubleImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.doubleValue()),
				SimpleDoubleProperty::new
		));
	}

	public DoubleProperty field(DoubleGetter<M> getter, DoubleSetter<M> setter, double defaultValue) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.doubleValue()),
				defaultValue,
				SimpleDoubleProperty::new));
	}

	public DoubleProperty immutableField(DoubleGetter<M> getter, DoubleImmutableSetter<M> immutableSetter, double defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.doubleValue()),
				defaultValue,
				SimpleDoubleProperty::new));
	}

	public DoubleProperty field(DoublePropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, SimpleDoubleProperty::new));
	}

	public DoubleProperty field(DoublePropertyAccessor<M> accessor, double defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				SimpleDoubleProperty::new));
	}

	public DoubleProperty field(String identifier, DoubleGetter<M> getter, DoubleSetter<M> setter) {

		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> setter.accept(m, number.doubleValue()),
				() -> new SimpleDoubleProperty(null, identifier)));
	}

	public DoubleProperty field(String identifier, DoubleGetter<M> getter, DoubleSetter<M> setter,
			double defaultValue) {
		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> setter.accept(m, number.doubleValue()),
				defaultValue,
				() -> new SimpleDoubleProperty(null, identifier)));
	}

	public DoubleProperty immutableField(String identifier, DoubleGetter<M> getter, DoubleImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.doubleValue()),
				() -> new SimpleDoubleProperty(null, identifier)));
	}

	public DoubleProperty immutableField(String identifier, DoubleGetter<M> getter, DoubleImmutableSetter<M> immutableSetter, double defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.doubleValue()),
				defaultValue,
				() -> new SimpleDoubleProperty(null, identifier)));
	}

	public DoubleProperty field(String identifier, DoublePropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(
				this::propertyWasChanged,
				accessor::apply,
				() -> new SimpleDoubleProperty(null, identifier)));
	}

	public DoubleProperty field(String identifier, DoublePropertyAccessor<M> accessor, double defaultValue) {
		return addIdentified(identifier,
				new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
						() -> new SimpleDoubleProperty(null, identifier)));
	}

	/* Field type Float */

	public FloatProperty field(FloatGetter<M> getter, FloatSetter<M> setter) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.floatValue()),
				SimpleFloatProperty::new));
	}

	public FloatProperty immutableField(FloatGetter<M> getter, FloatImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.floatValue()),
				SimpleFloatProperty::new
		));
	}

	public FloatProperty field(FloatGetter<M> getter, FloatSetter<M> setter, float defaultValue) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.floatValue()), defaultValue,
				SimpleFloatProperty::new));
	}

	public FloatProperty immutableField(FloatGetter<M> getter, FloatImmutableSetter<M> immutableSetter, float defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.floatValue()),
				defaultValue,
				SimpleFloatProperty::new));
	}

	public FloatProperty field(FloatPropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, SimpleFloatProperty::new));
	}

	public FloatProperty field(FloatPropertyAccessor<M> accessor, float defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				SimpleFloatProperty::new));
	}

	public FloatProperty field(String identifier, FloatGetter<M> getter, FloatSetter<M> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.floatValue()),
				() -> new SimpleFloatProperty(null, identifier)));
	}

	public FloatProperty field(String identifier, FloatGetter<M> getter, FloatSetter<M> setter, float defaultValue) {

		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.floatValue()),
				defaultValue,
				() -> new SimpleFloatProperty(null, identifier)));
	}

	public FloatProperty immutableField(String identifier, FloatGetter<M> getter, FloatImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.floatValue()),
				() -> new SimpleFloatProperty(null, identifier)));
	}

	public FloatProperty immutableField(String identifier, FloatGetter<M> getter, FloatImmutableSetter<M> immutableSetter, float defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.floatValue()),
				defaultValue,
				() -> new SimpleFloatProperty(null, identifier)));
	}

	public FloatProperty field(String identifier, FloatPropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor::apply,
				() -> new SimpleFloatProperty(null, identifier)));
	}

	public FloatProperty field(String identifier, FloatPropertyAccessor<M> accessor, float defaultValue) {
		return addIdentified(identifier,
				new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
						() -> new SimpleFloatProperty(null, identifier)));
	}

	/* Field type Integer */

	public IntegerProperty field(IntGetter<M> getter, IntSetter<M> setter) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.intValue()),
				SimpleIntegerProperty::new));
	}

	public IntegerProperty immutableField(IntGetter<M> getter, IntImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.intValue()),
				SimpleIntegerProperty::new
		));
	}

	public IntegerProperty field(IntGetter<M> getter, IntSetter<M> setter, int defaultValue) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.intValue()),
				defaultValue,
				SimpleIntegerProperty::new));
	}

	public IntegerProperty immutableField(IntGetter<M> getter, IntImmutableSetter<M> immutableSetter, int defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.intValue()),
				defaultValue,
				SimpleIntegerProperty::new));
	}

	public IntegerProperty field(IntPropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, SimpleIntegerProperty::new));
	}

	public IntegerProperty field(IntPropertyAccessor<M> accessor, int defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				SimpleIntegerProperty::new));
	}

	public IntegerProperty field(String identifier, IntGetter<M> getter, IntSetter<M> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.intValue()),
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	public IntegerProperty field(String identifier, IntGetter<M> getter, IntSetter<M> setter, int defaultValue) {
		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.intValue()),
				defaultValue,
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	public IntegerProperty immutableField(String identifier, IntGetter<M> getter, IntImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.intValue()),
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	public IntegerProperty immutableField(String identifier, IntGetter<M> getter, IntImmutableSetter<M> immutableSetter, int defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.intValue()),
				defaultValue,
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	public IntegerProperty field(String identifier, IntPropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor::apply,
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	public IntegerProperty field(String identifier, IntPropertyAccessor<M> accessor, int defaultValue) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				() -> new SimpleIntegerProperty(null, identifier)));
	}

	/* Field type Long */

	public LongProperty field(LongGetter<M> getter, LongSetter<M> setter) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.longValue()),
				SimpleLongProperty::new));
	}

	public LongProperty immutableField(LongGetter<M> getter, LongImmutableSetter<M> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.longValue()),
				SimpleLongProperty::new
		));
	}

	public LongProperty field(LongGetter<M> getter, LongSetter<M> setter, long defaultValue) {
		return add(new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.longValue()),
				defaultValue,
				SimpleLongProperty::new));
	}

	public LongProperty immutableField(LongGetter<M> getter, LongImmutableSetter<M> immutableSetter, long defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.longValue()),
				defaultValue,
				SimpleLongProperty::new));
	}

	public LongProperty field(LongPropertyAccessor<M> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, SimpleLongProperty::new));
	}

	public LongProperty field(LongPropertyAccessor<M> accessor, long defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				SimpleLongProperty::new));
	}

	public LongProperty field(String identifier, LongGetter<M> getter, LongSetter<M> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply, (m, number) -> setter.accept(m, number.longValue()),
				() -> new SimpleLongProperty(null, identifier)));
	}

	public LongProperty field(String identifier, LongGetter<M> getter, LongSetter<M> setter, long defaultValue) {
		return addIdentified(identifier,
				new BeanPropertyField<>(
						this::propertyWasChanged,
						getter::apply, (m, number) -> setter.accept(m, number.longValue()),
						defaultValue,
						() -> new SimpleLongProperty(null, identifier)));
	}

	public LongProperty immutableField(String identifier, LongGetter<M> getter, LongImmutableSetter<M> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.longValue()),
				() -> new SimpleLongProperty(null, identifier)));
	}

	public LongProperty immutableField(String identifier, LongGetter<M> getter, LongImmutableSetter<M> immutableSetter, long defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter::apply,
				(m, number) -> immutableSetter.apply(m, number.longValue()),
				defaultValue,
				() -> new SimpleLongProperty(null, identifier)));
	}

	public LongProperty field(String identifier, LongPropertyAccessor<M> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor::apply,
				() -> new SimpleLongProperty(null, identifier)));
	}

	public LongProperty field(String identifier, LongPropertyAccessor<M> accessor, long defaultValue) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor::apply, defaultValue,
				() -> new SimpleLongProperty(null, identifier)));
	}

	/* Field type generic */

	public <T> ObjectProperty<T> field(ObjectGetter<M, T> getter, ObjectSetter<M, T> setter) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, SimpleObjectProperty::new));
	}

	public <T> ObjectProperty<T> immutableField(ObjectGetter<M, T> getter, ObjectImmutableSetter<M, T> immutableSetter){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter,
				immutableSetter,
				SimpleObjectProperty::new
		));
	}

	public <T> ObjectProperty<T> field(ObjectGetter<M, T> getter, ObjectSetter<M, T> setter, T defaultValue) {
		return add(new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				SimpleObjectProperty::new));
	}

	public <T> ObjectProperty<T> immutableField(ObjectGetter<M,T> getter, ObjectImmutableSetter<M, T> immutableSetter, T defaultValue){
		return addImmutable(new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter,
				immutableSetter,
				defaultValue,
				SimpleObjectProperty::new));
	}

	public <T> ObjectProperty<T> field(ObjectPropertyAccessor<M, T> accessor) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, SimpleObjectProperty::new));
	}

	public <T> ObjectProperty<T> field(ObjectPropertyAccessor<M, T> accessor, T defaultValue) {
		return add(new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue, SimpleObjectProperty::new));
	}

	public <T> ObjectProperty<T> field(String identifier, ObjectGetter<M, T> getter, ObjectSetter<M, T> setter) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter,
				() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	public <T> ObjectProperty<T> field(String identifier, ObjectGetter<M, T> getter, ObjectSetter<M, T> setter,
			T defaultValue) {
		return addIdentified(identifier, new BeanPropertyField<>(this::propertyWasChanged, getter, setter, defaultValue,
				() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	public <T> ObjectProperty<T> immutableField(String identifier, ObjectGetter<M, T> getter, ObjectImmutableSetter<M, T> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter,
				immutableSetter,
				() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	public <T> ObjectProperty<T> immutableField(String identifier, ObjectGetter<M, T> getter, ObjectImmutableSetter<M, T> immutableSetter, T defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableBeanPropertyField<>(
				this::propertyWasChanged,
				getter,
				immutableSetter,
				defaultValue,
				() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	public <T> ObjectProperty<T> field(String identifier, ObjectPropertyAccessor<M, T> accessor) {
		return addIdentified(identifier, new FxPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	public <T> ObjectProperty<T> field(String identifier, ObjectPropertyAccessor<M, T> accessor, T defaultValue) {
		return addIdentified(identifier,
				new FxPropertyField<>(this::propertyWasChanged, accessor, defaultValue,
						() -> new SimpleObjectProperty<T>(null, identifier)));
	}

	/* Field type list */

	public <E> ListProperty<E> field(ListGetter<M, E> getter, ListSetter<M, E> setter) {
		return add(new BeanListPropertyField<>(this::propertyWasChanged, getter,
				(m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), SimpleListProperty::new));
	}

	public <E> ListProperty<E> immutableField(ListGetter<M, E> getter, ListImmutableSetter<M, E> immutableSetter){
		return addImmutable(new ImmutableListPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, list) -> immutableSetter.apply(m, FXCollections.observableArrayList(list)),
				SimpleListProperty::new
		));
	}

	public <E> ListProperty<E> field(ListGetter<M, E> getter, ListSetter<M, E> setter, List<E> defaultValue) {
		return add(new BeanListPropertyField<>(this::propertyWasChanged, getter,
				(m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), SimpleListProperty::new,
				defaultValue));
	}

	public <E> ListProperty<E> immutableField(ListGetter<M, E> getter, ListImmutableSetter<M, E> immutableSetter, List<E> defaultValue){
		return addImmutable(new ImmutableListPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, list) -> immutableSetter.apply(m, FXCollections.observableArrayList(list)),
				SimpleListProperty::new,
				defaultValue));
	}

	public <E> ListProperty<E> field(ListPropertyAccessor<M, E> accessor) {
		return add(new FxListPropertyField<>(this::propertyWasChanged, accessor, SimpleListProperty::new));
	}

	public <E> ListProperty<E> field(ListPropertyAccessor<M, E> accessor, List<E> defaultValue) {
		return add(
				new FxListPropertyField<>(this::propertyWasChanged, accessor, SimpleListProperty::new, defaultValue));
	}

	public <E> ListProperty<E> field(String identifier, ListGetter<M, E> getter, ListSetter<M, E> setter) {
		return addIdentified(identifier, new BeanListPropertyField<>(this::propertyWasChanged, getter,
				(m, list) -> setter.accept(m, FXCollections.observableArrayList(list)),
				() -> new SimpleListProperty<>(null, identifier)));
	}

	public <E> ListProperty<E> field(String identifier, ListGetter<M, E> getter, ListSetter<M, E> setter,
			List<E> defaultValue) {
		return addIdentified(identifier, new BeanListPropertyField<>(this::propertyWasChanged, getter,
				(m, list) -> setter.accept(m, FXCollections.observableArrayList(list)),
				() -> new SimpleListProperty<>(null, identifier), defaultValue));
	}

	public <E> ListProperty<E> immutableField(String identifier, ListGetter<M, E> getter, ListImmutableSetter<M, E> immutableSetter){
		return addIdentifiedImmutable(identifier, new ImmutableListPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, list) -> immutableSetter.apply(m, FXCollections.observableArrayList(list)),
				() -> new SimpleListProperty<>(null, identifier)));
	}

	public <E> ListProperty<E> immutableField(String identifier, ListGetter<M, E> getter, ListImmutableSetter<M, E> immutableSetter, List<E> defaultValue){
		return addIdentifiedImmutable(identifier, new ImmutableListPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, list) -> immutableSetter.apply(m, FXCollections.observableArrayList(list)),
				() -> new SimpleListProperty<>(null, identifier),
				defaultValue));
	}

	public <E> ListProperty<E> field(String identifier, ListPropertyAccessor<M, E> accessor) {
		return addIdentified(identifier, new FxListPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleListProperty<>(null, identifier)));
	}

	public <E> ListProperty<E> field(String identifier, ListPropertyAccessor<M, E> accessor, List<E> defaultValue) {
		return addIdentified(identifier, new FxListPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleListProperty<>(null, identifier), defaultValue));
	}

	/* Field type set */

	/**
	 * This helper method is needed because there is no equivalent of {@link FXCollections#observableArrayList(Collection)}
	 * for {@link Set}.
	 * The only factory method available for sets is {@link FXCollections#observableSet(Set)} which creates an observable set
	 * that is backed by the given set. However, this would mean that changes to the observable set are directly synchronized back to the
	 * source set. In contrast to this {@link FXCollections#observableArrayList(Collection)} creates an observable list that has
	 * initially all values of the source list but changes aren't synchronized back because internally a new ArrayList is created.
	 * We need exactly this behaviour for Sets and therefore are using this helper method.
	 */
	private static <T> ObservableSet<T> observableHashSet(Set<T> source) {
		return FXCollections.observableSet(new HashSet<>(source));
	}

	public <E> SetProperty<E> field(SetGetter<M, E> getter, SetSetter<M, E> setter) {
		return add(new BeanSetPropertyField<>(this::propertyWasChanged, getter,
				(m, set) -> setter.accept(m, observableHashSet(set)), SimpleSetProperty::new));
	}

	public <E> SetProperty<E> immutableField(SetGetter<M, E> getter, SetImmutableSetter<M, E> immutableSetter) {
		return addImmutable(new ImmutableSetPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, set) -> immutableSetter.apply(m, observableHashSet(set)),
				SimpleSetProperty::new
		));
	}

	public <E> SetProperty<E> field(SetGetter<M, E> getter, SetSetter<M, E> setter, Set<E> defaultValue) {
		return add(new BeanSetPropertyField<>(this::propertyWasChanged, getter,
				(m, set) -> setter.accept(m, observableHashSet(set)), SimpleSetProperty::new,
				defaultValue));
	}

	public <E> SetProperty<E> immutableField(SetGetter<M, E> getter, SetImmutableSetter<M, E> immutableSetter, Set<E> defaultValue) {
		return addImmutable(new ImmutableSetPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, set) -> immutableSetter.apply(m, observableHashSet(set)),
				SimpleSetProperty::new,
				defaultValue));
	}

	public <E> SetProperty<E> field(SetPropertyAccessor<M, E> accessor) {
		return add(new FxSetPropertyField<>(this::propertyWasChanged, accessor, SimpleSetProperty::new));
	}

	public <E> SetProperty<E> field(SetPropertyAccessor<M, E> accessor, Set<E> defaultValue) {
		return add(
				new FxSetPropertyField<>(this::propertyWasChanged, accessor, SimpleSetProperty::new, defaultValue));
	}

	public <E> SetProperty<E> field(String identifier, SetGetter<M, E> getter, SetSetter<M, E> setter) {
		return addIdentified(identifier, new BeanSetPropertyField<>(this::propertyWasChanged, getter,
				(m, set) -> setter.accept(m, observableHashSet(set)),
				() -> new SimpleSetProperty<>(null, identifier)));
	}

	public <E> SetProperty<E> field(String identifier, SetGetter<M, E> getter, SetSetter<M, E> setter,
			Set<E> defaultValue) {
		return addIdentified(identifier, new BeanSetPropertyField<>(this::propertyWasChanged, getter,
				(m, set) -> setter.accept(m, observableHashSet(set)),
				() -> new SimpleSetProperty<>(null, identifier), defaultValue));
	}

	public <E> SetProperty<E> immutableField(String identifier, SetGetter<M, E> getter, SetImmutableSetter<M, E> immutableSetter) {
		return addIdentifiedImmutable(identifier, new ImmutableSetPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, set) -> immutableSetter.apply(m, observableHashSet(set)),
				() -> new SimpleSetProperty<>(null, identifier)));
	}

	public <E> SetProperty<E> immutableField(String identifier, SetGetter<M, E> getter, SetImmutableSetter<M, E> immutableSetter,
			Set<E> defaultValue) {
		return addIdentifiedImmutable(identifier, new ImmutableSetPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, set) -> immutableSetter.apply(m, observableHashSet(set)),
				() -> new SimpleSetProperty<>(null, identifier),
				defaultValue));
	}

	public <E> SetProperty<E> field(String identifier, SetPropertyAccessor<M, E> accessor) {
		return addIdentified(identifier, new FxSetPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleSetProperty<>(null, identifier)));
	}

	public <E> SetProperty<E> field(String identifier, SetPropertyAccessor<M, E> accessor, Set<E> defaultValue) {
		return addIdentified(identifier, new FxSetPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleSetProperty<>(null, identifier), defaultValue));
	}

	/* Field type map */

	public <K, V> MapProperty<K, V> field(MapGetter<M, K, V> getter, MapSetter<M, K, V> setter) {
		return add(new BeanMapPropertyField<>(this::propertyWasChanged, getter,
				(m, map) -> setter.accept(m, FXCollections.observableMap(map)), SimpleMapProperty::new));
	}

	public <K, V> MapProperty<K, V> immutableField(MapGetter<M, K, V> getter,
			MapImmutableSetter<M, K, V> immutableSetter) {
		return addImmutable(new ImmutableMapPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, map) -> immutableSetter.apply(m, FXCollections.observableMap(map)),
				SimpleMapProperty::new
		));
	}

	public <K, V> MapProperty<K, V> field(MapGetter<M, K, V> getter, MapSetter<M, K, V> setter,
			Map<K, V> defaultValue) {
		return add(new BeanMapPropertyField<>(this::propertyWasChanged, getter,
				(m, map) -> setter.accept(m, FXCollections.observableMap(map)), SimpleMapProperty::new,
				defaultValue));
	}

	public <K, V> MapProperty<K, V> immutableField(MapGetter<M, K, V> getter,
			MapImmutableSetter<M, K, V> immutableSetter, Map<K, V> defaultValue) {
		return addImmutable(new ImmutableMapPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, map) -> immutableSetter.apply(m, FXCollections.observableMap(map)),
				SimpleMapProperty::new,
				defaultValue));
	}

	public <K, V> MapProperty<K, V> field(MapPropertyAccessor<M, K, V> accessor) {
		return add(new FxMapPropertyField<>(this::propertyWasChanged, accessor, SimpleMapProperty::new));
	}

	public <K, V> MapProperty<K, V> field(MapPropertyAccessor<M, K, V> accessor, Map<K, V> defaultValue) {
		return add(
				new FxMapPropertyField<>(this::propertyWasChanged, accessor, SimpleMapProperty::new, defaultValue));
	}

	public <K, V> MapProperty<K, V> field(String identifier, MapGetter<M, K, V> getter, MapSetter<M, K, V> setter) {
		return addIdentified(identifier, new BeanMapPropertyField<>(this::propertyWasChanged, getter,
				(m, map) -> setter.accept(m, FXCollections.observableMap(map)),
				() -> new SimpleMapProperty<>(null, identifier)));
	}

	public <K, V> MapProperty<K, V> field(String identifier, MapGetter<M, K, V> getter, MapSetter<M, K, V> setter,
			Map<K, V> defaultValue) {
		return addIdentified(identifier, new BeanMapPropertyField<>(this::propertyWasChanged, getter,
				(m, map) -> setter.accept(m, FXCollections.observableMap(map)),
				() -> new SimpleMapProperty<>(null, identifier), defaultValue));
	}

	public <K, V> MapProperty<K, V> immutableField(String identifier, MapGetter<M, K, V> getter,
			MapImmutableSetter<M, K, V> immutableSetter) {
		return addIdentifiedImmutable(identifier, new ImmutableMapPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, map) -> immutableSetter.apply(m, FXCollections.observableMap(map)),
				() -> new SimpleMapProperty<>(null, identifier)));
	}

	public <K, V> MapProperty<K, V> immutableField(String identifier, MapGetter<M, K, V> getter,
			MapImmutableSetter<M, K, V> immutableSetter,
			Map<K, V> defaultValue) {
		return addIdentifiedImmutable(identifier, new ImmutableMapPropertyField<>(
				this::propertyWasChanged,
				getter,
				(m, map) -> immutableSetter.apply(m, FXCollections.observableMap(map)),
				() -> new SimpleMapProperty<>(null, identifier),
				defaultValue));
	}

	public <K, V> MapProperty<K, V> field(String identifier, MapPropertyAccessor<M, K, V> accessor) {
		return addIdentified(identifier, new FxMapPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleMapProperty<>(null, identifier)));
	}

	public <K, V> MapProperty<K, V> field(String identifier, MapPropertyAccessor<M, K, V> accessor,
			Map<K, V> defaultValue) {
		return addIdentified(identifier, new FxMapPropertyField<>(this::propertyWasChanged, accessor,
				() -> new SimpleMapProperty<>(null, identifier), defaultValue));
	}
}


/*******************************************************************************
 * Copyright 2015 Alexander Casall, Manuel Mauky
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package de.saxsys.mvvmfx.utils.validation;

import javafx.beans.value.ObservableValue;

import java.util.ArrayList;
import java.util.List;

/**
 * This {@link Validator} implementation uses observable values as rules. Compared to the {@link FunctionBasedValidator}
 * this allows more complex validation logic.
 *

 * There are two variants of rules possible:
 *

 *
"boolean rules" of type ObservableValue<Boolean>

 *
"complex rules" of type ObservableValue<ValidationMessage>

 *

 *
 *
Boolean Rules of type ObservableValue<Boolean>

 *
 * The first variant uses an {@link ObservableValue<Boolean>} together with a static message. If this observable
 * has a value of false the validation status will be "invalid" and the given message will be present in the {@link ValidationStatus}
 * of this validator.
 * If the observable has a value of true it is considered to be valid.
 *
 *
 *
Complex Rules of type ObservableValue<ValidationMessage>

 *
 * The second variant allows more complex rules. It uses a {@link ObservableValue<ValidationMessage>} as rule.
 * If this observable has a value other then null it is considered to be invalid. The {@link ValidationMessage}
 * value will be present in the {@link ValidationStatus} of this validator.
 *
 *

 *
 * You can add multiple rules via the {@link #addRule(ObservableValue, ValidationMessage)} and {@link #addRule(ObservableValue)} method.
 * If multiple rules are violated, each message will be present.
 */
public class ObservableRuleBasedValidator implements Validator {

    /*
    These lists are used to store the rules that this validator is working with.
    The reason for this is to prevent problems with garbage collection.
    If the validator wouldn't keep references to all given rules it would be possible that they are
    removed by the garbage collector which would result in wrong validation results.
    To prevent this we store references to all given rules here.
    Don't get confused by the fact that no values are only added to these lists but not obtained.
     */
	private List<ObservableValue<Boolean>> booleanRules = new ArrayList<>();
    private List<ObservableValue<ValidationMessage>> complexRules = new ArrayList<>();

	private ValidationStatus validationStatus = new ValidationStatus();

    /**
     * Creates an instance of the Validator without any rules predefined.
     */
    public ObservableRuleBasedValidator() {}

    /**
     * Creates an instance of the Validator with the given rule predefined.
     * It's a shortcut for creating an empty validator and
     * adding a single boolean rule with {@link #addRule(ObservableValue, ValidationMessage)}.
     *
     * @param rule
     * @param message
     */
    public ObservableRuleBasedValidator(ObservableValue<Boolean> rule, ValidationMessage message) {
        addRule(rule, message);
    }

    /**
     * Creates an instance of the Validator with the given complex rules predefined.
     * It's a shortcut for creating an empty validator and
     * adding one or multiple complex rules with {@link #addRule(ObservableValue)}.
     * @param rules
     */
    public ObservableRuleBasedValidator(ObservableValue<ValidationMessage> ... rules) {
        for (ObservableValue<ValidationMessage> rule : rules) {
            addRule(rule);
        }
    }

	/**
	 * Add a rule for this validator.
	 *

	 * The rule defines a condition that has to be fulfilled.
	 *

	 * A rule is defined by an observable boolean value. If the rule has a value of true the rule is
	 * "fulfilled". If the rule has a value of false the rule is violated. In this case the given message
	 * object will be added to the status of this validator.
	 *

	 * There are some predefined rules for common use cases in the {@link ObservableRules} class that can be used.
	 *
	 * @param rule
	 * @param message
	 */
	public void addRule(ObservableValue<Boolean> rule, ValidationMessage message) {
	    booleanRules.add(rule);

		rule.addListener((observable, oldValue, newValue) -> {
			validateBooleanRule(newValue, message);
		});

		validateBooleanRule(rule.getValue(), message);
	}

	private void validateBooleanRule(boolean isValid, ValidationMessage message) {
		if (isValid) {
			validationStatus.removeMessage(message);
		} else {
			validationStatus.addMessage(message);
		}
	}

	@Override
	public ValidationStatus getValidationStatus() {
		return validationStatus;
	}

    /**
     * Add a complex rule for this validator.
     *

     * The rule is defined by an {@link ObservableValue}. If this observable contains a {@link ValidationMessage}
     * object the rule is considered to be violated and the {@link ValidationStatus} of this validator will contain
     * the validation message object contained in the observable value.
     * If the observable doesn't contain a value (in other words it contains null) the rule is considered to
     * be fulfilled and the validation status of this validator will be valid (given that no other rule is violated).
     *

     *
     *  This method allows some more complex rules compared to {@link #addRule(ObservableValue, ValidationMessage)}.
     *  This way you can define rules that have different messages for specific cases.
     *
     * @param rule
     */
    public void addRule(ObservableValue<ValidationMessage> rule) {
        complexRules.add(rule);

        rule.addListener((observable, oldValue, newValue) -> {
            if(oldValue != null) {
               validationStatus.removeMessage(oldValue);
            }
            if(newValue != null) {
                validationStatus.addMessage(newValue);
            }
        });

        if(rule.getValue() != null) {
            validationStatus.addMessage(rule.getValue());
        }
    }
}


package de.saxsys.mvvmfx.examples.contacts.ui.addressform;

import java.util.Optional;
import java.util.ResourceBundle;

import javax.inject.Inject;

import de.saxsys.mvvmfx.InjectScope;
import de.saxsys.mvvmfx.ViewModel;
import de.saxsys.mvvmfx.examples.contacts.model.Address;
import de.saxsys.mvvmfx.examples.contacts.model.Contact;
import de.saxsys.mvvmfx.examples.contacts.model.Country;
import de.saxsys.mvvmfx.examples.contacts.model.countries.CountrySelector;
import de.saxsys.mvvmfx.examples.contacts.model.Subdivision;
import de.saxsys.mvvmfx.examples.contacts.ui.scopes.ContactDialogScope;
import de.saxsys.mvvmfx.utils.itemlist.ItemList;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

public class AddressFormViewModel implements ViewModel {

	static final String NOTHING_SELECTED_MARKER = "---";
	static final String SUBDIVISION_LABEL_KEY = "addressform.subdivision.label";

	private ObservableList<String> countries;
	private ObservableList<String> subdivisions;

	private final ReadOnlyBooleanWrapper valid = new ReadOnlyBooleanWrapper(true);
	private final ReadOnlyStringWrapper subdivisionLabel = new ReadOnlyStringWrapper();

	private final StringProperty street = new SimpleStringProperty();
	private final StringProperty postalCode = new SimpleStringProperty();
	private final StringProperty city = new SimpleStringProperty();
	private final ObjectProperty<Subdivision> subdivision = new SimpleObjectProperty<>();
	private final ObjectProperty<Country> country = new SimpleObjectProperty<>();

	private final StringProperty selectedCountry = new SimpleStringProperty(NOTHING_SELECTED_MARKER);
	private final StringProperty selectedSubdivision = new SimpleStringProperty(NOTHING_SELECTED_MARKER);

	private final ReadOnlyBooleanWrapper loadingInProgress = new ReadOnlyBooleanWrapper();
	private final ReadOnlyBooleanWrapper countryInputDisabled = new ReadOnlyBooleanWrapper();
	private final ReadOnlyBooleanWrapper subdivisionInputDisabled = new ReadOnlyBooleanWrapper();

	@Inject
	CountrySelector countrySelector;

	@Inject
	ResourceBundle resourceBundle;

	@InjectScope
	ContactDialogScope dialogScope;

	// Don't inline this field. It's needed to prevent the list mapping from being garbage collected.
	private ItemList<Country> countryItemList;
	// Don't inline this field. It's needed to prevent the list mapping from being garbage collected.
	private ItemList<Subdivision> subdivisionItemList;
	private Address address;

	private ObjectBinding<Contact> contactBinding;

	public void initialize() {
		dialogScope.subscribe(ContactDialogScope.RESET_FORMS, (key, payload) -> resetForm());
		dialogScope.subscribe(ContactDialogScope.COMMIT, (key, payload) -> commitChanges());

		ObjectProperty<Contact> contactToEditProperty = dialogScope.contactToEditProperty();

		if (contactToEditProperty.get() != null) {
			initWithAddress(contactToEditProperty.get().getAddress());
		}

		contactToEditProperty.addListener((observable, oldValue, newValue) -> {
			if (newValue != null) {
				if (newValue.getAddress() == null) {
					System.out.println("Address is null");
				} else {
					initWithAddress(newValue.getAddress());
				}
			}
		});

		loadingInProgress.bind(countrySelector.inProgressProperty());

		initSubdivisionLabel();
		initCountryList();
		initSubdivisionList();

		selectedCountry.addListener((obs, oldV, newV) -> {
			if (newV != null && !newV.equals(NOTHING_SELECTED_MARKER)) {
				Optional<Country> matchingCountry = countrySelector.availableCountries().stream()
						.filter(country -> newV.equals(country.getName()))
						.findFirst();

				if (matchingCountry.isPresent()) {
					countrySelector.setCountry(matchingCountry.get());
					country.set(matchingCountry.get());
				}
			} else if (NOTHING_SELECTED_MARKER.equals(newV)) {
				countrySelector.setCountry(null);
				country.set(null);
			}
			selectedSubdivision.set(NOTHING_SELECTED_MARKER);
		});

		selectedSubdivision.addListener((obs, oldV, newV) -> {
			if (newV != null && !newV.equals(NOTHING_SELECTED_MARKER)) {
				Optional<Subdivision> subdivisionOptional = countrySelector.subdivisions().stream()
						.filter(subdivision -> subdivision.getName().equals(newV)).findFirst();

				if (subdivisionOptional.isPresent()) {
					subdivision.set(subdivisionOptional.get());
				} else {
					subdivision.set(null);
				}
			} else {
				subdivision.set(null);
			}
		});

		countryInputDisabled.bind(loadingInProgress);
		subdivisionInputDisabled.bind(loadingInProgress.or(Bindings.size(subdivisionsList()).lessThanOrEqualTo(1)));

		dialogScope.addressFormValidProperty().bind(valid);
	}

	void initSubdivisionLabel() {
		subdivisionLabel.bind(
				Bindings.when(
						countrySelector.subdivisionLabel().isEmpty())
				.then(resourceBundle.getString(SUBDIVISION_LABEL_KEY))
				.otherwise(countrySelector.subdivisionLabel()));
	}

	private void initSubdivisionList() {
		subdivisionItemList = new ItemList<>(countrySelector.subdivisions(), Subdivision::getName);
		subdivisions = createListWithNothingSelectedMarker(subdivisionItemList.getTargetList());
		subdivisions.addListener((ListChangeListener<String>) c -> selectedSubdivision.set(NOTHING_SELECTED_MARKER));
	}

	private void initCountryList() {
		countryItemList = new ItemList<>(countrySelector.availableCountries(), Country::getName);
		ObservableList<String> mappedList = countryItemList.getTargetList();

		countries = createListWithNothingSelectedMarker(mappedList);
		countries.addListener((ListChangeListener<String>) c -> selectedCountry.set(NOTHING_SELECTED_MARKER));
	}

	private void commitChanges() {
		address.setStreet(street.get());
		address.setCity(city.get());
		address.setPostalcode(postalCode.get());
		address.setCountry(country.get());
		address.setSubdivision(subdivision.get());
	}

	public void initWithAddress(Address address) {
		this.address = address;
		street.set(address.getStreet());
		city.set(address.getCity());
		postalCode.set(address.getPostalcode());

		if (address.getCountry() != null) {
			selectedCountry.set(address.getCountry().getName());
		}
		if (address.getSubdivision() != null) {
			selectedSubdivision.set(address.getSubdivision().getName());
		}
	}

	/**
	 * Creates an observable list that always has
	 * {@link #NOTHING_SELECTED_MARKER} as first element and the values of the
	 * given observable list.
	 */
	static ObservableList<String> createListWithNothingSelectedMarker(ObservableList<String> source) {
		final ObservableList<String> result = FXCollections.observableArrayList();
		result.add(NOTHING_SELECTED_MARKER);
		result.addAll(source);

		// for sure there are better solutions for this but it's sufficient for our demo
		source.addListener((ListChangeListener<String>) c -> {
			result.clear();
			result.add(NOTHING_SELECTED_MARKER);
			result.addAll(source);
		});
		return result;
	}

	public ObservableList<String> countriesList() {
		return countries;
	}

	public ObservableList<String> subdivisionsList() {
		return subdivisions;
	}

	public StringProperty streetProperty() {
		return street;
	}

	public StringProperty cityProperty() {
		return city;
	}

	public StringProperty postalCodeProperty() {
		return postalCode;
	}

	public StringProperty selectedCountryProperty() {
		return selectedCountry;
	}

	public StringProperty selectedSubdivisionProperty() {
		return selectedSubdivision;
	}

	public ReadOnlyStringProperty subdivisionLabel() {
		return subdivisionLabel.getReadOnlyProperty();
	}

	public ReadOnlyBooleanProperty loadingInProgressProperty() {
		return loadingInProgress.getReadOnlyProperty();
	}

	public ReadOnlyBooleanProperty countryInputDisabledProperty() {
		return countryInputDisabled.getReadOnlyProperty();
	}

	public ReadOnlyBooleanProperty subdivisionInputDisabledProperty() {
		return subdivisionInputDisabled.getReadOnlyProperty();
	}

	private void resetForm() {
		street.set("");
		city.set("");
		postalCode.set("");
		selectedCountry.set(NOTHING_SELECTED_MARKER);
		selectedSubdivision.set(NOTHING_SELECTED_MARKER);
		subdivision.set(null);
		country.set(null);
	}

}