/* * Copyright 2003-2016 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.text; import jetbrains.mps.messages.IMessage; import jetbrains.mps.messages.IMessageHandler; import jetbrains.mps.messages.Message; import jetbrains.mps.messages.MessageKind; import jetbrains.mps.smodel.CopyUtil; import jetbrains.mps.smodel.ModelReadRunnable; import jetbrains.mps.smodel.SModelId; import jetbrains.mps.smodel.SModelReference; import jetbrains.mps.smodel.SnapshotModelData; import jetbrains.mps.smodel.TrivialModelDescriptor; import jetbrains.mps.text.TextUnit.Status; import jetbrains.mps.text.impl.ModelOutline; import jetbrains.mps.text.impl.RegularTextUnit; import jetbrains.mps.text.impl.TextGenRegistry; import jetbrains.mps.text.rt.TextGenAspectBase; import jetbrains.mps.text.rt.TextGenAspectDescriptor; import jetbrains.mps.util.NamedThreadFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.module.ModelAccess; import java.util.Collection; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicInteger; /** * Facility to run parallel text generation for text units given model breaks down to. * <p/> * For the time being, text units correspond to model root nodes, the way it used to be in the past MPS history. * Pending API changes to bring control over text units to user and j.m.lang.textgen. * <p/> * Re-work of what used to be jetbrains.mps.generator.textGen.TextGeneratorEngine * @author Artem Tikhomirov * @since 3.3 */ public final class TextGeneratorEngine { private final ExecutorService myExecutor; private final IMessageHandler myMessages; public TextGeneratorEngine(@NotNull IMessageHandler messageHandler) { myMessages = messageHandler; // availableProcessors()*2 ? myExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("textgen-thread-")); } public void shutdown() { myExecutor.shutdown(); } /** * Contract: same as {@link #schedule(SModel, BlockingQueue)} * @param model input for model-to-text transformation * @return future result, use {@link Future#get()} to retrieve */ public Future<TextGenResult> generateText(@NotNull final SModel model) { final ArrayBlockingQueue<TextGenResult> queue = new ArrayBlockingQueue<TextGenResult>(1); schedule(model, queue); return new FutureTask<TextGenResult>(new Callable<TextGenResult>() { @Override public TextGenResult call() throws Exception { return queue.take(); } }); } /** * requires read access * Contract: for each model there'd be a TextGenResult instance in the queue (unless interrupted) * <p/> * might add schedule(SModel):Future<Result> (one more async alternative) and generate(SModel):Result (synchronous alternative) * @param model model to produce text for */ public void schedule(@NotNull final SModel model, @NotNull final BlockingQueue<TextGenResult> resultQueue) { final List<TextUnit> textUnits = breakdownToUnits(model); if (textUnits.size() == 0) { resultQueue.offer(new TextGenResult(model, textUnits)); } final ModelAccess modelAccess = model.getRepository() != null ? model.getRepository().getModelAccess() : null; final AtomicInteger unitsCount = new AtomicInteger(textUnits.size()); for (final TextUnit tu : textUnits) { final Runnable tuGenerate = new Runnable() { @Override public void run() { try { // XXX shall pass settings, e.g. needDebug, IMessageHandler, etc. try { tu.generate(); } finally { if (tu instanceof RegularTextUnit) { // even if there's exception, report messages first, as they 'happen-before' the error. for (IMessage msg : ((RegularTextUnit) tu).getMessages()) { myMessages.handle(msg); } } // once the last unit of the model is completed (either failed or succeeded), notify consumer if (unitsCount.decrementAndGet() == 0) { try { resultQueue.put(new TextGenResult(model, textUnits)); } catch (InterruptedException ex) { // it's ok, it's likely caller to stop the queue, thus it knows how to deal with incomplete state myMessages.handle(new Message(MessageKind.WARNING, String.format("TextGen interrupted for model %s", model.getName())).setException(ex)); } } } } catch (Throwable ex) { myMessages.handle(new Message(MessageKind.ERROR, String.format("TextGen threw an exception for model %s", model.getName())).setException(ex)); } } }; myExecutor.execute(modelAccess == null ? tuGenerate : new ModelReadRunnable(modelAccess, tuGenerate)); } } private static List<TextUnit> breakdownToUnits(@NotNull SModel model) { Collection<TextGenAspectDescriptor> tgad = TextGenRegistry.getInstance().getAspects(model); ModelOutline rv = new ModelOutline(model); for (TextGenAspectDescriptor d : tgad) { if (d instanceof TextGenAspectBase) { ((TextGenAspectBase) d).breakdownToUnits(rv); } } return rv.getUnits(); } /** * PROVISIONAL API INTENDED TO REPLACE TextGen.generateText(). DO NOT USE OUTSIDE OF MPS. * FIXME need better API to deal with outputs other than text * Assumes at least read access to node's repository * @param node * @return either character data of the outcome, or an error message */ public static String generateText(SNode node) { // First, try as single root of a model, to give a chance for file descriptors to populate text layout // then, if fail, use the node directly. // FIXME perhaps, TextGenModelOutline deserves a refactoring to tell sequence of SNode instead of SModel? // we could pass original node then, without need to make a copy in a distinct model final SnapshotModelData modelData = new SnapshotModelData(new SModelReference(null, SModelId.generate(), "textgen")); modelData.addRootNode(CopyUtil.copyAndPreserveId(node)); TrivialModelDescriptor model = new TrivialModelDescriptor(modelData); final List<TextUnit> textUnits = breakdownToUnits(model); final TextUnit textUnit; if (textUnits.size() == 1) { textUnit = textUnits.get(0); } else { textUnit = new RegularTextUnit(node, "dummy.txt", null); } textUnit.generate(); if (textUnit.getState() == Status.Empty) { return ""; } if (textUnit.getState() == Status.Generated) { return new String(textUnit.getBytes(), textUnit.getEncoding()); } return "Failed to generate text for node " + node; } }