2 * Copyright (c) 2005 Your Corporation. All Rights Reserved.
4 package com
.intellij
.util
.xml
.impl
;
6 import com
.intellij
.openapi
.diagnostic
.Logger
;
7 import com
.intellij
.openapi
.util
.Pair
;
8 import com
.intellij
.openapi
.fileEditor
.ex
.FileEditorManagerEx
;
9 import com
.intellij
.openapi
.fileEditor
.OpenFileDescriptor
;
10 import com
.intellij
.openapi
.project
.Project
;
11 import com
.intellij
.psi
.PsiLock
;
12 import com
.intellij
.psi
.search
.GlobalSearchScope
;
13 import com
.intellij
.psi
.xml
.XmlFile
;
14 import com
.intellij
.psi
.xml
.XmlTag
;
15 import com
.intellij
.psi
.xml
.XmlText
;
16 import com
.intellij
.util
.IncorrectOperationException
;
17 import com
.intellij
.util
.xml
.*;
18 import com
.intellij
.util
.xml
.events
.CollectionElementAddedEvent
;
19 import com
.intellij
.util
.xml
.events
.ElementDefinedEvent
;
20 import com
.intellij
.util
.xml
.events
.ElementUndefinedEvent
;
21 import com
.intellij
.javaee
.model
.ElementPresentation
;
22 import com
.intellij
.javaee
.model
.ElementPresentationManager
;
23 import net
.sf
.cglib
.proxy
.InvocationHandler
;
24 import org
.jetbrains
.annotations
.NotNull
;
25 import org
.jetbrains
.annotations
.Nullable
;
27 import java
.lang
.reflect
.InvocationTargetException
;
28 import java
.lang
.reflect
.Method
;
29 import java
.lang
.reflect
.Type
;
35 public abstract class DomInvocationHandler
implements InvocationHandler
, DomElement
{
36 private static final Logger LOG
= Logger
.getInstance("#com.intellij.util.xml.impl.DomInvocationHandler");
37 private static final String ATTRIBUTES
= "@";
40 private final DomInvocationHandler myParent
;
41 private final DomManagerImpl myManager
;
42 private final String myTagName
;
43 private final Converter myGenericConverter
;
44 private XmlTag myXmlTag
;
46 private XmlFile myFile
;
47 private DomElement myProxy
;
48 private final Set
<String
> myInitializedChildren
= new com
.intellij
.util
.containers
.HashSet
<String
>();
49 private final Map
<Pair
<String
, Integer
>, IndexedElementInvocationHandler
> myFixedChildren
= new HashMap
<Pair
<String
, Integer
>, IndexedElementInvocationHandler
>();
50 private final Map
<String
, AttributeChildInvocationHandler
> myAttributeChildren
= new HashMap
<String
, AttributeChildInvocationHandler
>();
51 private GenericInfoImpl myGenericInfoImpl
;
52 private final Map
<String
, Class
> myFixedChildrenClasses
= new HashMap
<String
, Class
>();
53 private boolean myInvalidated
;
54 private InvocationCache myInvocationCache
;
56 protected DomInvocationHandler(final Type type
,
58 final DomInvocationHandler parent
,
60 final DomManagerImpl manager
,
61 final Converter genericConverter
) {
66 myGenericConverter
= genericConverter
;
70 public DomFileElementImpl
getRoot() {
71 return isValid() ? myParent
.getRoot() : null;
74 public DomElement
getParent() {
75 return isValid() ? myParent
.getProxy() : null;
78 public void setType(final Type type
) {
80 myGenericInfoImpl
= myManager
.getGenericInfo(type
);
81 myInvocationCache
= myManager
.getInvocationCache(type
);
84 final DomInvocationHandler
getParentHandler() {
88 public final Type
getDomElementType() {
92 public XmlTag
ensureTagExists() {
93 if (myXmlTag
!= null) return myXmlTag
;
95 final boolean changing
= myManager
.setChanging(true);
97 attach(setXmlTag(createEmptyTag()));
99 catch (IncorrectOperationException e
) {
102 catch (IllegalAccessException e
) {
105 catch (InstantiationException e
) {
109 myManager
.setChanging(changing
);
110 myManager
.fireEvent(new ElementDefinedEvent(getProxy()));
115 protected final XmlTag
createEmptyTag() throws IncorrectOperationException
{
116 return createEmptyTag(myTagName
);
119 protected final XmlTag
createEmptyTag(final String tagName
) throws IncorrectOperationException
{
120 return getFile().getManager().getElementFactory().createTagFromText("<" + tagName
+ "/>");
123 public final boolean isValid() {
124 if (!myInvalidated
&& myXmlTag
!= null && !myXmlTag
.isValid()) {
125 myInvalidated
= true;
127 return !myInvalidated
;
130 public final GenericInfoImpl
getGenericInfo() {
131 myGenericInfoImpl
.buildMethodMaps();
132 return myGenericInfoImpl
;
135 protected abstract void undefineInternal();
137 public final void undefine() {
141 protected final void undefineChildren() {
142 for (final AttributeChildInvocationHandler handler
: myAttributeChildren
.values()) {
143 handler
.detach(false);
145 for (final IndexedElementInvocationHandler handler
: myFixedChildren
.values()) {
146 handler
.detach(false);
150 protected final void deleteTag(final XmlTag tag
) {
151 final boolean changing
= myManager
.setChanging(true);
155 catch (IncorrectOperationException e
) {
158 myManager
.setChanging(changing
);
163 protected final void setXmlTagToNull() {
167 protected final void fireUndefinedEvent() {
168 myManager
.fireEvent(new ElementUndefinedEvent(getProxy()));
171 protected final void fireDefinedEvent() {
172 myManager
.fireEvent(new ElementDefinedEvent(getProxy()));
175 protected abstract XmlTag
setXmlTag(final XmlTag tag
) throws IncorrectOperationException
, IllegalAccessException
, InstantiationException
;
177 public final String
getXmlElementName() {
181 public void accept(final DomElementVisitor visitor
) {
182 DomUtil
.tryAccept(visitor
, DomUtil
.getRawType(myType
), getProxy());
185 public final void acceptChildren(DomElementVisitor visitor
) {
186 for (DomInvocationHandler handler
: getAllChildren()) {
187 handler
.accept(visitor
);
191 private Collection
<DomInvocationHandler
> getAllChildren() {
192 initializeAllChildren();
193 Set
<DomInvocationHandler
> result
= new HashSet
<DomInvocationHandler
>(myFixedChildren
.values());
194 result
.addAll(getCollectionChildren());
198 public final void initializeAllChildren() {
199 myGenericInfoImpl
.buildMethodMaps();
200 for (final String s
: myGenericInfoImpl
.getFixedChildrenNames()) {
203 for (final String s
: myGenericInfoImpl
.getCollectionChildrenNames()) {
206 for (final String s
: myGenericInfoImpl
.getAttributeChildrenNames()) {
211 private List
<CollectionElementInvocationHandler
> getCollectionChildren() {
212 final List
<CollectionElementInvocationHandler
> collectionChildren
= new ArrayList
<CollectionElementInvocationHandler
>();
213 final XmlTag tag
= getXmlTag();
215 for (XmlTag xmlTag
: tag
.getSubTags()) {
216 final DomInvocationHandler cachedElement
= DomManagerImpl
.getCachedElement(xmlTag
);
217 if (cachedElement
instanceof CollectionElementInvocationHandler
) {
218 collectionChildren
.add((CollectionElementInvocationHandler
)cachedElement
);
222 return collectionChildren
;
226 protected final Converter
getConverter(final Method method
, final boolean getter
) throws IllegalAccessException
, InstantiationException
{
227 return myManager
.getConverterManager().getConverter(method
, getter
, myType
, myGenericConverter
);
230 public final DomElement
getProxy() {
234 public final void setProxy(final DomElement proxy
) {
239 protected final XmlFile
getFile() {
241 if (myFile
== null) {
242 myFile
= getRoot().getFile();
247 public final DomNameStrategy
getNameStrategy() {
248 final Class
<?
> rawType
= DomUtil
.getRawType(myType
);
249 final DomNameStrategy strategy
= DomUtil
.getDomNameStrategy(rawType
);
250 if (strategy
!= null) {
253 final DomInvocationHandler parent
= getParentHandler();
254 return parent
!= null ? parent
.getNameStrategy() : DomNameStrategy
.HYPHEN_STRATEGY
;
258 public ElementPresentation
getPresentation() {
259 final ElementPresentation presentation
= ElementPresentationManager
.getElementPresentation(getProxy());
260 if (presentation
!= null) {
263 return new BasicDomElementPresentation(getProxy());
266 public final GlobalSearchScope
getResolveScope() {
267 return getRoot().getResolveScope();
270 private static <T
extends DomElement
> T
_getParentOfType(Class
<T
> requiredClass
, DomElement element
) {
271 while (element
!= null && !(requiredClass
.isInstance(element
))) {
272 element
= element
.getParent();
277 public final <T
extends DomElement
> T
getParentOfType(Class
<T
> requiredClass
, boolean strict
) {
278 return _getParentOfType(requiredClass
, strict ?
getParent() : getProxy());
281 protected final Invocation
createInvocation(final Method method
) throws IllegalAccessException
, InstantiationException
{
282 if (DomUtil
.isTagValueGetter(method
)) {
283 return createGetValueInvocation(getConverter(method
, true));
286 if (DomUtil
.isTagValueSetter(method
)) {
287 return createSetValueInvocation(getConverter(method
, false));
290 return myGenericInfoImpl
.createInvocation(method
);
293 protected Invocation
createSetValueInvocation(final Converter converter
) {
294 return new SetValueInvocation(converter
);
297 protected Invocation
createGetValueInvocation(final Converter converter
) {
298 return new GetValueInvocation(converter
);
302 final IndexedElementInvocationHandler
getFixedChild(final Pair
<String
, Integer
> info
) {
303 return myFixedChildren
.get(info
);
307 final AttributeChildInvocationHandler
getAttributeChild(final JavaMethodSignature method
) {
308 final AttributeChildInvocationHandler domElement
= myAttributeChildren
.get(myGenericInfoImpl
.getAttributeName(method
));
309 assert domElement
!= null : method
.toString();
313 public final Object
invoke(Object proxy
, Method method
, Object
[] args
) throws Throwable
{
315 return doInvoke(JavaMethodSignature
.getSignature(method
), args
);
317 catch (InvocationTargetException ex
) {
318 throw ex
.getTargetException();
322 public final Object
doInvoke(final JavaMethodSignature signature
, final Object
... args
) throws Throwable
{
323 Invocation invocation
= myInvocationCache
.getInvocation(signature
);
324 if (invocation
== null) {
325 invocation
= createInvocation(signature
.findMethod(DomUtil
.getRawType(myType
)));
326 myInvocationCache
.putInvocation(signature
, invocation
);
328 return invocation
.invoke(this, args
);
331 static void setTagValue(final XmlTag tag
, final String value
) {
332 tag
.getValue().setText(value
);
335 static String
getTagValue(final XmlTag tag
) {
336 final XmlText
[] textElements
= tag
.getValue().getTextElements();
337 return textElements
.length
!= 0 ? textElements
[0].getValue() : null;
340 public final String
toString() {
341 return myType
.toString() + " @" + hashCode();
344 final void checkAttributesInitialized() {
345 checkInitialized(ATTRIBUTES
);
348 final void checkInitialized(final String qname
) {
350 synchronized (PsiLock
.LOCK
) {
351 if (myInitializedChildren
.contains(qname
)) return;
353 myGenericInfoImpl
.buildMethodMaps();
355 if (ATTRIBUTES
.equals(qname
)) {
356 for (Map
.Entry
<JavaMethodSignature
, String
> entry
: myGenericInfoImpl
.getAttributeChildrenEntries()) {
357 getOrCreateAttributeChild(entry
.getKey().findMethod(DomUtil
.getRawType(myType
)), entry
.getValue());
361 final XmlTag tag
= getXmlTag();
362 if (myGenericInfoImpl
.isFixedChild(qname
)) {
363 final int count
= myGenericInfoImpl
.getFixedChildrenCount(qname
);
364 for (int i
= 0; i
< count
; i
++) {
365 getOrCreateIndexedChild(findSubTag(tag
, qname
, i
), new Pair
<String
, Integer
>(qname
, i
));
367 } else if (tag
!= null && myGenericInfoImpl
.isCollectionChild(qname
)) {
368 for (XmlTag subTag
: tag
.findSubTags(qname
)) {
369 createCollectionElement(myGenericInfoImpl
.getCollectionChildrenType(qname
), subTag
);
375 myInitializedChildren
.add(qname
);
380 private void getOrCreateAttributeChild(final Method method
, final String attributeName
) {
381 final AttributeChildInvocationHandler handler
= new AttributeChildInvocationHandler(method
.getGenericReturnType(),
386 getConverterForChild(method
));
387 myManager
.createDomElement(handler
);
388 myAttributeChildren
.put(handler
.getXmlElementName(), handler
);
391 private IndexedElementInvocationHandler
getOrCreateIndexedChild(final XmlTag subTag
, final Pair
<String
, Integer
> pair
) {
392 IndexedElementInvocationHandler handler
= myFixedChildren
.get(pair
);
393 if (handler
== null) {
394 handler
= createIndexedChild(subTag
, pair
);
395 myFixedChildren
.put(pair
, handler
);
396 myManager
.createDomElement(handler
);
398 handler
.attach(subTag
);
403 private IndexedElementInvocationHandler
createIndexedChild(final XmlTag subTag
,
404 final Pair
<String
, Integer
> pair
) {
405 final JavaMethodSignature signature
= myGenericInfoImpl
.getFixedChildGetter(pair
);
406 final String qname
= pair
.getFirst();
407 final Class
<?
> rawType
= DomUtil
.getRawType(myType
);
408 final Method method
= signature
.findMethod(rawType
);
409 Converter converter
= getConverterForChild(method
);
410 Type type
= method
.getGenericReturnType();
411 if (myFixedChildrenClasses
.containsKey(qname
)) {
412 type
= getFixedChildrenClass(qname
);
414 final SubTag annotationDFS
= signature
.findAnnotation(SubTag
.class, rawType
);
415 final boolean indicator
= annotationDFS
!= null && annotationDFS
.indicator();
416 return new IndexedElementInvocationHandler(type
, subTag
, this, qname
, pair
.getSecond(), converter
, indicator
);
419 protected final Class
getFixedChildrenClass(final String tagName
) {
420 return myFixedChildrenClasses
.get(tagName
);
423 private Converter
getConverterForChild(final Method method
) {
425 final Class aClass
= DomUtil
.getGenericValueType(method
.getGenericReturnType());
426 if (aClass
== null) return null;
428 final Convert convertAnnotation
= DomUtil
.findAnnotationDFS(method
, Convert
.class);
429 if (convertAnnotation
!= null) {
430 return myManager
.getConverterManager().getConverter(convertAnnotation
.value());
433 return myManager
.getConverterManager().getConverter(aClass
);
435 catch (InstantiationException e
) {
438 catch (IllegalAccessException e
) {
444 final DomElement
createCollectionElement(final Type type
, final XmlTag subTag
) {
445 return myManager
.createDomElement(new CollectionElementInvocationHandler(type
, subTag
, this));
448 protected static XmlTag
findSubTag(final XmlTag tag
, final String qname
, final int index
) {
452 final XmlTag
[] subTags
= tag
.findSubTags(qname
);
453 return subTags
.length
<= index ?
null : subTags
[index
];
457 public XmlTag
getXmlTag() {
461 protected final void detach(boolean invalidate
) {
462 synchronized (PsiLock
.LOCK
) {
463 if (myXmlTag
== null) return;
464 myInvalidated
= invalidate
;
465 if (!myInitializedChildren
.isEmpty()) {
466 Set
<DomInvocationHandler
> fixedChildren
= new HashSet
<DomInvocationHandler
>(myFixedChildren
.values());
467 for (DomInvocationHandler handler
: fixedChildren
) {
468 handler
.detach(invalidate
);
470 for (CollectionElementInvocationHandler handler
: getCollectionChildren()) {
471 handler
.detach(true);
475 myInitializedChildren
.clear();
481 protected void removeFromCache() {
482 DomManagerImpl
.setCachedElement(myXmlTag
, null);
485 protected final void attach(final XmlTag tag
) {
486 synchronized (PsiLock
.LOCK
) {
492 protected void cacheInTag(final XmlTag tag
) {
493 DomManagerImpl
.setCachedElement(tag
, this);
496 public final DomManagerImpl
getManager() {
500 boolean isIndicator() {
504 public final DomElement
addChild(final String tagName
, final Type type
, int index
) throws IncorrectOperationException
{
505 checkInitialized(tagName
);
506 return addCollectionElement(type
, addEmptyTag(tagName
, index
));
509 protected final void createFixedChildrenTags(String tagName
, int count
) throws IncorrectOperationException
{
510 checkInitialized(tagName
);
511 final XmlTag tag
= ensureTagExists();
512 final XmlTag
[] subTags
= tag
.findSubTags(tagName
);
513 if (subTags
.length
< count
) {
514 getFixedChild(new Pair
<String
, Integer
>(tagName
, count
- 1)).ensureTagExists();
518 private DomElement
addCollectionElement(final Type type
, final XmlTag tag
) {
519 final DomElement element
= createCollectionElement(type
, tag
);
520 myManager
.fireEvent(new CollectionElementAddedEvent(element
, tag
.getName()));
524 private XmlTag
addEmptyTag(final String tagName
, int index
) throws IncorrectOperationException
{
525 final XmlTag tag
= ensureTagExists();
526 final XmlTag
[] subTags
= tag
.findSubTags(tagName
);
527 if (subTags
.length
< index
) {
528 index
= subTags
.length
;
530 final boolean changing
= myManager
.setChanging(true);
532 XmlTag newTag
= createEmptyTag(tagName
);
534 if (subTags
.length
== 0) {
535 return (XmlTag
)tag
.add(newTag
);
538 return (XmlTag
)tag
.addBefore(newTag
, subTags
[0]);
541 return (XmlTag
)tag
.addAfter(newTag
, subTags
[index
- 1]);
544 myManager
.setChanging(changing
);
548 public final boolean isInitialized(final String qname
) {
549 synchronized (PsiLock
.LOCK
) {
550 return myInitializedChildren
.contains(qname
);
554 public final boolean isAnythingInitialized() {
555 synchronized (PsiLock
.LOCK
) {
556 return !myInitializedChildren
.isEmpty();
560 public final boolean areAttributesInitialized() {
561 return isInitialized(ATTRIBUTES
);
564 public void setFixedChildClass(final String tagName
, final Class
<?
extends DomElement
> aClass
) {
565 synchronized (PsiLock
.LOCK
) {
566 assert !myInitializedChildren
.contains(tagName
);
567 myFixedChildrenClasses
.put(tagName
, aClass
);