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
;
54 import java
.util
.concurrent
.CopyOnWriteArrayList
;
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.
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";
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
>>() {
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();
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();
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();
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() {
162 final Configuration cfg
= load(getClass().getClassLoader().getResourceAsStream("/" + COMPONENT_NAME
+ ".xml"));
163 if (cfg
== null) return; // very strange
166 catch (Exception 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());
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();
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
) {
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
;
267 public static Configuration
load(final InputStream is
) throws IOException
, JDOMException
, InvalidDataException
{
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);
291 * Import from another configuration (e.g. imported file). Returns the number of imported items.
293 public int importFrom(final Configuration cfg
) {
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) {
301 mineInjections
.add(other
);
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
);
312 if (placesAdded
) n
++;
316 if (n
>= 0) configurationModified();
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
;
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
;
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
));
359 else newPlaces
.add(place
);
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());
377 public enum InstrumentationType
{
378 NONE
, ASSERT
, EXCEPTION
381 public InstrumentationType
getInstrumentation() {
382 return myInstrumentationType
;
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
);
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() {
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()])) {
435 for (PsiElement annotation
: psiElementsToRemove
) {
438 actualProcessor
.process(add
, remove
);
439 UndoManager
.getInstance(project
).undoableActionPerformed(action
);
443 protected UndoConfirmationPolicy
getUndoConfirmationPolicy() {
444 return UndoConfirmationPolicy
.REQUEST_CONFIRMATION
;
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);