/* * 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.GeneratableSModel; import jetbrains.mps.generator.impl.GenControllerContext; import jetbrains.mps.generator.impl.GenerationController; import jetbrains.mps.generator.impl.GeneratorLoggerAdapter; import jetbrains.mps.generator.impl.ModelStreamManager; import jetbrains.mps.generator.impl.ModelStreamProviderImpl; import jetbrains.mps.generator.impl.dependencies.GenerationDependencies; import jetbrains.mps.generator.impl.dependencies.GenerationDependenciesCache; import jetbrains.mps.messages.IMessageHandler; import jetbrains.mps.smodel.LanguageAspect; import jetbrains.mps.smodel.SModelStereotype; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.module.SRepository; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import org.jetbrains.mps.openapi.util.ProgressMonitor; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Entry point to model transformation (aka generation) process. Populate with relevant context information: * {@link #messages(IMessageHandler)} to receive generator messages (optional); * {@link #transients(TransientModelsProvider)} where to keep transient models (mandatory); * {@link #taskHandler(GeneratorTaskListener)} get notified about progress (optional); * then fire off with {@link #process(ProgressMonitor, List)} * * IMPLEMENTATION NOTE: * transformation requires model read lock for a repository of transformed model. At certain moments, it also requires write lock on a repository with * transient modules. Although I can (and would like to) hide appropriate locks inside TransientModelsProvider, now these are explicit and are outside * of the class to avoid accidental 'can't write from read'. Shall investigate if there's true need to expose transient modules in a repository right * from the very beginning. If yes, shall double efforts to get distinct repository for transient modules (so that only transient repo is write-locked, * while input model's repo is read-locked). * * @author Artem Tikhomirov * @author Evgeny Gryaznov, 1/25/11 */ public final class GenerationFacade { public static Collection<SModel> getModifiedModels(Collection<? extends SModel> models) { Set<SModel> result = new LinkedHashSet<>(); ModelGenerationStatusManager statusManager = ModelGenerationStatusManager.getInstance(); for (SModel sm : models) { if (statusManager.generationRequired(sm)) { result.add(sm); continue; } // TODO regenerating all dependant models can be slow, option? if (!(SModelStereotype.DESCRIPTOR.equals(SModelStereotype.getStereotype(sm)) || LanguageAspect.BEHAVIOR.is(sm) || LanguageAspect.CONSTRAINTS.is(sm))) { // temporary solution: only descriptor/behavior/constraints models continue; } final SRepository repository = sm.getRepository(); if (repository == null) { // no idea how to treat a model which hands in the air; expect it to be editable and tell isChanged if desires re-generation continue; } GenerationDependencies oldDependencies = GenerationDependenciesCache.getInstance().get(sm); // FIXME use SRepository to pick proper GenerationDependenciesCache instance if (oldDependencies == null) { // TODO turn on when generated file will be mandatory //result.add(sm); continue; } Map<String, String> externalHashes = oldDependencies.getExternalHashes(); for (Entry<String, String> entry : externalHashes.entrySet()) { String modelReference = entry.getKey(); SModel rmd = PersistenceFacade.getInstance().createModelReference(modelReference).resolve(repository); if (rmd == null) { result.add(sm); break; } String oldHash = entry.getValue(); if (oldHash == null) { continue; } String newHash = statusManager.currentHash(rmd); if (newHash == null || !oldHash.equals(newHash)) { result.add(sm); break; } } } return result; } public static boolean canGenerate(SModel sm) { return sm instanceof GeneratableSModel && ((GeneratableSModel) sm).isGeneratable(); } private final SRepository myRepository; private final GenerationOptions myGenerationOptions; private GeneratorTaskListener<GeneratorTask> myTaskListener; private TransientModelsProvider myTransientModelsProvider; private IMessageHandler myMessageHandler = IMessageHandler.NULL_HANDLER; private ModelStreamManager.Provider myStreamProvider; public GenerationFacade(@NotNull SRepository repository, @NotNull GenerationOptions generationOptions) { myRepository = repository; myGenerationOptions = generationOptions; } /** * Optional handler to get notified about generation process * @param taskHandler receives notifications * @return <code>this</code> for convenience */ public GenerationFacade taskHandler(@Nullable GeneratorTaskListener<GeneratorTask> taskHandler) { myTaskListener = taskHandler; return this; } /** * Register facility responsible for transient model handling, <em>mandatory</em>. * @param transientModelsProvider transient model facility * @return <code>this</code> for convenience */ public GenerationFacade transients(@NotNull TransientModelsProvider transientModelsProvider) { myTransientModelsProvider = transientModelsProvider; return this; } /** * Optional destination of all messages reported by generator, if none specified (or <code>null</code>), messages get discarded. * @param messages destination of generator messages, or <code>null</code> * @return <code>this</code> for convenience */ public GenerationFacade messages(@Nullable IMessageHandler messages) { myMessageHandler = messages == null ? IMessageHandler.NULL_HANDLER : messages; return this; } /** * Configure access to auxiliary data associated with model * FIXME public * @param streamProvider facility to keep model-associated data * @return <code>this</code> for convenience */ private GenerationFacade modelStreams(ModelStreamManager.Provider streamProvider) { myStreamProvider = streamProvider; return this; } /** * Generate single model. {@link GenerationFacade} instance can be reused then for other generation activities. * IMPORTANT: unlike {@link #process(ProgressMonitor, List)}, requires model write lock (on a repository of TransientModelsProvider) * as it needs to create and publish module with transient models. * @param monitor report progress/cancellation * @param model input * @return status object that describes generation outcome */ public GenerationStatus process(@NotNull final ProgressMonitor monitor, @NotNull SModel model) { final GeneratorTaskListener<GeneratorTask> originalListener = myTaskListener; final GenerationTaskRecorder<GeneratorTask> recorder = new GenerationTaskRecorder<>(originalListener); myTaskListener = recorder; try { final GeneratorTaskBase task = new GeneratorTaskBase(model); TransientModelsModule tm = myTransientModelsProvider.createModule(model.getModule().getModuleName()); myTransientModelsProvider.associate(task, tm); modelStreams(new ModelStreamProviderImpl()); process0(monitor, Collections.singletonList(task)); myTransientModelsProvider.publishAll(); return recorder.getRecorded(task); } finally { myTaskListener = originalListener; } } /** * Feed transformation process with sequence of task. Tasks are processed in the order given. If a task deals with a model * from a repository, calling code shall ensure respective read lock. * @param monitor report progress/cancellation * @param tasks models to generate */ public void process(@NotNull final ProgressMonitor monitor, @NotNull final List<? extends GeneratorTask> tasks) { modelStreams(new ModelStreamProviderImpl()); process0(monitor, tasks); } private void process0(@NotNull final ProgressMonitor monitor, @NotNull final List<? extends GeneratorTask> tasks) { myTransientModelsProvider.startGeneration(myGenerationOptions.getNumberOfModelsToKeep()); final GeneratorLoggerAdapter logger = new GeneratorLoggerAdapter(myMessageHandler, myGenerationOptions.isShowInfo(), myGenerationOptions.isShowWarnings()); GenControllerContext ctx = new GenControllerContext(myRepository, myGenerationOptions, myTransientModelsProvider, myStreamProvider); GeneratorTaskListener<GeneratorTask> taskListener; if (myTaskListener != null) { taskListener = myTaskListener; } else { taskListener = new EmptyTaskListener(); } final GenerationController gc = new GenerationController(tasks, ctx, taskListener, logger); gc.generate(monitor); } private static class EmptyTaskListener implements GeneratorTaskListener<GeneratorTask> { @Override public void start(@NotNull GeneratorTask task) { // no-op } @Override public void done(@NotNull GeneratorTask task, @NotNull GenerationStatus status) { // no-op } } }