ccb3a30ae3a78a6d764e0cac92cb1fb84ad91800
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateContext.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.template.impl;
18
19
20 import com.google.common.annotations.VisibleForTesting;
21 import com.intellij.codeInsight.template.TemplateContextType;
22 import com.intellij.openapi.util.WriteExternalException;
23 import com.intellij.util.containers.ContainerUtil;
24 import com.intellij.util.containers.JBIterable;
25 import org.jdom.Element;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.function.Function;
33 import java.util.stream.Collectors;
34
35 public class TemplateContext {
36   private final Map<String, Boolean> myContextStates = ContainerUtil.newTroveMap();
37
38   private static class ContextInterner {
39     private static final Map<String, String> internMap = Arrays.stream(TemplateContextType.EP_NAME.getExtensions())
40       .map(TemplateContextType::getContextId)
41       .distinct()
42       .collect(Collectors.toMap(Function.identity(), Function.identity()));
43   }
44
45   public TemplateContext createCopy()  {
46     TemplateContext cloneResult = new TemplateContext();
47     cloneResult.myContextStates.putAll(myContextStates);
48     return cloneResult;
49   }
50
51   @Nullable
52   TemplateContextType getDifference(@NotNull TemplateContext defaultContext) {
53     return ContainerUtil.find(TemplateManagerImpl.getAllContextTypes(), type -> isEnabled(type) != defaultContext.isEnabled(type));
54   }
55
56   public boolean isEnabled(@NotNull TemplateContextType contextType) {
57     synchronized (myContextStates) {
58       Boolean storedValue = getOwnValue(contextType);
59       if (storedValue == null) {
60         TemplateContextType baseContextType = contextType.getBaseContextType();
61         return baseContextType != null && isEnabled(baseContextType);
62       }
63       return storedValue.booleanValue();
64     }
65   }
66
67   @Nullable
68   public Boolean getOwnValue(TemplateContextType contextType) {
69     synchronized (myContextStates) {
70       return myContextStates.get(contextType.getContextId());
71     }
72   }
73
74   public void setEnabled(TemplateContextType contextType, boolean value) {
75     synchronized (myContextStates) {
76       myContextStates.put(contextType.getContextId(), value);
77     }
78   }
79
80   // used during initialization => no sync
81   @VisibleForTesting
82   public void setDefaultContext(@NotNull TemplateContext defContext) {
83     HashMap<String, Boolean> copy = new HashMap<>(myContextStates);
84     myContextStates.clear();
85     myContextStates.putAll(defContext.myContextStates);
86     myContextStates.putAll(copy);
87   }
88
89   // used during initialization => no sync
90   @VisibleForTesting
91   public void readTemplateContext(Element element) {
92     for (Element option : element.getChildren("option")) {
93       String name = option.getAttributeValue("name");
94       String value = option.getAttributeValue("value");
95       if (name != null && value != null) {
96         myContextStates.put(ContainerUtil.getOrElse(ContextInterner.internMap, name, name), Boolean.parseBoolean(value));
97       }
98     }
99
100     myContextStates.putAll(makeInheritanceExplicit());
101   }
102
103   /**
104    * Mark contexts explicitly as excluded which are excluded because some of their bases is explicitly marked as excluded.
105    * Otherwise that `excluded` status will be forgotten if the base context is enabled.
106    */
107   @NotNull
108   private Map<String, Boolean> makeInheritanceExplicit() {
109     Map<String, Boolean> explicitStates = ContainerUtil.newHashMap();
110     for (TemplateContextType type : ContainerUtil.filter(TemplateManagerImpl.getAllContextTypes(), this::isDisabledByInheritance)) {
111       explicitStates.put(type.getContextId(), false);
112     }
113     return explicitStates;
114   }
115
116   private boolean isDisabledByInheritance(TemplateContextType type) {
117     return !hasOwnValue(type) &&
118            !isEnabled(type) &&
119            JBIterable.generate(type, TemplateContextType::getBaseContextType).filter(this::hasOwnValue).first() != null;
120   }
121
122   private boolean hasOwnValue(TemplateContextType t) {
123     return getOwnValue(t) != null;
124   }
125
126   @VisibleForTesting
127   public void writeTemplateContext(Element element) throws WriteExternalException {
128     for (TemplateContextType type : TemplateManagerImpl.getAllContextTypes()) {
129       Boolean ownValue = getOwnValue(type);
130       if (ownValue != null) {
131         TemplateContextType base = type.getBaseContextType();
132         boolean baseEnabled = base != null && isEnabled(base);
133         if (ownValue != baseEnabled) {
134           Element optionElement = new Element("option");
135           optionElement.setAttribute("name", type.getContextId());
136           optionElement.setAttribute("value", ownValue.toString());
137           element.addContent(optionElement);
138         }
139       }
140     }
141   }
142
143   @Override
144   public String toString() {
145     return myContextStates.toString();
146   }
147 }