navigation providers for dom elements
[fedora-idea.git] / source / com / intellij / util / xml / impl / DomInvocationHandler.java
blob14805aa5e0bc062dedccc7000575d33bc592fefb
1 /*
2 * Copyright (c) 2005 Your Corporation. All Rights Reserved.
3 */
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;
30 import java.util.*;
32 /**
33 * @author peter
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 = "@";
39 private Type myType;
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,
57 final XmlTag tag,
58 final DomInvocationHandler parent,
59 final String tagName,
60 final DomManagerImpl manager,
61 final Converter genericConverter) {
62 myXmlTag = tag;
63 myParent = parent;
64 myTagName = tagName;
65 myManager = manager;
66 myGenericConverter = genericConverter;
67 setType(type);
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) {
79 myType = type;
80 myGenericInfoImpl = myManager.getGenericInfo(type);
81 myInvocationCache = myManager.getInvocationCache(type);
84 final DomInvocationHandler getParentHandler() {
85 return myParent;
88 public final Type getDomElementType() {
89 return myType;
92 public XmlTag ensureTagExists() {
93 if (myXmlTag != null) return myXmlTag;
95 final boolean changing = myManager.setChanging(true);
96 try {
97 attach(setXmlTag(createEmptyTag()));
99 catch (IncorrectOperationException e) {
100 LOG.error(e);
102 catch (IllegalAccessException e) {
103 LOG.error(e);
105 catch (InstantiationException e) {
106 LOG.error(e);
108 finally {
109 myManager.setChanging(changing);
110 myManager.fireEvent(new ElementDefinedEvent(getProxy()));
112 return myXmlTag;
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() {
138 undefineInternal();
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);
152 try {
153 tag.delete();
155 catch (IncorrectOperationException e) {
156 LOG.error(e);
157 } finally {
158 myManager.setChanging(changing);
160 setXmlTagToNull();
163 protected final void setXmlTagToNull() {
164 myXmlTag = null;
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() {
178 return myTagName;
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());
195 return result;
198 public final void initializeAllChildren() {
199 myGenericInfoImpl.buildMethodMaps();
200 for (final String s : myGenericInfoImpl.getFixedChildrenNames()) {
201 checkInitialized(s);
203 for (final String s : myGenericInfoImpl.getCollectionChildrenNames()) {
204 checkInitialized(s);
206 for (final String s : myGenericInfoImpl.getAttributeChildrenNames()) {
207 checkInitialized(s);
211 private List<CollectionElementInvocationHandler> getCollectionChildren() {
212 final List<CollectionElementInvocationHandler> collectionChildren = new ArrayList<CollectionElementInvocationHandler>();
213 final XmlTag tag = getXmlTag();
214 if (tag != null) {
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;
225 @NotNull
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() {
231 return myProxy;
234 public final void setProxy(final DomElement proxy) {
235 myProxy = proxy;
238 @NotNull
239 protected final XmlFile getFile() {
240 assert isValid();
241 if (myFile == null) {
242 myFile = getRoot().getFile();
244 return myFile;
247 public final DomNameStrategy getNameStrategy() {
248 final Class<?> rawType = DomUtil.getRawType(myType);
249 final DomNameStrategy strategy = DomUtil.getDomNameStrategy(rawType);
250 if (strategy != null) {
251 return strategy;
253 final DomInvocationHandler parent = getParentHandler();
254 return parent != null ? parent.getNameStrategy() : DomNameStrategy.HYPHEN_STRATEGY;
257 @NotNull
258 public ElementPresentation getPresentation() {
259 final ElementPresentation presentation = ElementPresentationManager.getElementPresentation(getProxy());
260 if (presentation != null) {
261 return presentation;
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();
274 return (T)element;
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);
301 @NotNull
302 final IndexedElementInvocationHandler getFixedChild(final Pair<String, Integer> info) {
303 return myFixedChildren.get(info);
306 @NotNull
307 final AttributeChildInvocationHandler getAttributeChild(final JavaMethodSignature method) {
308 final AttributeChildInvocationHandler domElement = myAttributeChildren.get(myGenericInfoImpl.getAttributeName(method));
309 assert domElement != null : method.toString();
310 return domElement;
313 public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
314 try {
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) {
349 assert isValid();
350 synchronized (PsiLock.LOCK) {
351 if (myInitializedChildren.contains(qname)) return;
352 try {
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);
374 finally {
375 myInitializedChildren.add(qname);
380 private void getOrCreateAttributeChild(final Method method, final String attributeName) {
381 final AttributeChildInvocationHandler handler = new AttributeChildInvocationHandler(method.getGenericReturnType(),
382 getXmlTag(),
383 this,
384 attributeName,
385 myManager,
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);
397 } else {
398 handler.attach(subTag);
400 return handler;
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) {
424 try {
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) {
436 LOG.error(e);
438 catch (IllegalAccessException e) {
439 LOG.error(e);
441 return null;
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) {
449 if (tag == null) {
450 return null;
452 final XmlTag[] subTags = tag.findSubTags(qname);
453 return subTags.length <= index ? null : subTags[index];
456 @Nullable
457 public XmlTag getXmlTag() {
458 return myXmlTag;
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();
476 removeFromCache();
477 setXmlTagToNull();
481 protected void removeFromCache() {
482 DomManagerImpl.setCachedElement(myXmlTag, null);
485 protected final void attach(final XmlTag tag) {
486 synchronized (PsiLock.LOCK) {
487 myXmlTag = tag;
488 cacheInTag(tag);
492 protected void cacheInTag(final XmlTag tag) {
493 DomManagerImpl.setCachedElement(tag, this);
496 public final DomManagerImpl getManager() {
497 return myManager;
500 boolean isIndicator() {
501 return false;
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()));
521 return element;
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);
531 try {
532 XmlTag newTag = createEmptyTag(tagName);
533 if (index == 0) {
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]);
543 finally {
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);