/* * 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.extapi.module; import jetbrains.mps.extapi.model.SModelBase; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelId; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleListener; import org.jetbrains.mps.openapi.module.SModuleListenerBase; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; public abstract class SModuleBase implements SModule { private static final Logger LOG = LogManager.getLogger(SModuleBase.class); public static final Comparator<SModel> MODEL_BY_NAME_COMPARATOR = (m1, m2) -> m1.getName().getValue().compareTo(m2.getName().getValue()); private volatile SRepository myRepository = null; private List<SModuleListener> myListeners = new CopyOnWriteArrayList<>(); private final Set<SModelBase> myModels = new LinkedHashSet<>(); private final ConcurrentMap<SModelId, SModel> myIdToModelMap = new ConcurrentHashMap<>(); protected SModuleBase() { } @Override public final SRepository getRepository() { SRepository repository = myRepository; if (repository != null) { // XXX it's odd model.getRepository doesn't require model read, while model.getModule().getRepository() does. repository.getModelAccess().checkReadAccess(); } return repository; } @Override @NotNull public final List<SModel> getModels() { assertCanRead(); ArrayList<SModel> models = new ArrayList<>(myModels); models.sort(MODEL_BY_NAME_COMPARATOR); return models; } public void attach(@NotNull SRepository repo) { if (myRepository != null) { throw new IllegalStateException("Already attached."); } repo.getModelAccess().checkWriteAccess(); myRepository = repo; for (SModelBase m : myModels) { m.attach(repo); } } public void dispose() { assert myRepository != null; assertCanChange(); for (SModelBase m : myModels) { m.detach(); } myModels.clear(); myIdToModelMap.clear(); myRepository = null; } @Override public final void addModuleListener(SModuleListener listener) { myListeners.add(listener); } @Override public final void removeModuleListener(SModuleListener listener) { myListeners.remove(listener); } protected final void fireChanged() { assertCanChange(); for (SModuleListener listener : myListeners) { try { listener.moduleChanged(this); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } protected final void fireModuleRenamed(SModuleReference oldRef) { assertCanChange(); for (SModuleListener listener : myListeners) { try { if (listener instanceof SModuleListenerBase) { ((SModuleListenerBase) listener).moduleRenamed(this, oldRef); } } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } private void fireModelAdded(SModel model) { assertCanRead(); for (SModuleListener listener : myListeners) { try { listener.modelAdded(this, model); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } private void fireBeforeModelRemoved(SModel model) { assertCanChange(); for (SModuleListener listener : myListeners) { try { listener.beforeModelRemoved(this, model); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } private void fireModelRemoved(SModelReference model) { assertCanChange(); for (SModuleListener listener : myListeners) { try { listener.modelRemoved(this, model); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } /** * Note: this method must not be used, except from within the model implementation classes. */ public void fireBeforeModelRenamed(SModelBase model, SModelReference newName) { assertCanChange(); if (!(myModels.contains(model))) { return; } for (SModuleListener listener : myListeners) { try { listener.beforeModelRenamed(this, model, newName); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } /** * Note: this method must not be used, except from within the model implementation classes. */ public void fireModelRenamed(SModelBase model, SModelReference oldName) { assertCanChange(); if (!(myModels.contains(model))) { return; } for (SModuleListener listener : myListeners) { try { listener.modelRenamed(this, model, oldName); } catch (VirtualMachineError e) { throw e; } catch (Throwable t) { LOG.error("", t); } } } @Override public SModel getModel(SModelId id) { // XXX used to be final, which looks right, but there's scenario with TransientModule which needs to answer // models not yet published (i.e. resolve references to proxy models, see StaticReference#getTargetSModel()) // Re-consider once understand better if TransientModelsModule should indeed be that special, and whether we need // new resolveInDependencies(SModelReference) assertCanRead(); return myIdToModelMap.get(id); } public void registerModel(SModelBase model) { assertCanChange(); if (model.getModule() != null && model.getModule() != this) { throw new IllegalArgumentException(String.format("Model '%s' is already registered in the module: '%s', " + "when trying to register it in '%s'.", model.getModelName(), model.getModule(), this)); } myModels.add(model); myIdToModelMap.put(model.getModelId(), model); if (myRepository != null) { model.attach(myRepository); } model.setModule(this); fireModelAdded(model); } public void unregisterModel(SModelBase model) { assertCanChange(); if (model.getModule() != this) { throw new IllegalArgumentException("Model `" + model.getModelName() + "' is registered elsewhere."); } fireBeforeModelRemoved(model); SModelReference reference = model.getReference(); myIdToModelMap.remove(reference.getModelId()); myModels.remove(model); model.detach(); fireModelRemoved(reference); } protected void assertCanRead() { final SRepository repository = myRepository; if (repository != null) { repository.getModelAccess().checkReadAccess(); } } protected void assertCanChange() { SRepository repository = myRepository; if (repository != null) { repository.getModelAccess().checkWriteAccess(); } } }