/* * 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.impl.plan; import jetbrains.mps.generator.runtime.TemplateMappingConfiguration; import jetbrains.mps.generator.runtime.TemplateMappingPriorityRule; import jetbrains.mps.generator.runtime.TemplateModel; import jetbrains.mps.generator.runtime.TemplateModule; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_AbstractRef; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_ExternalRef; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefAllGlobal; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefAllLocal; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_RefSet; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingConfig_SimpleRef; import jetbrains.mps.project.structure.modules.mappingpriorities.MappingPriorityRule; import jetbrains.mps.project.structure.modules.mappingpriorities.RuleType; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import jetbrains.mps.smodel.language.LanguageRuntime; import jetbrains.mps.util.Pair; import jetbrains.mps.util.containers.MultiMap; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.mps.openapi.language.SLanguage; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Igor Alshannikov * Date: Mar 27, 2007 */ public class GenerationPartitioner { private static final Logger LOG = LogManager.getLogger(GenerationPlan.class); // generators private final Collection<TemplateModule> myGenerators; // maps private final Map<SModuleReference, TemplateModule> myModulesMap; private final Map<SModelReference, TemplateModel> myModelMap; // record dependencies between generators explicitly established by priority rules // use these to avoid adding implicit rules ('not later' than target language) between generators with explicit rules private final Set<Pair<TemplateModule, TemplateModule>> myExplicitDependencies; // result private final PartitioningSolver mySolver; private final PriorityConflicts myConflicts; public GenerationPartitioner(Collection<TemplateModule> generators) { myGenerators = generators; myConflicts = new PriorityConflicts(generators); mySolver = new PartitioningSolver(myConflicts); myModulesMap = new HashMap<>(myGenerators.size()); myModelMap = new HashMap<>(); for (TemplateModule module : myGenerators) { myModulesMap.put(module.getModuleReference(), module); for (TemplateModel model : module.getModels()) { myModelMap.put(model.getSModelReference(), model); } } myExplicitDependencies = new HashSet<>(); } public List<List<TemplateMappingConfiguration>> createMappingSets() { ArrayList<TemplateMappingConfiguration> allMappingConfigurations = new ArrayList<>(); for (TemplateModule generator : myGenerators) { for (TemplateModel model : generator.getModels()) { allMappingConfigurations.addAll(model.getConfigurations()); } } mySolver.prepare(allMappingConfigurations); // get priority mapping rules from generators and build 'priority map' loadRules(); // solve final List<GenerationPhase> generationPhases = mySolver.solve(); if (LOG.isDebugEnabled()) { dump(generationPhases); } // return phaseAsPlainList(generationPhases); return phaseGroupedByGenerator(generationPhases); } private static void dump(Collection<GenerationPhase> generationPhases) { StringBuilder sb = new StringBuilder(); for (GenerationPhase gp : generationPhases) { sb.append("Phase\n"); for (Group g : gp.getGroups()) { sb.append('\t'); sb.append(g); sb.append('\n'); } } LOG.debug(sb.toString()); } static List<List<TemplateMappingConfiguration>> phaseAsPlainList(List<GenerationPhase> phases) { List<List<TemplateMappingConfiguration>> rv = new ArrayList<>(); for (GenerationPhase gp : phases) { rv.add(gp.getAllElements()); } return rv; } static List<List<TemplateMappingConfiguration>> phaseGroupedByGenerator(List<GenerationPhase> phases) { List<List<TemplateMappingConfiguration>> rv = new ArrayList<>(); for (GenerationPhase gp : phases) { for (Group g : gp.groupByGenerator()) { rv.add(new ArrayList<>(g.getElements())); } } return rv; } private void loadRules() { // read user-defined rules for (TemplateModule generator : myGenerators) { Collection<TemplateMappingPriorityRule> priorities = generator.getPriorities(); for (TemplateMappingPriorityRule rule : priorities) { processRule((MappingPriorityRule) rule, generator); } } // auxiliary rules to ensure generator runs no later than any generator of its target language MultiMap<SLanguage, TemplateModule> lang2gen = new MultiMap<>(); for (TemplateModule generator : myGenerators) { LanguageRuntime sLanguage = generator.getSourceLanguage(); SLanguage lang = MetaAdapterFactory.getLanguage(sLanguage.getId(),sLanguage.getNamespace()); lang2gen.putValue(lang, generator); } for (TemplateModule generator : myGenerators) { final RuleHelper lhsHelper = new RuleHelper(generator, new MappingConfig_RefAllLocal()); if (lhsHelper.getAllMappings().isEmpty()) { continue; } HashSet<TemplateModule> targetGenerators = new HashSet<>(); for (SLanguage targetLang : generator.getTargetLanguages()) { targetGenerators.addAll(lang2gen.get(targetLang)); } targetGenerators.remove(generator); if (targetGenerators.isEmpty()) { continue; } if (LOG.isDebugEnabled()) { List<String> l = targetGenerators.stream().map(g -> g.getModuleReference().getModuleName()).collect(Collectors.toList()); LOG.debug(String.format("Generator %s targets languages with generators %s", generator.getModuleReference().getModuleName(), l)); } // for each target generator, add a rule {all MC in the current generator} <= {all MC of target generator}, with respect to top-pri MC for (TemplateModule tg : targetGenerators) { if (myExplicitDependencies.contains(new Pair<>(generator, tg))) { continue; } RuleHelper rhsHelper = new RuleHelper(tg, new MappingConfig_RefAllLocal()); if (rhsHelper.getAllMappings().isEmpty()) { continue; } if (rhsHelper.getTopPriMappings().isEmpty()) { addImplicitTargetLanguageRule(lhsHelper.getAllMappings(), generator, rhsHelper.getAllMappings(), tg); } else { if (!lhsHelper.getTopPriMappings().isEmpty()) { // both lhs and rhs with top pri - add two distinct rules, one for top, another for regular addImplicitTargetLanguageRule(lhsHelper.getTopPriMappings(), generator, rhsHelper.getTopPriMappings(), tg); } // otherwise don't care, establish 'not later' for regular MC only (top are going to be handled within their own top group) if (!lhsHelper.getRegularMappings().isEmpty() && !rhsHelper.getRegularMappings().isEmpty()) { addImplicitTargetLanguageRule(lhsHelper.getRegularMappings(), generator, rhsHelper.getRegularMappings(), tg); } } } } } private void addImplicitTargetLanguageRule(Collection<TemplateMappingConfiguration> lhs, TemplateModule generator1, Collection<TemplateMappingConfiguration> rhs, TemplateModule generator2) { MappingPriorityRule rule = new MappingPriorityRule(); rule.setLeft(createRefs(generator1, lhs)); rule.setRight(createRefs(generator2, rhs)); rule.setType(RuleType.BEFORE_OR_TOGETHER); if (LOG.isDebugEnabled()) { LOG.debug(String.format(" Implicit rule added between %s and %s:", generator1.getAlias(), generator2.getAlias())); List<String> lhString = lhs.stream().map(TemplateMappingConfiguration::getName).collect(Collectors.toList()); List<String> rhString = rhs.stream().map(TemplateMappingConfiguration::getName).collect(Collectors.toList()); LOG.debug(String.format(" %s <= %s", lhString, rhString)); } processRule(rule, generator1); } // XXX likely there's similar code in UI and/or TemplateUtil, need to check/refactor private static MappingConfig_AbstractRef createRefs(TemplateModule generator, Collection<TemplateMappingConfiguration> cfgs) { final MappingConfig_ExternalRef ext = new MappingConfig_ExternalRef(); ext.setGenerator(generator.getModuleReference()); final MappingConfig_RefSet set = new MappingConfig_RefSet(); ext.setMappingConfig(set); for (TemplateMappingConfiguration mc : cfgs) { final MappingConfig_SimpleRef e = new MappingConfig_SimpleRef(); e.setModelUID(mc.getMappingNode().getModelReference().toString()); e.setNodeID(mc.getMappingNode().getNodeId().toString()); set.getMappingConfigs().add(e); } return ext; } private void processRule(MappingPriorityRule rule, TemplateModule generator) { MappingConfig_AbstractRef left = rule.getLeft(); MappingConfig_AbstractRef right = rule.getRight(); if (left == null || right == null) return; final RuleHelper lhsHelper = new RuleHelper(generator, left); final RuleHelper rhsHelper = new RuleHelper(generator, right); Collection<TemplateMappingConfiguration> lhs = lhsHelper.getAllMappings(); Collection<TemplateMappingConfiguration> rhs = rhsHelper.getAllMappings(); if (lhs.isEmpty() || rhs.isEmpty()) { final String lang = generator.getSourceLanguage().getNamespace(); if (lhs.isEmpty() && rhs.isEmpty()) { final String msg = String.format("Generator for language %s defines priority rule %s, both sides of the rule miss mapping configuration. The rule doesn't affect the generation and is ignored.", lang, rule); myConflicts.registerInvalid(generator.getModuleReference(), msg, rule); } else { final String msg = String.format("Generator for language %s defines invalid priority rule %s, with no mapping configurations specified at one side. The rule is ignored.", lang, rule); myConflicts.registerInvalid(generator.getModuleReference(), msg, rule); } return; } for (TemplateModule l : lhsHelper.getInvolvedGenerators()) { for (TemplateModule r : rhsHelper.getInvolvedGenerators()) { myExplicitDependencies.add(new Pair<>(l, r)); } } switch (rule.getType()) { case STRICTLY_TOGETHER: Set<TemplateMappingConfiguration> coherentMappings = new HashSet<>(rhs); coherentMappings.addAll(lhs); mySolver.registerCoherent(coherentMappings, rule); return; case STRICTLY_BEFORE: case BEFORE_OR_TOGETHER: mySolver.establishDependency(lhs, rhs, rule); return; case AFTER_OR_TOGETHER: case STRICTLY_AFTER: mySolver.establishDependency(rhs, lhs, rule); return; default: throw new IllegalStateException(String.valueOf(rule.getType())); } } private class RuleHelper { private final TemplateModule myGenerator; private final MappingConfig_AbstractRef myInitialRef; private Collection<TemplateMappingConfiguration> myMapConfigs; public RuleHelper(TemplateModule generator, MappingConfig_AbstractRef mcRef) { myGenerator = generator; myInitialRef = mcRef; } public Collection<TemplateMappingConfiguration> getAllMappings() { build(); return myMapConfigs; } public Collection<TemplateMappingConfiguration> getTopPriMappings() { ArrayList<TemplateMappingConfiguration> rv = new ArrayList<>(); for (TemplateMappingConfiguration mc : getAllMappings()) { if (mc.isTopPriority()) { rv.add(mc); } } return rv; } public Collection<TemplateMappingConfiguration> getRegularMappings() { ArrayList<TemplateMappingConfiguration> rv = new ArrayList<>(); for (TemplateMappingConfiguration mc : getAllMappings()) { if (!mc.isTopPriority()) { rv.add(mc); } } return rv; } public Collection<TemplateModule> getInvolvedGenerators() { HashSet<TemplateModule> rv = new HashSet<>(); for (TemplateMappingConfiguration mc : getAllMappings()) { rv.add(mc.getModel().getModule()); } return rv; } private void build() { if (myMapConfigs == null) { myMapConfigs = getMappingsFromRef(myInitialRef, myGenerator); } } private Collection<TemplateMappingConfiguration> getMappingsFromRef(MappingConfig_AbstractRef mappingRef, TemplateModule refGenerator) { if (mappingRef instanceof MappingConfig_RefAllGlobal) { return new ArrayList<>(mySolver.getKnownMapConfigs()); } if (mappingRef instanceof MappingConfig_RefAllLocal) { List<TemplateMappingConfiguration> mappingConf = new ArrayList<>(); for (TemplateModel templateModel : refGenerator.getModels()) { for (TemplateMappingConfiguration n : templateModel.getConfigurations()) { mappingConf.add(n); } } return mappingConf; } if (mappingRef instanceof MappingConfig_RefSet) { List<TemplateMappingConfiguration> result = new ArrayList<>(); MappingConfig_RefSet refSet = ((MappingConfig_RefSet) mappingRef); for (MappingConfig_AbstractRef simpleRef : refSet.getMappingConfigs()) { result.addAll(getMappingsFromRef(simpleRef, refGenerator)); } return result; } if (mappingRef instanceof MappingConfig_ExternalRef) { SModuleReference generatorRef = ((MappingConfig_ExternalRef) mappingRef).getGenerator(); if (generatorRef != null) { TemplateModule newRefGenerator = myModulesMap.get(generatorRef); if (newRefGenerator != null) { return getMappingsFromRef(((MappingConfig_ExternalRef) mappingRef).getMappingConfig(), newRefGenerator); } else { // generator is not in the plan - just ignore // LOG.error("couldn't get generator by uid: '" + generatorRef + "'"); } } return Collections.emptyList(); } if (mappingRef instanceof MappingConfig_SimpleRef) { MappingConfig_SimpleRef simpleRef = (MappingConfig_SimpleRef) mappingRef; String modelUID = simpleRef.getModelUID(); String nodeID = simpleRef.getNodeID(); if (modelUID != null && nodeID != null) { SModelReference reference = PersistenceFacade.getInstance().createModelReference(modelUID); TemplateModel refModel = myModelMap.get(reference); if (refModel != null) { if (simpleRef.includesAll()) { return refModel.getConfigurations(); } else { SNodeReference node = new jetbrains.mps.smodel.SNodePointer(reference, PersistenceFacade.getInstance().createNodeId(nodeID)); for (TemplateMappingConfiguration m : refModel.getConfigurations()) { if (node.equals(m.getMappingNode())) { return Collections.singletonList(m); } } LOG.warn( "couldn't get node by id: '" + nodeID + "' in model " + modelUID + " while loading priority rules for generator: " + myGenerator.getAlias()); } } else { LOG.warn( "couldn't get model by uid: '" + modelUID + "' in generator " + refGenerator.getAlias() + " while loading priority rules for generator: " + myGenerator.getAlias()); } } return Collections.emptyList(); } return Collections.emptyList(); } } public PriorityConflicts getConflictingPriorityRules() { return myConflicts; } }