/** * Get more info at : www.jrebirth.org . * Copyright JRebirth.org © 2011-2013 * Contact : sebastien.bordes@jrebirth.org * * 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 org.jrebirth.af.core.wave; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import org.jrebirth.af.api.log.JRLogger; import org.jrebirth.af.api.wave.Wave; import org.jrebirth.af.api.wave.WaveBean; import org.jrebirth.af.api.wave.WaveGroup; import org.jrebirth.af.api.wave.WaveListener; import org.jrebirth.af.api.wave.contract.WaveData; import org.jrebirth.af.api.wave.contract.WaveItem; import org.jrebirth.af.api.wave.contract.WaveType; import org.jrebirth.af.core.link.LinkMessages; import org.jrebirth.af.core.log.JRLoggerFactory; /** * * The class <strong>WaveBase</strong>. * * This Bean is used to move wave's data through layer. It allow to manage priorities. */ public class WaveBase implements Wave, LinkMessages { /** The class logger. */ private static final JRLogger LOGGER = JRLoggerFactory.getLogger(WaveBase.class); /** The space separator. */ private static final String SPACE_SEP = " "; /** The Wave Unique Identifier. */ private final String wuid; /** The Wave timestamp. */ private final long timestamp; /** The wave status (can be bound). */ private final ObjectProperty<Status> statusProperty = new SimpleObjectProperty<>(Status.Created); /** The group of the wave used to dispatch the right event. */ private WaveGroup waveGroup = WaveGroup.UNDEFINED; /** * The type of the wave used to call the right method name of the receiver object. */ private WaveType waveType; /** The from class to used for create waves. */ private Class<?> fromClass; /** The related component class to used for create waves. */ private Class<?> componentClass; /** The priority used to process wave according to a custom order. */ private int priority; /** The related wave to the current wave, cold be a parent wave or child wave according context. */ private Wave relatedWave; /** A map used to contain all data. */ private final Map<WaveItem<?>, WaveData<?>> waveItemsMap = new HashMap<>(); /** A sorted list that contains all data. */ private final List<WaveData<?>> waveDataList = new ArrayList<>(); /** * The wave bean. */ private Map<Class<? extends WaveBean>, WaveBean> waveBeanMap; /** The type extending WaveBean to use to embed some values. */ // private Class<? extends WaveBean> waveBeanClass; /** * The list of wave Listener to warn when wave status changed. */ private final List<WaveListener> waveListeners = Collections.synchronizedList(new ArrayList<WaveListener>()); /** The list of Wave Handlers used to manage the Handled status. */ private List<? extends Object> waveHandlers; /** * Default Constructor. */ WaveBase() { super(); // Generate a random but unique identifier this.wuid = UUID.randomUUID().toString(); // Store the creation date this.timestamp = System.currentTimeMillis(); } /** * {@inheritDoc} */ @Override public WaveGroup waveGroup() { return this.waveGroup; } /** * {@inheritDoc} */ @Override public Wave waveGroup(final WaveGroup waveGroup) { this.waveGroup = waveGroup; return this; } /** * {@inheritDoc} */ @Override public WaveType waveType() { return this.waveType; } /** * {@inheritDoc} */ @Override public Wave waveType(final WaveType waveType) { this.waveType = waveType; return this; } /** * {@inheritDoc} */ @Override public Class<?> fromClass() { return this.fromClass; } /** * {@inheritDoc} */ @Override public Wave fromClass(final Class<?> fromClass) { this.fromClass = fromClass; return this; } /** * {@inheritDoc} */ @Override public Class<?> componentClass() { return this.componentClass; } /** * {@inheritDoc} */ @Override public Wave componentClass(final Class<?> componentClass) { this.componentClass = componentClass; return this; } /** * {@inheritDoc} */ @Override public int priority() { return this.priority; } /** * {@inheritDoc} */ @Override public Wave priority(final int priority) { this.priority = priority; return this; } /** * {@inheritDoc} */ @Override public Wave relatedWave() { return this.relatedWave; } /** * {@inheritDoc} */ @Override public Wave relatedWave(final Wave nextWave) { this.relatedWave = nextWave; return this; } /** * {@inheritDoc} */ @Override public List<WaveData<?>> waveDatas() { return this.waveDataList; } /** * {@inheritDoc} */ @Override public Wave addDatas(final WaveData<?>... waveDatas) { for (final WaveData<?> waveData : waveDatas) { // Init the order of the wave Data waveData.setOrder(waveDatas().size()); // Grab the previous value if any final WaveData<?> previous = this.waveItemsMap.get(waveData.getKey()); // Store into the map to allow access by WaveItem this.waveItemsMap.put(waveData.getKey(), waveData); // Remove the old value from the list if (previous != null) { this.waveDataList.remove(previous); } // Add into the list to enable sorting this.waveDataList.add(waveData); // Sort the list Collections.sort(this.waveDataList); } return this; } /** * {@inheritDoc} */ @Override public <T> Wave add(final WaveItem<T> waveItem, final T value) { final WaveData<T> waveData = Builders.waveData(waveItem, value); addDatas(waveData); return this; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <T> WaveData<T> getData(final WaveItem<T> waveItem) { return (WaveData<T>) this.waveItemsMap.get(waveItem); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <T> T get(final WaveItem<T> waveItem) { return (T) (this.waveItemsMap.containsKey(waveItem) ? this.waveItemsMap.get(waveItem).getValue() : null); } /** * {@inheritDoc} */ @Override public boolean contains(final WaveItem<?> waveItem) { return this.waveItemsMap.containsKey(waveItem); } /** * {@inheritDoc} */ @Override public boolean containsNotNull(final WaveItem<?> waveItem) { return contains(waveItem) && getData(waveItem).getValue() != null; } /** * {@inheritDoc} */ @Override public String getWUID() { return this.wuid; } /** * {@inheritDoc} */ @Override public long getTimestamp() { return this.timestamp; } /** * {@inheritDoc} */ @Override public Wave waveBean(final WaveBean waveBean) { getWaveBeanMap().put(waveBean.getClass(), waveBean); return this; } /** * Get the Wave Bean map. Create it if it hasn't been done before. * * @return the wave bean map never null */ private Map<Class<? extends WaveBean>, WaveBean> getWaveBeanMap() { if (this.waveBeanMap == null) { this.waveBeanMap = new HashMap<>(); } return this.waveBeanMap; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <WB extends WaveBean> WB waveBean(final Class<WB> waveBeanClass) { if (!getWaveBeanMap().containsKey(waveBeanClass)) { try { final WB waveBean = waveBeanClass.newInstance(); getWaveBeanMap().put(waveBeanClass, waveBean); } catch (InstantiationException | IllegalAccessException e) { LOGGER.error(WAVE_BEAN_CREATION_ERROR, e, waveBeanClass.toString()); } finally { // if (this.waveBean == null) { // this.waveBean = new DefaultWaveBean(); // } } } return (WB) getWaveBeanMap().get(waveBeanClass); } /** * {@inheritDoc} * * Don't use the returned list to add WaveBean. */ @Override public List<WaveBean> waveBeanList() { if (this.waveBeanMap != null && !this.waveBeanMap.isEmpty()) { return new ArrayList<>(this.waveBeanMap.values()); } return new ArrayList<>(); } /** * {@inheritDoc} */ @Override public Wave waveBeanList(final List<WaveBean> waveBeanList) { if (waveBeanList != null && !waveBeanList.isEmpty()) { waveBeanList.forEach(wb -> getWaveBeanMap().put(wb.getClass(), wb)); } return this; } /** * {@inheritDoc} */ @Override public Wave addWaveListener(final WaveListener waveListener) { this.waveListeners.add(waveListener); return this; } /** * {@inheritDoc} */ @Override public Wave removeWaveListener(final WaveListener waveListener) { this.waveListeners.remove(waveListener); return this; } /** * {@inheritDoc} */ @Override public Status status() { synchronized (this) { return this.statusProperty.get(); } } /** * {@inheritDoc} */ @Override public ObjectProperty<Status> statusProperty() { return this.statusProperty; } /** * {@inheritDoc} */ @Override public Wave status(final Status status) { synchronized (this) { if (this.statusProperty.get() == status) { // throw new CoreRuntimeException("The status " + status.toString() + " has been already set for this wave " + toString()); } else { this.statusProperty.set(status); fireStatusChanged(); } } return this; } /** * Fire a wave status change. */ private void fireStatusChanged() { // System.out.println("fireStatusChanged " + this.status.toString()); for (final WaveListener waveListener : this.waveListeners) { switch (this.statusProperty.get()) { case Created: waveListener.waveCreated(this); break; case Sent: waveListener.waveSent(this); break; case Processing: waveListener.waveProcessed(this); break; case Consumed: waveListener.waveConsumed(this); break; case Handled: waveListener.waveHandled(this); break; case Failed: waveListener.waveFailed(this); break; default: break; } } } /** * {@inheritDoc} */ @Override public String toString() { final StringBuilder sb = new StringBuilder(); if (waveGroup() != null) { sb.append(waveGroup()).append(SPACE_SEP); } if (fromClass() != null) { sb.append("fromClass=").append(fromClass().getSimpleName()).append(SPACE_SEP); } if (componentClass() != null) { sb.append("relatedClass=").append(componentClass().getSimpleName()).append(SPACE_SEP); } if (waveType() != null) { sb.append(waveType()).append(SPACE_SEP); } if (getWUID() != null) { sb.append("(").append(getWUID()).append(") "); } if (waveDatas().size() > 0) { sb.append("\r\nData=>"); for (final WaveData<?> wd : waveDatas()) { sb.append(wd.getKey()).append("=").append(wd.getValue()); } } return sb.toString(); } /** * {@inheritDoc} */ @Override public void setWaveHandlers(final List<? extends Object> waveHandlers) { this.waveHandlers = waveHandlers; } /** * {@inheritDoc} */ @Override public void removeWaveHandler(final Object waveHandler) { if (waveHandler != null) { // Remove the handler that has terminated this.waveHandlers.remove(waveHandler); // Update the status if required if (status() == Status.Consumed && this.waveHandlers.isEmpty()) { status(Status.Handled); } } } /** * {@inheritDoc} */ @Override public boolean hasWaveBean(final Class<? extends WaveBean> waveBeanClass) { if (this.waveBeanMap != null && !this.waveBeanMap.isEmpty()) { return this.waveBeanMap.containsKey(waveBeanClass); } return false; } }