/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* 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 jetbrains.mps.smodel.event;
import jetbrains.mps.util.annotation.ToRemove;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.event.SNodeAddEvent;
import org.jetbrains.mps.openapi.event.SNodeReadEvent;
import org.jetbrains.mps.openapi.event.SNodeRemoveEvent;
import org.jetbrains.mps.openapi.event.SPropertyChangeEvent;
import org.jetbrains.mps.openapi.event.SPropertyReadEvent;
import org.jetbrains.mps.openapi.event.SReferenceChangeEvent;
import org.jetbrains.mps.openapi.event.SReferenceReadEvent;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelAccessListener;
import org.jetbrains.mps.openapi.model.SModelChangeListener;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeAccessListener;
import org.jetbrains.mps.openapi.model.SNodeChangeListener;
import org.jetbrains.mps.openapi.model.SReference;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Facility to track model listeners and to dispatch events.
* Responsible for OpenAPI listeners only (i.e. not about legacy and soon-to-cease {@link jetbrains.mps.smodel.event.SModelListener}).
* Keeping this separate from SModelBase gives flexibility in implementing SModel.
* @author Artem Tikhomirov
*/
public final class ModelEventDispatch {
// model we dispatch events for
private final SModel myModel;
// same as myModel, casted to EditableSModel for convenience, or null if myModel is not editable
private final EditableSModel myEditableSModel;
private final List<SNodeAccessListener> myAccessListeners = new CopyOnWriteArrayList<SNodeAccessListener>();
private final List<SNodeChangeListener> myChangeListeners = new CopyOnWriteArrayList<SNodeChangeListener>();
@ToRemove(version = 3.3)
private final List<LegacyNodeAccessListener> myLegacyReadListeners = new CopyOnWriteArrayList<LegacyNodeAccessListener>();
@ToRemove(version = 3.3)
private final List<LegacyNodeChangeListener> myLegacyChangeListeners = new CopyOnWriteArrayList<LegacyNodeChangeListener>();
public ModelEventDispatch(@NotNull SModel model) {
myModel = model;
myEditableSModel = model instanceof EditableSModel ? (EditableSModel) model : null;
}
@ToRemove(version = 3.3)
public void addAccessListener(@Nullable SModelAccessListener l) {
if (l == null) {
return;
}
LegacyNodeAccessListener wrap = new LegacyNodeAccessListener(l);
addAccessListener(wrap);
myLegacyReadListeners.add(wrap);
}
@ToRemove(version = 3.3)
public void removeAccessListener(@Nullable SModelAccessListener l) {
if (l == null) {
return;
}
LegacyNodeAccessListener wrap = null;
for (LegacyNodeAccessListener w : myLegacyReadListeners) {
if (w.wraps(l)) {
wrap = w;
break;
}
}
if (wrap == null) {
return;
}
myLegacyReadListeners.remove(wrap);
removeAccessListener(wrap);
}
public void addAccessListener(@Nullable SNodeAccessListener l) {
if (l != null) {
myAccessListeners.add(l);
}
}
public void removeAccessListener(@Nullable SNodeAccessListener l) {
if (l != null) {
myAccessListeners.remove(l);
}
}
@ToRemove(version = 3.3)
public void addChangeListener(SModelChangeListener l) {
if (l == null) {
return;
}
LegacyNodeChangeListener wrap = new LegacyNodeChangeListener(l);
myLegacyChangeListeners.add(wrap);
addChangeListener(wrap);
}
@ToRemove(version = 3.3)
public void removeChangeListener(SModelChangeListener l) {
if (l == null) {
return;
}
LegacyNodeChangeListener wrap = null;
for (LegacyNodeChangeListener w : myLegacyChangeListeners) {
if (w.wraps(l)) {
wrap = w;
break;
}
}
if (wrap == null) {
return;
}
myLegacyChangeListeners.remove(wrap);
removeChangeListener(wrap);
}
public void addChangeListener(SNodeChangeListener l) {
if (l == null) {
return;
}
myChangeListeners.add(l);
}
public void removeChangeListener(SNodeChangeListener l) {
if (l == null) {
return;
}
myChangeListeners.remove(l);
}
public void fireNodeRead(SNode node) {
if (myAccessListeners.isEmpty()) {
return;
}
final SNodeReadEvent event = new SNodeReadEvent(node);
for (SNodeAccessListener l : myAccessListeners) {
l.nodeRead(event);
}
}
public void fireReferenceRead(SNode node, SReferenceLink reference) {
if (myAccessListeners.isEmpty()) {
return;
}
final SReferenceReadEvent event = new SReferenceReadEvent(node, reference);
for (SNodeAccessListener l : myAccessListeners) {
l.referenceRead(event);
}
}
public void firePropertyRead(SNode node, SProperty property) {
if (myAccessListeners.isEmpty()) {
return;
}
final SPropertyReadEvent event = new SPropertyReadEvent(node, property);
for (SNodeAccessListener l : myAccessListeners) {
l.propertyRead(event);
}
}
public void fireReferenceChange(SNode node, SReferenceLink role, SReference oldValue, SReference newValue) {
markEditableModelChanged();
if (myChangeListeners.isEmpty()) {
return;
}
final SReferenceChangeEvent event = new SReferenceChangeEvent(myModel, node, role, oldValue, newValue);
for (SNodeChangeListener l : myChangeListeners) {
l.referenceChanged(event);
}
}
public void firePropertyChange(SNode node, SProperty property, String oldValue, String newValue) {
markEditableModelChanged();
if (myChangeListeners.isEmpty()) {
return;
}
final SPropertyChangeEvent event = new SPropertyChangeEvent(myModel, node, property, oldValue, newValue);
for (SNodeChangeListener l : myChangeListeners) {
l.propertyChanged(event);
}
}
public void fireNodeAdd(SNode node, SContainmentLink role, SNode child) {
markEditableModelChanged();
if (myChangeListeners.isEmpty()) {
return;
}
final SNodeAddEvent event = role == null ? new SNodeAddEvent(myModel, child) : new SNodeAddEvent(myModel, node, child, role);
for (SNodeChangeListener l : myChangeListeners) {
l.nodeAdded(event);
}
}
public void fireNodeRemove(SNode node, SContainmentLink role, SNode child) {
markEditableModelChanged();
if (myChangeListeners.isEmpty()) {
return;
}
final SNodeRemoveEvent event = role == null ? new SNodeRemoveEvent(myModel, child) : new SNodeRemoveEvent(myModel, node, child, role);
for (SNodeChangeListener l : myChangeListeners) {
l.nodeRemoved(event);
}
}
// instead of EditableSModelBase attaching a change listener to itself to update its 'changed' state,
// we update this state from event dispatcher
private void markEditableModelChanged() {
if (myEditableSModel != null) {
myEditableSModel.setChanged(true);
}
}
}