/* * 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.generator.impl.ExportsSessionContext; import jetbrains.mps.generator.impl.GenControllerContext; import jetbrains.mps.generator.impl.GenerationSessionLogger; import jetbrains.mps.generator.impl.RoleValidation; import jetbrains.mps.generator.impl.plan.CrossModelEnvironment; import jetbrains.mps.generator.template.ITemplateGenerator; import jetbrains.mps.project.Project; import jetbrains.mps.project.ProjectRepository; import jetbrains.mps.project.StandaloneMPSContext; import jetbrains.mps.smodel.SNodePointer; import jetbrains.mps.smodel.SNodeUtil; import jetbrains.mps.util.IterableUtil; import jetbrains.mps.util.annotation.ToRemove; import jetbrains.mps.util.containers.ConcurrentHashSet; import jetbrains.mps.util.performance.IPerformanceTracer; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SModel; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SRepository; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Available from {@link ITemplateGenerator#getGeneratorSessionContext()}. * Generally shall not get exposed in generated code, however there are templates that cast genContext to TemplateQueryContext and access ITemplateGenerator * from there, with subsequent access to this class (e.g. bl.java.closures - those under BL, not that standalone). * Igor Alshannikov * Sep 19, 2005 */ public class GenerationSessionContext extends StandaloneMPSContext { private final Object COPIED_ROOTS = new Object(); private final SModel myOriginalInputModel; private final GenControllerContext myEnvironment; private final TransientModelsModule myTransientModule; private final GenerationSessionLogger myLogger; private final RoleValidation myValidation; /* * GenerationSessionContext is not the perfect place for this tracer, as it's not really session object, * however it's per-model object, and generation session is per-model as well (thus, can't put it into e.g. GenControllerContext) */ private final IPerformanceTracer myPerfTrace; private final Object NULL_OBJECT = new Object(); /** * Transient objects survive micro-step only */ private final Map<Object, Object> myTransientObjects; /** * Step objects survive survive major step */ private final Map<Object, Object> myStepObjects; /** * Session objects survive complete generation session for the given model */ private final Map<Object, Object> mySessionObjects; private final ExportsSessionContext myExportsSession; // these objects survive through all steps of generation private final ConcurrentMap<SNodeReference, Set<String>> myUsedNames; private final SNodeReference myFakeNameTopContextNode = new SNodePointer((SModelReference) null, null); private final Map<SNode, String> topToSuffix = new ConcurrentHashMap<SNode, String>(); public GenerationSessionContext(GenControllerContext environment, TransientModelsModule transientModule, GenerationSessionLogger logger, SModel inputModel, IPerformanceTracer performanceTracer) { myEnvironment = environment; myTransientModule = transientModule; myOriginalInputModel = inputModel; myPerfTrace = performanceTracer; myLogger = logger; myValidation = new RoleValidation(environment.getOptions().isShowBadChildWarning()); myExportsSession = new ExportsSessionContext(environment.getExportModels(), this); mySessionObjects = new ConcurrentHashMap<Object, Object>(); myTransientObjects = new ConcurrentHashMap<Object, Object>(); myStepObjects = new ConcurrentHashMap<Object, Object>(); myUsedNames = new ConcurrentHashMap<SNodeReference, Set<String>>(); } /** * copy cons for each major step. Nothing but an odd way to clear step and transient objects */ public GenerationSessionContext(@NotNull GenerationSessionContext prevContext) { myEnvironment = prevContext.myEnvironment; myTransientModule = prevContext.myTransientModule; myOriginalInputModel = prevContext.myOriginalInputModel; myPerfTrace = prevContext.myPerfTrace; myLogger = prevContext.myLogger; mySessionObjects = prevContext.mySessionObjects; myUsedNames = prevContext.myUsedNames; myValidation = prevContext.myValidation; myExportsSession = prevContext.myExportsSession; // this copy cons indicate new major step, hence new empty maps myTransientObjects = new ConcurrentHashMap<Object, Object>(); myStepObjects = new ConcurrentHashMap<Object, Object>(); } public void clearTransientObjects() { myTransientObjects.clear(); } public SModel getOriginalInputModel() { return myOriginalInputModel; } @Override @NotNull public TransientModelsModule getModule() { return myTransientModule; } @Override @ToRemove(version = 3.3) public Project getProject() { // XXX still in use in mbeddr (through IOperationContext) SRepository repository = myOriginalInputModel.getModule().getRepository(); if (!(repository instanceof ProjectRepository)) { repository = myEnvironment.getRepository(); } assert repository instanceof ProjectRepository; return ((ProjectRepository) repository).getProject(); } /** * @deprecated Transitional access to SRepository. Likely, the code that needs a repository shall get refactored * not to use one. Contract is questionable, provided we head towards distinct repository for transient models. * For the time being, this repository corresponds to generator's environment, e.g. project it's run from. */ @Deprecated public SRepository getRepository() { return myEnvironment.getRepository(); } public String toString() { return String.format("%s: generating %s in", getClass().getSimpleName(), myOriginalInputModel.getName()); } public void putTransientObject(Object key, Object o) { myTransientObjects.put(key, o != null ? o : NULL_OBJECT); } public Object getTransientObject(Object key) { Object result = myTransientObjects.get(key); return result == NULL_OBJECT ? null : result; } public void putStepObject(Object key, Object o) { myStepObjects.put(key, o != null ? o : NULL_OBJECT); } public Object getStepObject(Object key) { Object result = myStepObjects.get(key); return result == NULL_OBJECT ? null : result; } public void putSessionObject(Object key, Object o) { mySessionObjects.put(key, o != null ? o : NULL_OBJECT); } public Object getSessionObject(Object key) { Object result = mySessionObjects.get(key); return result == NULL_OBJECT ? null : result; } public ExportsSessionContext getExports() { return myExportsSession; } private static void appendNodeUniqueId(SNode node, StringBuilder sb) { SNode parent = node.getParent(); boolean sym = true; while (parent != null) { int index = IterableUtil.asList(parent.getChildren(node.getContainmentLink())).indexOf(node); if (index == 0) { sb.append(sym ? 'a' : '0'); } while (index > 0) { int curr = sym ? 'a' + (index % 26) : '0' + (index % 10); index /= sym ? 26 : 10; sb.appendCodePoint(curr); } sym = !sym; node = parent; parent = node.getParent(); } } public String createUniqueName(String roughName, SNode contextNode, SNode inputNode) { StringBuilder uniqueNameBuffer = new StringBuilder(50); uniqueNameBuffer.append(roughName); if (roughName.length() > 0 && roughName.charAt(roughName.length()-1) == '_') { uniqueNameBuffer.setLength(roughName.length()-1); } if (contextNode != null) { // find topmost 'named' ancestor SNode topmostNamed = null; SNode node_ = contextNode; while (node_ != null) { if (node_.getConcept().isSubConceptOf(SNodeUtil.concept_INamedConcept)) { topmostNamed = node_; } node_ = node_.getParent(); } if (topmostNamed != null) { String suffix = topToSuffix.get(topmostNamed); if (suffix != null) { uniqueNameBuffer.append('_'); uniqueNameBuffer.append(suffix); } else { String name = topmostNamed.getName(); if (name != null) { // In fact, ("v2".hashCode >>> 1) == ("v3".hashCode >>> 1) and "unique" names // in two distinct roots (and distinct contextNode) happen to share top suffix. // When such two roots were generated in parallel, unique names weren't truly unique, and got mixed up. // Can't simply get rid of >>>1 as it would require rebuilding all the models where unique names were // used (i.e. almost every model out there). suffix = Integer.toString(name.hashCode() >>> 1, Character.MAX_RADIX); topToSuffix.put(topmostNamed, suffix); uniqueNameBuffer.append('_'); uniqueNameBuffer.append(suffix); } } contextNode = topmostNamed; } } // if(contextNode != null) if (inputNode != null) { uniqueNameBuffer.append('_'); appendNodeUniqueId(inputNode, uniqueNameBuffer); } final boolean suffixAdded = roughName.length() < uniqueNameBuffer.length(); String uniqueName = uniqueNameBuffer.toString(); final Set<String> usedNames = getUsedNames(contextNode); if (suffixAdded && usedNames.add(uniqueName)) { return uniqueName; } // assumption: !suffixAdded || usedNames.contains(uniqueName); uniqueNameBuffer.append('_'); final int trimPos = uniqueNameBuffer.length(); for (int count = 0; ; count++) { uniqueNameBuffer.append(count); uniqueName = uniqueNameBuffer.toString(); if (usedNames.add(uniqueName)) { break; } uniqueNameBuffer.setLength(trimPos); } return uniqueName; } /** * names are unique within given context, not globally in the session */ private Set<String> getUsedNames(SNode contextNode) { SNodeReference key = contextNode == null ? myFakeNameTopContextNode : contextNode.getReference(); Set<String> rv = myUsedNames.putIfAbsent(key, new ConcurrentHashSet<String>()); return rv == null ? myUsedNames.get(key) : rv; } public void clearCopiedRootsSet() { Set<SNode> set = getCopiedRoots(false); if (set != null) { set.clear(); } } public void registerCopiedRoot(SNode outputRootNode) { getCopiedRoots(true).add(outputRootNode); } public boolean isCopiedRoot(SNode inputNode) { Set<SNode> set = getCopiedRoots(false); return set != null && set.contains(inputNode); } private Set<SNode> getCopiedRoots(boolean create) { @SuppressWarnings("unchecked") Set<SNode> set = (Set<SNode>) getStepObject(COPIED_ROOTS); if (set == null && create) { putStepObject(COPIED_ROOTS, set = new HashSet<SNode>()); } return set; } public Object getGenerationParameter(String name) { if (getGenerationOptions().getParametersProvider() != null) { final Map<String, Object> parameters = getGenerationOptions().getParametersProvider().getParameters(myOriginalInputModel); return parameters == null ? null : parameters.get(name); } return null; } public GenerationOptions getGenerationOptions() { return myEnvironment.getOptions(); } /** * @return never <code>null</code> */ public IGeneratorLogger getLogger() { return myLogger; } /** * @return never <code>null</code> */ public RoleValidation getRoleValidationFacility() { // XXX in fact, GenerationSessionContext seems to serve as an API (resides in public package and provides public services // to genContext, like unique name), while RoleValidation is implementation class. // However, don't want to refactor GSC now (split iface and impl) - there's e.g. GenerationPlan (impl class) exposed here as well, so it doesn't // look like that intention was to keep it API, rather a facility to keep everything handy. return myValidation; } /** * @return never <code>null</code> */ public CrossModelEnvironment getCrossModelEnvironment() { // same considerations applies as for #getRoleValidationFacility() above, need a distinct implementation context for TG (could use StepArguments, perhaps). return myEnvironment.getCrossModelEnvironment(); } public IPerformanceTracer getPerformanceTracer() { return myPerfTrace; } }