/*
* Copyright 2003-2017 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;
import jetbrains.mps.smodel.event.ModelEventDispatch;
import jetbrains.mps.smodel.references.UnregisteredNodes;
import org.jetbrains.annotations.NotNull;
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.module.SRepository;
/**
* Normal state of any node, being part of a model.
*
* Events are dispatched, model access ensured.
* <p>
* OpenAPI listeners ({@link org.jetbrains.mps.openapi.model.SNodeAccessListener}, {@link org.jetbrains.mps.openapi.model.SModelChangeListener},
* {@link org.jetbrains.mps.openapi.model.SModelAccessListener}) are notified through {@link jetbrains.mps.smodel.event.ModelEventDispatch},
* this class shall not depend on particular openapi.SModel implementation (e.g. SModelBase or EditableSModelBase).
* <p>
* Legacy listeners ({@link jetbrains.mps.smodel.event.SModelListener}, {@link jetbrains.mps.smodel.NodeReadEventsCaster} and
* {@link jetbrains.mps.smodel.NodeReadAccessCasterInEditor} are handled here.
* <p>
* IMPORTANT: property/reference access shall not trigger node read. Node read is triggered once the node is obtained from the model,
* either as children, sibling or any other navigation means.
*
* @author Artem Tikhomirov
*/
final class AttachedNodeOwner extends SNodeOwner {
private final SModel myModel;
// can be null
private ModelEventDispatch myEventDispatch;
public AttachedNodeOwner(@NotNull SModel model) {
myModel = model;
}
/*package*/ void setEventDispatch(ModelEventDispatch dispatch) {
// the reason why I don't care to make myEventDispatch immediately visible
// in a multi-thread environment (i.e. one thread reads model and dispatches notifications
// while another attaches the model to model descriptor and updates myEventDispatch) as there's
// no contract whatsoever about what happens in this case. In a single-thread, this assignment would
// 'happen-before' any subsequent read and we are all set.
myEventDispatch = dispatch;
}
@Override
public void assertLegalRead() {
// FIXME explicit attach to set repository? So that it behaves exactly as it was prior to SNodeOwner?
final SRepository repo = myModel.getRepository();
if (repo != null) {
repo.getModelAccess().checkReadAccess();
}
}
@Override
public void assertLegalChange() {
final SModel model = myModel;
if (model.isUpdateMode()) {
return;
}
final SRepository repo = myModel.getRepository();
if (repo == null) {
return;
}
repo.getModelAccess().checkWriteAccess();
// due to isCommandAction() check one can't modify an attached model from within a write action (e.g. JavaDebugEvaluate facet during make).
// Is it right? What's the reason to have write action then?
if (!repo.getModelAccess().isCommandAction()) {
throw new IllegalModelChangeError("registered node can be modified only inside a command or during model loading process " + myModel);
}
}
@Override
public SModel getModel() {
return myModel;
}
@Override
public void registerNode(SNode node) {
myModel.registerNode(node);
// FIXME why UnregisteredNodes.put in SNode#unRegisterFromModel (#detach(SNodeOwner)) us conditioned with !isUpdateMode(), and this one is not?
UnregisteredNodes.instance().remove(node);
}
@Override
public void unregisterNode(SNode node) {
myModel.unregisterNode(node);
if (!myModel.isUpdateMode()) {
UnregisteredNodes.instance().put(node);
}
}
@Override
void performUndoableAction(org.jetbrains.mps.openapi.model.SNode node, SNodeUndoableAction action) {
myModel.performUndoableAction(action);
}
@Override
/*package*/ void fireNodeRead(SNode node, boolean needUnclassified) {
// nodeRead()
if (myModel.isUpdateMode()) {
return;
}
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireNodeRead(node);
}
if (!myModel.canFireReadEvent()) {
return;
}
// fireNodeReadAccess()
NodeReadAccessCasterInEditor.fireNodeReadAccessed(node);
if (needUnclassified) {
// fireNodeUnclassifiedReadAccess()
NodeReadEventsCaster.fireNodeUnclassifiedReadAccess(node);
}
}
@Override
/*package*/ void firePropertyRead(SNode node, SProperty p, String value, boolean hasProperty) {
// propertyRead();
if (myModel.isUpdateMode()) {
return;
}
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.firePropertyRead(node, p);
}
//firePropertyReadAccessInEditor();
//fireNodePropertyReadAccess();
if (!myModel.canFireReadEvent()) {
return;
}
final String propertyName = p.getName();
NodeReadAccessCasterInEditor.firePropertyReadAccessed(node, propertyName, hasProperty);
NodeReadEventsCaster.fireNodePropertyReadAccess(node, propertyName, value);
}
/**
* @param link not null
* @param target may be null
*/
@Override
/*package*/ void fireReferenceRead(SNode node, SReferenceLink link, SNode target) {
if (myModel.isUpdateMode()) {
return;
}
// referenceRead()
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireReferenceRead(node, link);
}
// fireNodeReferentReadAccess();
if (myModel.canFireReadEvent()) {
NodeReadEventsCaster.fireNodeReferentReadAccess(node, link.getRoleName(), target);
}
}
@Override
/*package*/ void firePropertyChange(SNode node, SProperty property, String oldValue, String newValue) {
if (myModel.isUpdateMode()) {
return;
}
myModel.firePropertyChangedEvent(node, property, oldValue, newValue);
//propertyChanged(property, oldValue, newValue);
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.firePropertyChange(node, property, oldValue, newValue);
}
}
@Override
/*package*/ void fireReferenceChange(SNode node, SReferenceLink l, org.jetbrains.mps.openapi.model.SReference oldRef, org.jetbrains.mps.openapi.model.SReference newRef) {
if (myModel.isUpdateMode()) {
return;
}
if (oldRef != null) {
myModel.fireReferenceRemovedEvent(oldRef);
}
if (newRef != null) {
myModel.fireReferenceAddedEvent(newRef);
}
// referenceChanged(l, oldRef, newRef);
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireReferenceChange(node, l, oldRef, newRef);
}
}
@Override
/*package*/ void fireNodeAdd(SNode node, SContainmentLink role, SNode child, SNode anchor) {
if (node == null && role == null) {
// root
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireNodeAdd(null, null, child);
}
myModel.fireRootAddedEvent(child);
return;
}
if (myModel.isUpdateMode()) {
return;
}
myModel.fireChildAddedEvent(node, role, child, anchor);
//nodeAdded(role, child);
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireNodeAdd(node, role, child);
}
}
@Override
void fireBeforeNodeRemove(SNode node, SContainmentLink role, SNode child, SNode anchor) {
if (node == null && role == null) {
myModel.fireBeforeRootRemovedEvent(child);
} else {
myModel.fireBeforeChildRemovedEvent(node, role, child, anchor);
}
}
@Override
/*package*/ void fireNodeRemove(SNode node, SContainmentLink role, SNode child, SNode anchor) {
if (node == null && role == null) {
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireNodeRemove(null, null, child);
}
myModel.fireRootRemovedEvent(child);
return;
}
if (myModel.isUpdateMode()) {
return;
}
myModel.fireChildRemovedEvent(node, role, child, anchor);
//nodeRemoved(child, role);
final ModelEventDispatch md = myEventDispatch;
if (md != null) {
md.fireNodeRemove(node, role, child);
}
}
}