default injector performance
[fedora-idea.git] / plugins / IntelliLang / src / org / intellij / plugins / intelliLang / Configuration.java
blob274f3d0a8544b3eb77b701abbf7941322f26d637
1 /*
2 * Copyright 2006 Sascha Weinreuter
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.intellij.plugins.intelliLang;
18 import com.intellij.openapi.command.UndoConfirmationPolicy;
19 import com.intellij.openapi.command.WriteCommandAction;
20 import com.intellij.openapi.command.undo.DocumentReference;
21 import com.intellij.openapi.command.undo.UndoManager;
22 import com.intellij.openapi.command.undo.UndoableAction;
23 import com.intellij.openapi.command.undo.UnexpectedUndoException;
24 import com.intellij.openapi.components.*;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.project.ProjectManager;
29 import com.intellij.openapi.util.*;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.psi.PsiCompiledElement;
32 import com.intellij.psi.PsiElement;
33 import com.intellij.psi.PsiFile;
34 import com.intellij.psi.PsiLanguageInjectionHost;
35 import com.intellij.util.FileContentUtil;
36 import com.intellij.util.NullableFunction;
37 import com.intellij.util.PairProcessor;
38 import com.intellij.util.containers.ConcurrentFactoryMap;
39 import com.intellij.util.containers.ContainerUtil;
40 import gnu.trove.THashMap;
41 import org.intellij.plugins.intelliLang.inject.InjectorUtils;
42 import org.intellij.plugins.intelliLang.inject.LanguageInjectionSupport;
43 import org.intellij.plugins.intelliLang.inject.config.*;
44 import org.jdom.Document;
45 import org.jdom.Element;
46 import org.jdom.JDOMException;
47 import org.jetbrains.annotations.NonNls;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.util.*;
54 import java.util.concurrent.CopyOnWriteArrayList;
56 /**
57 * Configuration that holds configured xml tag, attribute and method parameter
58 * injection settings as well as the annotations to use for injection, pattern
59 * validation and for substituting non-compile time constant expression.
61 @State(
62 name = Configuration.COMPONENT_NAME,
63 storages = {@Storage(id = "dir", file = "$APP_CONFIG$/IntelliLang.xml", scheme = StorageScheme.DIRECTORY_BASED)})
64 public final class Configuration implements PersistentStateComponent<Element> {
65 static final Logger LOG = Logger.getInstance(Configuration.class.getName());
67 @NonNls public static final String COMPONENT_NAME = "LanguageInjectionConfiguration";
69 // element names
70 @NonNls private static final String TAG_INJECTION_NAME = "TAGS";
71 @NonNls private static final String ATTRIBUTE_INJECTION_NAME = "ATTRIBUTES";
72 @NonNls private static final String PARAMETER_INJECTION_NAME = "PARAMETERS";
73 @NonNls private static final String INSTRUMENTATION_TYPE_NAME = "INSTRUMENTATION";
74 @NonNls private static final String LANGUAGE_ANNOTATION_NAME = "LANGUAGE_ANNOTATION";
75 @NonNls private static final String PATTERN_ANNOTATION_NAME = "PATTERN_ANNOTATION";
76 @NonNls private static final String SUBST_ANNOTATION_NAME = "SUBST_ANNOTATION";
77 @NonNls private static final String ENTRY_NAME = "entry";
78 @NonNls private static final String RESOLVE_REFERENCES = "RESOLVE_REFERENCES";
80 private final Map<String, List<BaseInjection>> myInjections = new ConcurrentFactoryMap<String, List<BaseInjection>>() {
81 @Override
82 protected List<BaseInjection> create(final String key) {
83 return new CopyOnWriteArrayList<BaseInjection>();
87 // runtime pattern validation instrumentation
88 @NotNull private InstrumentationType myInstrumentationType = InstrumentationType.ASSERT;
90 // annotation class names
91 @NotNull private String myLanguageAnnotation;
92 @NotNull private String myPatternAnnotation;
93 @NotNull private String mySubstAnnotation;
95 private boolean myResolveReferences;
97 // cached annotation name pairs
98 private Pair<String, ? extends Set<String>> myLanguageAnnotationPair;
99 private Pair<String, ? extends Set<String>> myPatternAnnotationPair;
100 private Pair<String, ? extends Set<String>> mySubstAnnotationPair;
102 private volatile long myModificationCount;
104 public Configuration() {
105 setResolveReferences(true);
106 setLanguageAnnotation("org.intellij.lang.annotations.Language");
107 setPatternAnnotation("org.intellij.lang.annotations.Pattern");
108 setSubstAnnotation("org.intellij.lang.annotations.Subst");
111 public void loadState(final Element element) {
112 loadState(element, true);
115 public void loadState(final Element element, final boolean mergeWithOriginalAndCompile) {
116 final THashMap<String, LanguageInjectionSupport> supports = new THashMap<String, LanguageInjectionSupport>();
117 for (LanguageInjectionSupport support : Extensions.getExtensions(LanguageInjectionSupport.EP_NAME)) {
118 supports.put(support.getId(), support);
120 myInjections.get(LanguageInjectionSupport.XML_SUPPORT_ID).addAll(readExternal(element.getChild(TAG_INJECTION_NAME), new Factory<XmlTagInjection>() {
121 public XmlTagInjection create() {
122 return new XmlTagInjection();
124 }));
125 myInjections.get(LanguageInjectionSupport.XML_SUPPORT_ID).addAll(readExternal(element.getChild(ATTRIBUTE_INJECTION_NAME), new Factory<XmlAttributeInjection>() {
126 public XmlAttributeInjection create() {
127 return new XmlAttributeInjection();
129 }));
130 myInjections.get(LanguageInjectionSupport.JAVA_SUPPORT_ID).addAll(readExternal(element.getChild(PARAMETER_INJECTION_NAME), new Factory<MethodParameterInjection>() {
131 public MethodParameterInjection create() {
132 return new MethodParameterInjection();
134 }));
135 for (Element child : (List<Element>)element.getChildren("injection")){
136 final String key = child.getAttributeValue("injector-id");
137 final LanguageInjectionSupport support = supports.get(key);
138 final BaseInjection injection = support == null ? new BaseInjection(key) : support.createInjection(child);
139 injection.loadState(child);
140 myInjections.get(key).add(injection);
142 setInstrumentationType(JDOMExternalizerUtil.readField(element, INSTRUMENTATION_TYPE_NAME));
143 setLanguageAnnotation(JDOMExternalizerUtil.readField(element, LANGUAGE_ANNOTATION_NAME));
144 setPatternAnnotation(JDOMExternalizerUtil.readField(element, PATTERN_ANNOTATION_NAME));
145 setSubstAnnotation(JDOMExternalizerUtil.readField(element, SUBST_ANNOTATION_NAME));
146 final String resolveReferences = JDOMExternalizerUtil.readField(element, RESOLVE_REFERENCES);
147 setResolveReferences(resolveReferences == null || Boolean.parseBoolean(resolveReferences));
149 if (mergeWithOriginalAndCompile) {
150 mergeWithDefaultConfiguration();
152 for (String supportId : InjectorUtils.getActiveInjectionSupportIds()) {
153 for (BaseInjection injection : getInjections(supportId)) {
154 injection.initializePlaces(true);
160 private void mergeWithDefaultConfiguration() {
161 try {
162 final Configuration cfg = load(getClass().getClassLoader().getResourceAsStream("/" + COMPONENT_NAME + ".xml"));
163 if (cfg == null) return; // very strange
164 importFrom(cfg);
166 catch (Exception e) {
167 LOG.warn(e);
171 public Element getState() {
172 final Element element = new Element(COMPONENT_NAME);
174 JDOMExternalizerUtil.writeField(element, INSTRUMENTATION_TYPE_NAME, myInstrumentationType.toString());
175 JDOMExternalizerUtil.writeField(element, LANGUAGE_ANNOTATION_NAME, myLanguageAnnotation);
176 JDOMExternalizerUtil.writeField(element, PATTERN_ANNOTATION_NAME, myPatternAnnotation);
177 JDOMExternalizerUtil.writeField(element, SUBST_ANNOTATION_NAME, mySubstAnnotation);
178 JDOMExternalizerUtil.writeField(element, RESOLVE_REFERENCES, String.valueOf(myResolveReferences));
180 final List<String> injectoIds = new ArrayList<String>(myInjections.keySet());
181 Collections.sort(injectoIds);
182 for (String key : injectoIds) {
183 final List<BaseInjection> injections = new ArrayList<BaseInjection>(myInjections.get(key));
184 Collections.sort(injections, new Comparator<BaseInjection>() {
185 public int compare(final BaseInjection o1, final BaseInjection o2) {
186 return Comparing.compare(o1.getDisplayName(), o2.getDisplayName());
189 for (BaseInjection injection : injections) {
190 element.addContent(injection.getState());
193 return element;
196 @SuppressWarnings({"unchecked"})
197 private static <T extends BaseInjection> List<T> readExternal(Element element, Factory<T> factory) {
198 final List<T> injections = new ArrayList<T>();
199 if (element != null) {
200 final List<Element> list = element.getChildren(ENTRY_NAME);
201 for (Element entry : list) {
202 final T o = factory.create();
203 o.loadState(entry);
204 injections.add(o);
207 return injections;
210 public static Configuration getInstance() {
211 return ServiceManager.getService(Configuration.class);
214 public String getLanguageAnnotationClass() {
215 return myLanguageAnnotation;
218 public String getPatternAnnotationClass() {
219 return myPatternAnnotation;
222 public String getSubstAnnotationClass() {
223 return mySubstAnnotation;
226 public void setInstrumentationType(@Nullable String type) {
227 if (type != null) {
228 setInstrumentationType(InstrumentationType.valueOf(type));
232 public void setInstrumentationType(@NotNull InstrumentationType type) {
233 myInstrumentationType = type;
236 public void setLanguageAnnotation(@Nullable String languageAnnotation) {
237 if (languageAnnotation == null) return;
238 myLanguageAnnotation = languageAnnotation;
239 myLanguageAnnotationPair = Pair.create(languageAnnotation, Collections.singleton(languageAnnotation));
242 public Pair<String, ? extends Set<String>> getLanguageAnnotationPair() {
243 return myLanguageAnnotationPair;
246 public void setPatternAnnotation(@Nullable String patternAnnotation) {
247 if (patternAnnotation == null) return;
248 myPatternAnnotation = patternAnnotation;
249 myPatternAnnotationPair = Pair.create(patternAnnotation, Collections.singleton(patternAnnotation));
252 public Pair<String, ? extends Set<String>> getPatternAnnotationPair() {
253 return myPatternAnnotationPair;
256 public void setSubstAnnotation(@Nullable String substAnnotation) {
257 if (substAnnotation == null) return;
258 mySubstAnnotation = substAnnotation;
259 mySubstAnnotationPair = Pair.create(substAnnotation, Collections.singleton(substAnnotation));
262 public Pair<String, ? extends Set<String>> getSubstAnnotationPair() {
263 return mySubstAnnotationPair;
266 @Nullable
267 public static Configuration load(final InputStream is) throws IOException, JDOMException, InvalidDataException {
268 try {
269 final Document document = JDOMUtil.loadDocument(is);
270 final ArrayList<Element> elements = new ArrayList<Element>();
271 elements.add(document.getRootElement());
272 elements.addAll(document.getRootElement().getChildren("component"));
273 final Element element = ContainerUtil.find(elements, new Condition<Element>() {
274 public boolean value(final Element element) {
275 return "component".equals(element.getName()) && COMPONENT_NAME.equals(element.getAttributeValue("name"));
278 if (element != null) {
279 final Configuration cfg = new Configuration();
280 cfg.loadState(element, false);
281 return cfg;
283 return null;
285 finally {
286 is.close();
291 * Import from another configuration (e.g. imported file). Returns the number of imported items.
293 public int importFrom(final Configuration cfg) {
294 int n = 0;
295 for (String supportId : InjectorUtils.getActiveInjectionSupportIds()) {
296 final List<BaseInjection> mineInjections = myInjections.get(supportId);
297 for (BaseInjection other : cfg.getInjections(supportId)) {
298 final BaseInjection existing = findExistingInjection(other);
299 if (existing == null) {
300 n ++;
301 mineInjections.add(other);
303 else {
304 if (existing.equals(other)) continue;
305 boolean placesAdded = false;
306 for (InjectionPlace place : other.getInjectionPlaces()) {
307 if (existing.findPlaceByText(place.getText()) == null) {
308 existing.getInjectionPlaces().add(place);
309 placesAdded = true;
312 if (placesAdded) n++;
316 if (n >= 0) configurationModified();
317 return n;
320 public void configurationModified() {
321 myModificationCount ++;
324 public long getModificationCount() {
325 return myModificationCount;
328 public boolean isResolveReferences() {
329 return myResolveReferences;
332 public void setResolveReferences(final boolean resolveReferences) {
333 myResolveReferences = resolveReferences;
336 @Nullable
337 public BaseInjection findExistingInjection(@NotNull final BaseInjection injection) {
338 final List<BaseInjection> list = getInjections(injection.getSupportId());
339 for (BaseInjection cur : list) {
340 if (cur.intersectsWith(injection)) return cur;
342 return null;
345 public boolean setHostInjectionEnabled(final PsiLanguageInjectionHost host, final Collection<String> languages, final boolean enabled) {
346 final ArrayList<BaseInjection> originalInjections = new ArrayList<BaseInjection>();
347 final ArrayList<BaseInjection> newInjections = new ArrayList<BaseInjection>();
348 for (String supportId : getAllInjectorIds()) {
349 for (BaseInjection injection : getInjections(supportId)) {
350 if (!languages.contains(injection.getInjectedLanguageId())) continue;
351 boolean replace = false;
352 final ArrayList<InjectionPlace> newPlaces = new ArrayList<InjectionPlace>();
353 for (InjectionPlace place : injection.getInjectionPlaces()) {
354 if (place.isEnabled() != enabled && place.getElementPattern() != null &&
355 (place.getElementPattern().accepts(host) || place.getElementPattern().accepts(host.getParent()))) {
356 newPlaces.add(new InjectionPlace(place.getText(), place.getElementPattern(), enabled));
357 replace = true;
359 else newPlaces.add(place);
361 if (replace) {
362 originalInjections.add(injection);
363 final BaseInjection newInjection = injection.copy();
364 newInjection.getInjectionPlaces().clear();
365 newInjection.getInjectionPlaces().addAll(newPlaces);
366 newInjections.add(newInjection);
370 if (!originalInjections.isEmpty()) {
371 replaceInjectionsWithUndo(host.getProject(), newInjections, originalInjections, Collections.<PsiElement>emptyList());
372 return true;
374 return false;
377 public enum InstrumentationType {
378 NONE, ASSERT, EXCEPTION
381 public InstrumentationType getInstrumentation() {
382 return myInstrumentationType;
385 @NotNull
386 public List<BaseInjection> getInjections(final String injectorId) {
387 return Collections.unmodifiableList(myInjections.get(injectorId));
390 public Set<String> getAllInjectorIds() {
391 return Collections.unmodifiableSet(myInjections.keySet());
394 public void replaceInjectionsWithUndo(final Project project,
395 final List<? extends BaseInjection> newInjections,
396 final List<? extends BaseInjection> originalInjections,
397 final List<? extends PsiElement> psiElementsToRemove) {
398 replaceInjectionsWithUndo(project, newInjections, originalInjections, psiElementsToRemove,
399 new PairProcessor<List<? extends BaseInjection>, List<? extends BaseInjection>>() {
400 public boolean process(final List<? extends BaseInjection> add, final List<? extends BaseInjection> remove) {
401 replaceInjections(add, remove);
402 return true;
407 public static <T> void replaceInjectionsWithUndo(final Project project, final T add, final T remove,
408 final List<? extends PsiElement> psiElementsToRemove,
409 final PairProcessor<T, T> actualProcessor) {
410 final UndoableAction action = new UndoableAction() {
411 public void undo() throws UnexpectedUndoException {
412 actualProcessor.process(remove, add);
415 public void redo() throws UnexpectedUndoException {
416 actualProcessor.process(add, remove);
419 public DocumentReference[] getAffectedDocuments() {
420 return DocumentReference.EMPTY_ARRAY;
423 public boolean isComplex() {
424 return true;
428 final List<PsiFile> psiFiles = ContainerUtil.mapNotNull(psiElementsToRemove, new NullableFunction<PsiElement, PsiFile>() {
429 public PsiFile fun(final PsiElement psiAnnotation) {
430 return psiAnnotation instanceof PsiCompiledElement ? null : psiAnnotation.getContainingFile();
433 new WriteCommandAction.Simple(project, psiFiles.toArray(new PsiFile[psiFiles.size()])) {
434 public void run() {
435 for (PsiElement annotation : psiElementsToRemove) {
436 annotation.delete();
438 actualProcessor.process(add, remove);
439 UndoManager.getInstance(project).undoableActionPerformed(action);
442 @Override
443 protected UndoConfirmationPolicy getUndoConfirmationPolicy() {
444 return UndoConfirmationPolicy.REQUEST_CONFIRMATION;
446 }.execute();
449 public void replaceInjections(final List<? extends BaseInjection> newInjections, final List<? extends BaseInjection> originalInjections) {
450 for (BaseInjection injection : originalInjections) {
451 myInjections.get(injection.getSupportId()).remove(injection);
453 for (BaseInjection injection : newInjections) {
454 injection.initializePlaces(true);
455 myInjections.get(injection.getSupportId()).add(injection);
457 configurationModified();
458 for (Project project : ProjectManager.getInstance().getOpenProjects()) {
459 FileContentUtil.reparseFiles(project, Collections.<VirtualFile>emptyList(), true);