All Downloads are FREE. Search and download functionalities are using the official Maven repository.

is.codion.framework.model.EntityEditEvents Maven / Gradle / Ivy

There is a newer version: 0.18.25
Show newest version
/*
 * This file is part of Codion.
 *
 * Codion is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Codion 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 General Public License
 * along with Codion.  If not, see .
 *
 * Copyright (c) 2019 - 2024, Björn Darri Sigurðsson.
 */
package is.codion.framework.model;

import is.codion.framework.domain.entity.Entity;
import is.codion.framework.domain.entity.EntityType;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import static is.codion.framework.domain.entity.Entity.groupByType;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.*;

/**
 * A central event hub for listening for entity inserts, updates and deletes.
 * You must keep a live reference to any listeners added in order to prevent
 * them from being garbage collected, since listeners are added via a {@link java.lang.ref.WeakReference}.
 * {@link EntityEditModel} uses this to post its events.
 * @see EntityEditModel#POST_EDIT_EVENTS
 */
public final class EntityEditEvents {

	private static final EntityEditListener EDIT_LISTENER = new EntityEditListener();

	private EntityEditEvents() {}

	/**
	 * Adds an insert listener, notified each time entities of the given type are inserted.
	 * Note that you have to keep a live reference to the listener instance,
	 * otherwise it will be garbage collected, due to a weak reference.
	 * @param entityType the type of entity to listen for
	 * @param listener the listener
	 */
	public static void addInsertListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.addInsertListener(entityType, listener);
	}

	/**
	 * Adds an update listener, notified each time entities of the given type are updated.
	 * Note that you have to keep a live reference to the listener instance,
	 * otherwise it will be garbage collected, due to a weak reference.
	 * @param entityType the type of entity to listen for
	 * @param listener the listener
	 */
	public static void addUpdateListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.addUpdateListener(entityType, listener);
	}

	/**
	 * Adds a delete listener, notified each time entities of the given type are deleted.
	 * Note that you have to keep a live reference to the listener instance,
	 * otherwise it will be garbage collected, due to a weak reference.
	 * @param entityType the type of entity to listen for
	 * @param listener the listener
	 */
	public static void addDeleteListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.addDeleteListener(entityType, listener);
	}

	/**
	 * Removes the given listener
	 * @param entityType the entityType
	 * @param listener the listener to remove
	 */
	public static void removeInsertListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.removeInsertListener(entityType, listener);
	}

	/**
	 * Removes the given listener
	 * @param entityType the entityType
	 * @param listener the listener to remove
	 */
	public static void removeUpdateListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.removeUpdateListener(entityType, listener);
	}

	/**
	 * Removes the given listener
	 * @param entityType the entityType
	 * @param listener the listener to remove
	 */
	public static void removeDeleteListener(EntityType entityType, Consumer> listener) {
		EDIT_LISTENER.removeDeleteListener(entityType, listener);
	}

	/**
	 * Notifies insert
	 * @param insertedEntities the inserted entities
	 */
	public static void inserted(Collection insertedEntities) {
		EDIT_LISTENER.notifyInserted(requireNonNull(insertedEntities));
	}

	/**
	 * Notifies update
	 * @param updatedEntities the updated entities mapped to their original primary key
	 */
	public static void updated(Map updatedEntities) {
		EDIT_LISTENER.notifyUpdated(requireNonNull(updatedEntities));
	}

	/**
	 * Notifies delete
	 * @param deletedEntities the deleted entities
	 */
	public static void deleted(Collection deletedEntities) {
		EDIT_LISTENER.notifyDeleted(requireNonNull(deletedEntities));
	}

	private static final class EntityEditListener {

		private final Map>> insertListeners = new ConcurrentHashMap<>();
		private final Map>> updateListeners = new ConcurrentHashMap<>();
		private final Map>> deleteListeners = new ConcurrentHashMap<>();

		private void addInsertListener(EntityType entityType, Consumer> listener) {
			insertListeners(entityType).addListener(listener);
		}

		private void removeInsertListener(EntityType entityType, Consumer> listener) {
			insertListeners(entityType).removeListener(listener);
		}

		private void addUpdateListener(EntityType entityType, Consumer> listener) {
			updateListeners(entityType).addListener(listener);
		}

		private void removeUpdateListener(EntityType entityType, Consumer> listener) {
			updateListeners(entityType).removeListener(listener);
		}

		private void addDeleteListener(EntityType entityType, Consumer> listener) {
			deleteListeners(entityType).addListener(listener);
		}

		private void removeDeleteListener(EntityType entityType, Consumer> listener) {
			deleteListeners(entityType).removeListener(listener);
		}

		private void notifyInserted(Collection inserted) {
			groupByType(inserted).forEach(this::notifyInserted);
		}

		private void notifyInserted(EntityType entityType, Collection inserted) {
			Listeners> listeners = insertListeners.get(entityType);
			if (listeners != null) {
				listeners.onEvent(inserted);
			}
		}

		private void notifyUpdated(Map updated) {
			updated.entrySet()
							.stream()
							.collect(groupingBy(entry -> entry.getKey().entityType(), LinkedHashMap::new, toList()))
							.forEach(this::notifyUpdated);
		}

		private void notifyUpdated(EntityType entityType, List> updated) {
			Listeners> listeners = updateListeners.get(entityType);
			if (listeners != null) {
				listeners.onEvent(updated.stream()
								.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
			}
		}

		private void notifyDeleted(Collection deleted) {
			groupByType(deleted).forEach(this::notifyDeleted);
		}

		private void notifyDeleted(EntityType entityType, Collection deleted) {
			Listeners> listeners = deleteListeners.get(entityType);
			if (listeners != null) {
				listeners.onEvent(deleted);
			}
		}

		private Listeners> insertListeners(EntityType entityType) {
			return insertListeners.computeIfAbsent(requireNonNull(entityType), type -> new Listeners<>());
		}

		private Listeners> updateListeners(EntityType entityType) {
			return updateListeners.computeIfAbsent(requireNonNull(entityType), type -> new Listeners<>());
		}

		private Listeners> deleteListeners(EntityType entityType) {
			return deleteListeners.computeIfAbsent(requireNonNull(entityType), type -> new Listeners<>());
		}

		private static final class Listeners {

			private final List>> listenerReferences = new ArrayList<>();

			private synchronized void onEvent(T data) {
				requireNonNull(data);
				Iterator>> iterator = listenerReferences.iterator();
				while (iterator.hasNext()) {
					Consumer listener = iterator.next().get();
					if (listener == null) {
						iterator.remove();
					}
					else {
						listener.accept(data);
					}
				}
			}

			private synchronized void addListener(Consumer listener) {
				requireNonNull(listener);
				for (WeakReference> reference : listenerReferences) {
					if (reference.get() == listener) {
						return;
					}
				}
				listenerReferences.add(new WeakReference<>(listener));
			}

			private synchronized void removeListener(Consumer listener) {
				requireNonNull(listener);
				listenerReferences.removeIf(reference -> reference.get() == null || reference.get() == listener);
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy