/* * 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.generator; import jetbrains.mps.extapi.model.SModelData; import jetbrains.mps.extapi.module.SRepositoryExt; import jetbrains.mps.project.ModuleId; import jetbrains.mps.project.structure.modules.ModuleReference; import jetbrains.mps.smodel.BaseMPSModuleOwner; import jetbrains.mps.smodel.MPSModuleOwner; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; public class TransientModelsProvider { private final ConcurrentMap<GeneratorTask, TransientModelsModule> myModuleMap = new ConcurrentHashMap<>(); private int myModelsToKeepMax = 0; /* unlimited */ private final SRepositoryExt myRepository; private int myKeptModels; private final TransientSwapOwner myTransientSwapOwner; private String mySessionId; private final MPSModuleOwner myOwner = new BaseMPSModuleOwner(); private TransientModelsModule myCheckpointsModule; public TransientModelsProvider(@NotNull SRepository repository, @Nullable TransientSwapOwner swapOwner) { myRepository = (SRepositoryExt) repository; myTransientSwapOwner = swapOwner; } /** * @return repository where transient modules reside */ public SRepository getRepository() { return myRepository; } protected void clearAll(final boolean dropCheckpoint) { myRepository.getModelAccess().runWriteAction(new Runnable() { @Override public void run() { Collection<TransientModelsModule> toRemove = getModuleMapValues(); myModuleMap.clear(); for (TransientModelsModule m : toRemove) { myRepository.unregisterModule(m, myOwner); } if (dropCheckpoint && myCheckpointsModule != null) { myRepository.unregisterModule(myCheckpointsModule, myOwner); myCheckpointsModule = null; } } }); TransientSwapSpace space = getTransientSwapSpace(); if (space != null) { space.clear(); } mySessionId = null; myKeptModels = 0; } /** * Requires write lock for {@linkplain #getRepository() associated repository} */ public void publishAll() { for (TransientModelsModule m : getModuleMapValues()) { myRepository.registerModule(m, myOwner); m.publishAll(); } if (myCheckpointsModule != null) { myCheckpointsModule.publishAll(); } } private static final AtomicInteger ourModuleCounter = new AtomicInteger(); /** * Instantiate new transient, free-floating module with given {@code moduleName} augmented with implementation-specific stereotype. * The module is NOT registered with any repository (including associated {@linkplain #getRepository() one}). Transient modules * get registered in the associated repository when {@link #publishAll()} is requested. Thus, only publish activity would require write lock, * while the transformation process is ok with a read on source model's repository. It's not final, though. If we manage to maintain distinct * repository for transients, we still may lock it for write during transformation process (transitively locking source model's one for read) * and there'd be no reason to be minimalistic about write lock then. * @param moduleName name for a new transient module, without stereotype * @return new module instance */ @NotNull public TransientModelsModule createModule(@NotNull String moduleName) { String fqName = moduleName + "@transient" + ourModuleCounter.getAndIncrement(); SModuleReference reference = PersistenceFacade.getInstance().createModuleReference(ModuleId.regular(), fqName); final TransientModelsModule transientModelsModule = new TransientModelsModule(this, reference); return transientModelsModule; } /** * Record module that keeps transient models for the given task. Module originates from {@link #createModule(String)}. * This association is utilized by {@link #getModule(GeneratorTask)}. Silently overwrites existing association, if any. * @param task transformation activity * @param transientModule module to keep transient models at */ public void associate(@NotNull GeneratorTask task, @NotNull TransientModelsModule transientModule) { myModuleMap.put(task, transientModule); } /** * XXX perhaps, GeneratorTask shall answer getTransientModule():SModule itself. * * @param task not {@code null} * @return module to keep transients models of the task at */ public TransientModelsModule getModule(GeneratorTask task) { if (myModuleMap.containsKey(task)) { return myModuleMap.get(task); } throw new IllegalStateException("No transient module associated with task " + task); } // despite public, not part of API/contract. Made available to friend classes public TransientModelsModule getCheckpointsModule() { return myCheckpointsModule; } /** * Requires model write to register checkpoint module with a {@linkplain #getRepository() repository}. */ public void initCheckpointModule() { if (myCheckpointsModule == null) { final String checkpointModuleName = "checkpoints"; // HACK. Though Make disposes TMP if creates one, there's distinct TMP for each model generated during the session. // We can't dispose all transients right after generation (need them for textgen), hence we re-use checkpoints module here for (SModule m : myRepository.getModules()) { if (checkpointModuleName.equals(m.getModuleName()) && m instanceof TransientModelsModule) { myCheckpointsModule = (TransientModelsModule) m; return; } } SModuleReference cpModuleRef = new ModuleReference(checkpointModuleName, ModuleId.regular()); // I could have used custom subclass of AbstractModule and regular models (instanceof extapi.TransientSModel, not necessarily // the same as generator.TransientSModel TransientModelsModule class produces), there's no true need in TransientModelsModule, however, // (a) don't want to refactor right now; (b) perhaps, could use swap mechanism of TransientModelsModule in future to keep checkpoint models // (though later could be addressed with extra consumer for TransientSwapOwner, not to mix the two kinds of transient models into single module kind). myCheckpointsModule = new TransientModelsModule(this, cpModuleRef); myRepository.registerModule(myCheckpointsModule, myOwner); } } public boolean canKeepOneMore() { return myModelsToKeepMax <= 0 || myKeptModels < myModelsToKeepMax; } public void decreaseKeptModels() { if (myModelsToKeepMax <= 0) { return; } myKeptModels++; // I know it's stupid and misguiding, but these two methods (canKeepOneMore and decreaseKeptModels) shall become history anyway } @Nullable private TransientSwapOwner getTransientSwapOwner() { return myTransientSwapOwner; } @Nullable public TransientSwapSpace getTransientSwapSpace() { if (mySessionId == null) { return null; } TransientSwapOwner tso = getTransientSwapOwner(); if (tso == null) { return null; } TransientSwapSpace space = tso.accessSwapSpace(mySessionId); if (space != null) { return space; } return tso.initSwapSpace(mySessionId); } public void removeAllTransient() { clearAll(false); } public void removeAllTransients(boolean includeCheckpoints) { clearAll(includeCheckpoints); } public Iterable<TransientModelsModule> getModules() { myRepository.getModelAccess().checkReadAccess(); Collection<TransientModelsModule> knownTransientModules = getModuleMapValues(); List<TransientModelsModule> result = new ArrayList<>(knownTransientModules.size() + 1); if (getCheckpointsModule() != null && getCheckpointsModule().hasPublished()) { result.add(getCheckpointsModule()); } for (TransientModelsModule m : knownTransientModules) { if (m.hasPublished()) { result.add(m); } } return result; } @Nullable public GenerationTrace getTrace(@NotNull SModelReference model) { for (TransientModelsModule m : getModuleMapValues()) { if (m.hasPublished() ) { // not quite sure there's strong reason to check if module has anything published, // although we are likely to navigate from trace to transient models and would need them published GenerationTrace trace = m.getTrace(model); if (trace != null) { return trace; } } } return null; } public void startGeneration(int numberOfModelsToKeep) { if (mySessionId == null) { mySessionId = newSessionId(); myKeptModels = 0; } myModelsToKeepMax = numberOfModelsToKeep; } private String newSessionId() { return String.valueOf(System.identityHashCode(myRepository)) + Long.toHexString(System.currentTimeMillis()); } /** * @return collection of unique modules associated with tasks */ private Collection<TransientModelsModule> getModuleMapValues() { return new LinkedHashSet<>(myModuleMap.values()); } public interface TransientSwapSpace { boolean swapOut(SModelData model); <T extends SModelData> T restoreFromSwap(SModelReference mref, T modelData); void clear(); } public interface TransientSwapOwner { TransientSwapSpace initSwapSpace(String spaceId); TransientSwapSpace accessSwapSpace(String spaceId); } }