| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.util.xml.impl; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.util.NullableFactory; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.UserDataHolderBase; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiInvalidElementAccessException; |
| import com.intellij.psi.SmartPointerManager; |
| import com.intellij.psi.SmartPsiElementPointer; |
| import com.intellij.psi.XmlElementFactory; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.semantic.SemElement; |
| import com.intellij.semantic.SemKey; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.xml.*; |
| import com.intellij.util.xml.events.DomEvent; |
| import com.intellij.util.xml.reflect.*; |
| import com.intellij.util.xml.stubs.AttributeStub; |
| import com.intellij.util.xml.stubs.DomStub; |
| import com.intellij.util.xml.stubs.ElementStub; |
| import com.intellij.util.xml.stubs.StubParentStrategy; |
| import net.sf.cglib.proxy.AdvancedProxy; |
| import net.sf.cglib.proxy.InvocationHandler; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * @author peter |
| */ |
| public abstract class DomInvocationHandler<T extends AbstractDomChildDescriptionImpl, Stub extends DomStub> extends UserDataHolderBase implements InvocationHandler, DomElement, |
| SemElement { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.util.xml.impl.DomInvocationHandler"); |
| public static final Method ACCEPT_METHOD = ReflectionUtil.getMethod(DomElement.class, "accept", DomElementVisitor.class); |
| public static final Method ACCEPT_CHILDREN_METHOD = ReflectionUtil.getMethod(DomElement.class, "acceptChildren", DomElementVisitor.class); |
| private static final JavaMethod ourGetValue = JavaMethod.getMethod(GenericValue.class, new JavaMethodSignature("getValue")); |
| |
| private final Type myType; |
| private final DomManagerImpl myManager; |
| private final EvaluatedXmlName myTagName; |
| private final T myChildDescription; |
| private DomParentStrategy myParentStrategy; |
| private volatile long myLastModCount; |
| |
| private volatile DomElement myProxy; |
| private DomGenericInfoEx myGenericInfo; |
| private final InvocationCache myInvocationCache; |
| private volatile Converter myScalarConverter = null; |
| private volatile SmartFMap<Method, Invocation> myAccessorInvocations = SmartFMap.emptyMap(); |
| @Nullable protected Stub myStub; |
| |
| protected DomInvocationHandler(Type type, DomParentStrategy parentStrategy, |
| @NotNull final EvaluatedXmlName tagName, |
| final T childDescription, |
| final DomManagerImpl manager, |
| boolean dynamic, |
| @Nullable Stub stub) { |
| myManager = manager; |
| myParentStrategy = parentStrategy; |
| myTagName = tagName; |
| myChildDescription = childDescription; |
| myStub = stub; |
| myLastModCount = manager.getPsiModificationCount(); |
| |
| myType = narrowType(type); |
| |
| myInvocationCache = manager.getApplicationComponent().getInvocationCache(getRawType()); |
| refreshGenericInfo(dynamic); |
| if (stub != null) { |
| stub.setHandler(this); |
| } |
| } |
| |
| protected Type narrowType(@NotNull Type nominalType) { |
| return nominalType; |
| } |
| |
| @Override |
| @Nullable |
| public DomElement getParent() { |
| final DomInvocationHandler handler = getParentHandler(); |
| return handler == null ? null : handler.getProxy(); |
| } |
| |
| protected final void assertValid() { |
| final String s = checkValidity(); |
| if (s != null) { |
| throw new AssertionError(myType.toString() + " @" + hashCode() + "\nclass=" + getClass() + "\nxml=" + getXmlElement() + "; " + s); |
| } |
| } |
| |
| @Nullable |
| final DomInvocationHandler getParentHandler() { |
| return getParentStrategy().getParentHandler(); |
| } |
| |
| @Nullable |
| public Stub getStub() { |
| return myStub; |
| } |
| |
| @Override |
| @NotNull |
| public final Type getDomElementType() { |
| return myType; |
| } |
| |
| @Nullable |
| protected String getValue() { |
| final XmlTag tag = getXmlTag(); |
| return tag == null ? null : getTagValue(tag); |
| } |
| |
| protected void setValue(@Nullable final String value) { |
| final XmlTag tag = ensureTagExists(); |
| myManager.runChange(new Runnable() { |
| @Override |
| public void run() { |
| setTagValue(tag, value); |
| } |
| }); |
| myManager.fireEvent(new DomEvent(getProxy(), false)); |
| } |
| |
| @Override |
| public void copyFrom(final DomElement other) { |
| if (other == getProxy()) return; |
| assert other.getDomElementType().equals(myType) : "Can't copy from " + other.getDomElementType() + " to " + myType; |
| |
| if (other.getXmlElement() == null) { |
| undefine(); |
| return; |
| } |
| |
| myManager.performAtomicChange(new Runnable() { |
| @Override |
| public void run() { |
| ensureXmlElementExists(); |
| final DomInvocationHandler otherInvocationHandler = DomManagerImpl.getDomInvocationHandler(other); |
| assert otherInvocationHandler != null : other; |
| |
| final DomGenericInfoEx genericInfo = otherInvocationHandler.getGenericInfo(); |
| for (final AttributeChildDescriptionImpl description : genericInfo.getAttributeChildrenDescriptions()) { |
| description.getDomAttributeValue(DomInvocationHandler.this).setStringValue(description.getDomAttributeValue(other).getStringValue()); |
| } |
| for (final DomFixedChildDescription description : genericInfo.getFixedChildrenDescriptions()) { |
| final List<? extends DomElement> list = description.getValues(getProxy()); |
| final List<? extends DomElement> otherValues = description.getValues(other); |
| for (int i = 0; i < list.size(); i++) { |
| final DomElement otherValue = otherValues.get(i); |
| final DomElement value = list.get(i); |
| if (!DomUtil.hasXml(otherValue)) { |
| value.undefine(); |
| } |
| else { |
| value.copyFrom(otherValue); |
| } |
| } |
| } |
| for (final DomCollectionChildDescription description : genericInfo.getCollectionChildrenDescriptions()) { |
| for (final DomElement value : description.getValues(getProxy())) { |
| value.undefine(); |
| } |
| for (final DomElement otherValue : description.getValues(other)) { |
| description.addValue(getProxy(), otherValue.getDomElementType()).copyFrom(otherValue); |
| } |
| } |
| |
| final String stringValue = otherInvocationHandler.getValue(); |
| if (StringUtil.isNotEmpty(stringValue)) { |
| setValue(stringValue); |
| } |
| } |
| }); |
| |
| if (!myManager.getSemService().isInsideAtomicChange()) { |
| myManager.fireEvent(new DomEvent(getProxy(), false)); |
| } |
| } |
| |
| @Override |
| public <T extends DomElement> T createStableCopy() { |
| XmlTag tag = getXmlTag(); |
| if (tag != null && tag.isPhysical()) { |
| final DomElement existing = myManager.getDomElement(tag); |
| assert existing != null : existing + "\n---------\n" + tag.getParent().getText() + "\n-----------\n" + tag.getText(); |
| assert getProxy().equals(existing) : existing + "\n---------\n" + tag.getParent().getText() + "\n-----------\n" + tag.getText() + "\n----\n" + this + " != " + |
| DomManagerImpl.getDomInvocationHandler(existing); |
| final SmartPsiElementPointer<XmlTag> pointer = |
| SmartPointerManager.getInstance(myManager.getProject()).createSmartPsiElementPointer(tag); |
| return myManager.createStableValue(new StableCopyFactory<T>(pointer, myType, getClass())); |
| } |
| return (T)createPathStableCopy(); |
| } |
| |
| protected DomElement createPathStableCopy() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public final <T extends DomElement> T createMockCopy(final boolean physical) { |
| final T copy = myManager.createMockElement((Class<? extends T>)getRawType(), getProxy().getModule(), physical); |
| copy.copyFrom(getProxy()); |
| return copy; |
| } |
| |
| @Override |
| @NotNull |
| public String getXmlElementNamespace() { |
| final DomInvocationHandler parent = getParentHandler(); |
| assert parent != null : "this operation should be performed on the DOM having a physical parent, your DOM may be not very fresh"; |
| final XmlElement element = parent.getXmlElement(); |
| assert element != null; |
| return getXmlName().getNamespace(element, getFile()); |
| } |
| |
| @Override |
| @Nullable |
| public String getXmlElementNamespaceKey() { |
| return getXmlName().getXmlName().getNamespaceKey(); |
| } |
| |
| @Override |
| public final Module getModule() { |
| final Module module = ModuleUtilCore.findModuleForPsiElement(getFile()); |
| return module != null ? module : DomUtil.getFile(this).getUserData(DomManager.MOCK_ELEMENT_MODULE); |
| } |
| |
| @Override |
| public XmlTag ensureTagExists() { |
| assertValid(); |
| |
| XmlTag tag = getXmlTag(); |
| if (tag != null) return tag; |
| |
| tag = setEmptyXmlTag(); |
| setXmlElement(tag); |
| |
| final DomElement element = getProxy(); |
| myManager.fireEvent(new DomEvent(element, true)); |
| addRequiredChildren(); |
| myManager.cacheHandler(getCacheKey(), tag, this); |
| return getXmlTag(); |
| } |
| |
| @Override |
| public XmlElement getXmlElement() { |
| return getParentStrategy().getXmlElement(); |
| } |
| |
| @Override |
| public boolean exists() { |
| return getParentStrategy().isPhysical(); |
| } |
| |
| private DomParentStrategy getParentStrategy() { |
| myParentStrategy = myParentStrategy.refreshStrategy(this); |
| return myParentStrategy; |
| } |
| |
| @Override |
| public XmlElement ensureXmlElementExists() { |
| return ensureTagExists(); |
| } |
| |
| protected final XmlTag createChildTag(final EvaluatedXmlName tagName) { |
| final String localName = tagName.getXmlName().getLocalName(); |
| if (localName.contains(":")) { |
| try { |
| return XmlElementFactory.getInstance(myManager.getProject()).createTagFromText("<" + localName + "/>"); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| final XmlElement element = getXmlElement(); |
| assert element != null; |
| return getXmlTag().createChildTag(localName, tagName.getNamespace(element, getFile()), null, false); |
| } |
| |
| @Override |
| public final boolean isValid() { |
| return checkValidity() == null; |
| } |
| |
| String toStringEx() { |
| return myType.toString() + " @" + hashCode() + "&handler=" + super.toString() + "&cd=" + myChildDescription + "&ps=" + myParentStrategy; |
| } |
| |
| @Nullable |
| protected String checkValidity() { |
| ProgressManager.checkCanceled(); |
| final DomParentStrategy parentStrategy = getParentStrategy(); |
| String error = parentStrategy.checkValidity(); |
| if (error != null) { |
| return "Strategy: " + error; |
| } |
| |
| final long modCount = myManager.getPsiModificationCount(); |
| if (myLastModCount == modCount) { |
| return null; |
| } |
| |
| final XmlElement xmlElement = parentStrategy.getXmlElement(); |
| if (xmlElement != null) { |
| final DomInvocationHandler actual = myManager.getDomHandler(xmlElement); |
| if (!equals(actual)) { |
| return "element changed: " + this.toStringEx() + "!=" + (actual == null ? null : actual.toStringEx()); |
| } |
| myLastModCount = modCount; |
| return null; |
| } |
| |
| final DomInvocationHandler parent = getParentHandler(); |
| if (parent == null) { |
| return "no parent: " + getDomElementType(); |
| } |
| |
| error = parent.checkValidity(); |
| if (error != null) { |
| return "parent: " + error; |
| } |
| |
| myLastModCount = modCount; |
| return null; |
| } |
| |
| |
| @Override |
| @NotNull |
| public final DomGenericInfoEx getGenericInfo() { |
| return myGenericInfo; |
| } |
| |
| protected abstract void undefineInternal(); |
| |
| @Override |
| public final void undefine() { |
| undefineInternal(); |
| } |
| |
| protected final void deleteTag(final XmlTag tag) { |
| final boolean changing = myManager.setChanging(true); |
| try { |
| tag.delete(); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| finally { |
| myManager.setChanging(changing); |
| } |
| } |
| |
| protected final void fireUndefinedEvent() { |
| myManager.fireEvent(new DomEvent(getProxy(), false)); |
| } |
| |
| protected abstract XmlTag setEmptyXmlTag(); |
| |
| protected void addRequiredChildren() { |
| for (final AbstractDomChildrenDescription description : getGenericInfo().getChildrenDescriptions()) { |
| if (description instanceof DomAttributeChildDescription) { |
| final Required required = description.getAnnotation(Required.class); |
| |
| if (required != null && required.value()) { |
| description.getValues(getProxy()).get(0).ensureXmlElementExists(); |
| } |
| } |
| else if (description instanceof DomFixedChildDescription) { |
| final DomFixedChildDescription childDescription = (DomFixedChildDescription)description; |
| List<? extends DomElement> values = null; |
| final int count = childDescription.getCount(); |
| for (int i = 0; i < count; i++) { |
| final Required required = childDescription.getAnnotation(i, Required.class); |
| if (required != null && required.value()) { |
| if (values == null) { |
| values = description.getValues(getProxy()); |
| } |
| values.get(i).ensureTagExists(); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| @NotNull |
| public final String getXmlElementName() { |
| return myTagName.getXmlName().getLocalName(); |
| } |
| |
| @NotNull |
| public final EvaluatedXmlName getXmlName() { |
| return myTagName; |
| } |
| |
| @Override |
| public void accept(final DomElementVisitor visitor) { |
| ProgressManager.checkCanceled(); |
| myManager.getApplicationComponent().getVisitorDescription(visitor.getClass()).acceptElement(visitor, getProxy()); |
| } |
| |
| @Override |
| @SuppressWarnings("ForLoopReplaceableByForEach") |
| public void acceptChildren(DomElementVisitor visitor) { |
| ProgressManager.checkCanceled(); |
| final DomElement element = getProxy(); |
| List<? extends AbstractDomChildrenDescription> descriptions = getGenericInfo().getChildrenDescriptions(); |
| for (int i = 0, descriptionsSize = descriptions.size(); i < descriptionsSize; i++) { |
| AbstractDomChildrenDescription description = descriptions.get(i); |
| List<? extends DomElement> values = description.getValues(element); |
| for (int j = 0, valuesSize = values.size(); j < valuesSize; j++) { |
| DomElement value = values.get(j); |
| value.accept(visitor); |
| } |
| } |
| } |
| |
| @NotNull |
| protected final Converter getScalarConverter() { |
| Converter converter = myScalarConverter; |
| if (converter == null) { |
| converter = myScalarConverter = createConverter(ourGetValue); |
| } |
| return converter; |
| } |
| |
| @NotNull |
| private Converter createConverter(final JavaMethod method) { |
| final Type returnType = method.getGenericReturnType(); |
| final Type type = returnType == void.class ? method.getGenericParameterTypes()[0] : returnType; |
| final Class parameter = DomUtil.substituteGenericType(type, myType); |
| if (parameter == null) { |
| LOG.error(type + " " + myType); |
| } |
| Converter converter = getConverter(new AnnotatedElement() { |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { |
| return myInvocationCache.getMethodAnnotation(method, annotationClass); |
| } |
| }, parameter); |
| if (converter == null && type instanceof TypeVariable) { |
| converter = getConverter(this, DomUtil.getGenericValueParameter(myType)); |
| } |
| if (converter == null) { |
| converter = myManager.getConverterManager().getConverterByClass(parameter); |
| } |
| if (converter == null) { |
| throw new AssertionError("No converter specified: String<->" + parameter.getName() + "; method=" + method + "; place=" + myChildDescription); |
| } |
| return converter; |
| } |
| |
| @Override |
| public final T getChildDescription() { |
| return myChildDescription; |
| } |
| |
| @Override |
| @Nullable |
| public <T extends Annotation> T getAnnotation(final Class<T> annotationClass) { |
| final AnnotatedElement childDescription = getChildDescription(); |
| if (childDescription != null) { |
| final T annotation = childDescription.getAnnotation(annotationClass); |
| if (annotation != null) return annotation; |
| } |
| |
| return getClassAnnotation(annotationClass); |
| } |
| |
| protected <T extends Annotation> T getClassAnnotation(Class<T> annotationClass) { |
| return myInvocationCache.getClassAnnotation(annotationClass); |
| } |
| |
| @Nullable |
| private Converter getConverter(final AnnotatedElement annotationProvider, |
| Class parameter) { |
| final Resolve resolveAnnotation = annotationProvider.getAnnotation(Resolve.class); |
| if (resolveAnnotation != null) { |
| final Class<? extends DomElement> aClass = resolveAnnotation.value(); |
| if (!DomElement.class.equals(aClass)) { |
| return DomResolveConverter.createConverter(aClass); |
| } else { |
| LOG.assertTrue(parameter != null, "You should specify @Resolve#value() parameter"); |
| return DomResolveConverter.createConverter(parameter); |
| } |
| } |
| |
| final ConverterManager converterManager = myManager.getConverterManager(); |
| Convert convertAnnotation = annotationProvider.getAnnotation(Convert.class); |
| if (convertAnnotation != null) { |
| if (convertAnnotation instanceof ConvertAnnotationImpl) { |
| return ((ConvertAnnotationImpl)convertAnnotation).getConverter(); |
| } |
| return converterManager.getConverterInstance(convertAnnotation.value()); |
| } |
| |
| return null; |
| } |
| |
| @NotNull |
| public final DomElement getProxy() { |
| DomElement proxy = myProxy; |
| if (proxy == null) { |
| Class<?> rawType = getRawType(); |
| Class<? extends DomElement> implementation = myManager.getApplicationComponent().getImplementation(rawType); |
| final boolean isInterface = rawType.isInterface(); |
| if (implementation == null && !isInterface) { |
| //noinspection unchecked |
| implementation = (Class<? extends DomElement>)rawType; |
| } |
| myProxy = proxy = AdvancedProxy.createProxy(this, implementation, isInterface ? new Class[]{rawType} : ArrayUtil.EMPTY_CLASS_ARRAY); |
| } |
| return proxy; |
| } |
| |
| @NotNull |
| public final XmlFile getFile() { |
| return getParentStrategy().getContainingFile(this); |
| } |
| |
| @Override |
| @NotNull |
| public DomNameStrategy getNameStrategy() { |
| final Class<?> rawType = getRawType(); |
| final DomNameStrategy strategy = DomImplUtil.getDomNameStrategy(rawType, isAttribute()); |
| if (strategy != null) { |
| return strategy; |
| } |
| final DomInvocationHandler handler = getParentHandler(); |
| return handler == null ? DomNameStrategy.HYPHEN_STRATEGY : handler.getNameStrategy(); |
| } |
| |
| protected boolean isAttribute() { |
| return false; |
| } |
| |
| @Override |
| @NotNull |
| public ElementPresentation getPresentation() { |
| ElementPresentationTemplate template = getChildDescription().getPresentationTemplate(); |
| if (template != null) { |
| return template.createPresentation(getProxy()); |
| } |
| return new ElementPresentation() { |
| @Override |
| public String getElementName() { |
| return ElementPresentationManager.getElementName(getProxy()); |
| } |
| |
| @Override |
| public String getTypeName() { |
| return ElementPresentationManager.getTypeNameForObject(getProxy()); |
| } |
| |
| @Override |
| public Icon getIcon() { |
| return ElementPresentationManager.getIconOld(getProxy()); |
| } |
| }; |
| } |
| |
| @Override |
| public final GlobalSearchScope getResolveScope() { |
| return DomUtil.getFile(this).getResolveScope(); |
| } |
| |
| private static <T extends DomElement> T _getParentOfType(Class<T> requiredClass, DomElement element) { |
| while (element != null && !requiredClass.isInstance(element)) { |
| element = element.getParent(); |
| } |
| return (T)element; |
| } |
| |
| @Override |
| public final <T extends DomElement> T getParentOfType(Class<T> requiredClass, boolean strict) { |
| return _getParentOfType(requiredClass, strict ? getParent() : getProxy()); |
| } |
| |
| @NotNull |
| final IndexedElementInvocationHandler getFixedChild(final Pair<FixedChildDescriptionImpl, Integer> info) { |
| final FixedChildDescriptionImpl description = info.first; |
| XmlName xmlName = description.getXmlName(); |
| final EvaluatedXmlName evaluatedXmlName = createEvaluatedXmlName(xmlName); |
| if (myStub != null && description.isStubbed()) { |
| List<DomStub> stubs = myStub.getChildrenByName(xmlName.getLocalName(), xmlName.getNamespaceKey()); |
| DomStub stub = stubs.isEmpty() ? null : stubs.get(0); |
| DomParentStrategy strategy = stub == null ? new StubParentStrategy.Empty(myStub) : new StubParentStrategy(stub); |
| return new IndexedElementInvocationHandler(evaluatedXmlName, description, 0, strategy, myManager, (ElementStub)stub); |
| } |
| final XmlTag tag = getXmlTag(); |
| final int index = info.second; |
| if (tag != null) { |
| if (!tag.isValid()) { |
| throw new PsiInvalidElementAccessException(tag); |
| } |
| final XmlTag[] subTags = tag.getSubTags(); |
| for (int i = 0, subTagsLength = subTags.length; i < subTagsLength; i++) { |
| XmlTag xmlTag = subTags[i]; |
| if (!xmlTag.isValid()) { |
| throw new PsiInvalidElementAccessException(xmlTag, |
| "invalid children of valid tag: " + tag.getText() + "; subtag=" + xmlTag + "; index=" + i); |
| } |
| } |
| final List<XmlTag> tags = DomImplUtil.findSubTags(subTags, evaluatedXmlName, getFile()); |
| if (tags.size() > index) { |
| final XmlTag child = tags.get(index); |
| final IndexedElementInvocationHandler semElement = myManager.getSemService().getSemElement(DomManagerImpl.DOM_INDEXED_HANDLER_KEY, child); |
| if (semElement == null) { |
| final IndexedElementInvocationHandler take2 = myManager.getSemService().getSemElement(DomManagerImpl.DOM_INDEXED_HANDLER_KEY, child); |
| throw new AssertionError("No DOM at XML. Parent=" + tag + "; child=" + child + "; index=" + index+ "; second attempt=" + take2); |
| |
| } |
| return semElement; |
| } |
| } |
| return new IndexedElementInvocationHandler(evaluatedXmlName, description, index, new VirtualDomParentStrategy(this), myManager, null); |
| } |
| |
| @NotNull |
| final AttributeChildInvocationHandler getAttributeChild(final AttributeChildDescriptionImpl description) { |
| final EvaluatedXmlName evaluatedXmlName = createEvaluatedXmlName(description.getXmlName()); |
| if (myStub != null && description.isStubbed()) { |
| AttributeStub stub = myStub.getAttributeStub(description.getXmlName()); |
| StubParentStrategy strategy = StubParentStrategy.createAttributeStrategy(stub, myStub); |
| return new AttributeChildInvocationHandler(evaluatedXmlName, description, myManager, strategy, stub); |
| } |
| final XmlTag tag = getXmlTag(); |
| |
| if (tag != null) { |
| // TODO: this seems ugly |
| String ns = evaluatedXmlName.getNamespace(tag, getFile()); |
| final XmlAttribute attribute = tag.getAttribute(description.getXmlName().getLocalName(), ns.equals(tag.getNamespace())? null:ns); |
| |
| if (attribute != null) { |
| PsiUtilCore.ensureValid(attribute); |
| AttributeChildInvocationHandler semElement = |
| myManager.getSemService().getSemElement(DomManagerImpl.DOM_ATTRIBUTE_HANDLER_KEY, attribute); |
| if (semElement == null) { |
| final AttributeChildInvocationHandler take2 = myManager.getSemService().getSemElement(DomManagerImpl.DOM_ATTRIBUTE_HANDLER_KEY, attribute); |
| throw new AssertionError("No DOM at XML. Parent=" + tag + "; attribute=" + attribute + "; second attempt=" + take2); |
| } |
| return semElement; |
| } |
| } |
| return new AttributeChildInvocationHandler(evaluatedXmlName, description, myManager, new VirtualDomParentStrategy(this), null); |
| } |
| |
| @Override |
| @Nullable |
| public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| try { |
| return findInvocation(method).invoke(this, args); |
| } |
| catch (InvocationTargetException ex) { |
| throw ex.getTargetException(); |
| } |
| } |
| |
| @NotNull |
| private Invocation findInvocation(Method method) { |
| Invocation invocation = myAccessorInvocations.get(method); |
| if (invocation != null) return invocation; |
| |
| invocation = myInvocationCache.getInvocation(method); |
| if (invocation != null) return invocation; |
| |
| JavaMethod javaMethod = myInvocationCache.getInternedMethod(method); |
| invocation = myGenericInfo.createInvocation(javaMethod); |
| if (invocation != null) { |
| myInvocationCache.putInvocation(method, invocation); |
| return invocation; |
| } |
| |
| if (myInvocationCache.isTagValueGetter(javaMethod)) { |
| invocation = new GetInvocation(createConverter(javaMethod)); |
| } |
| else if (myInvocationCache.isTagValueSetter(javaMethod)) { |
| invocation = new SetInvocation(createConverter(javaMethod)); |
| } |
| else { |
| throw new RuntimeException("No implementation for method " + method.toString() + " in class " + myType); |
| } |
| myAccessorInvocations = myAccessorInvocations.plus(method, invocation); |
| return invocation; |
| } |
| |
| private static void setTagValue(final XmlTag tag, final String value) { |
| tag.getValue().setText(value); |
| } |
| |
| private static String getTagValue(final XmlTag tag) { |
| return tag.getValue().getTrimmedText(); |
| } |
| |
| public final String toString() { |
| if (ReflectionUtil.isAssignable(GenericValue.class, getRawType())) { |
| return ((GenericValue)getProxy()).getStringValue(); |
| } |
| return myType.toString() + " @" + hashCode(); |
| } |
| |
| public final Class<?> getRawType() { |
| return ReflectionUtil.getRawType(myType); |
| } |
| |
| @Override |
| @Nullable |
| public XmlTag getXmlTag() { |
| return (XmlTag) getXmlElement(); |
| } |
| |
| @Nullable |
| protected XmlElement recomputeXmlElement(@NotNull final DomInvocationHandler parentHandler) { |
| return null; |
| } |
| |
| protected final void detach() { |
| setXmlElement(null); |
| } |
| |
| final SemKey getCacheKey() { |
| if (this instanceof AttributeChildInvocationHandler) { |
| return DomManagerImpl.DOM_ATTRIBUTE_HANDLER_KEY; |
| } |
| if (this instanceof DomRootInvocationHandler) { |
| return DomManagerImpl.DOM_HANDLER_KEY; |
| } |
| if (this instanceof IndexedElementInvocationHandler) { |
| return DomManagerImpl.DOM_INDEXED_HANDLER_KEY; |
| } |
| |
| if (getChildDescription() instanceof CustomDomChildrenDescription) { |
| return DomManagerImpl.DOM_CUSTOM_HANDLER_KEY; |
| } |
| |
| return DomManagerImpl.DOM_COLLECTION_HANDLER_KEY; |
| } |
| |
| protected final void setXmlElement(final XmlElement element) { |
| refreshGenericInfo(element != null && !isAttribute()); |
| myStub = null; |
| myParentStrategy = element == null ? myParentStrategy.clearXmlElement() : myParentStrategy.setXmlElement(element); |
| } |
| |
| private void refreshGenericInfo(final boolean dynamic) { |
| final StaticGenericInfo staticInfo = myManager.getApplicationComponent().getStaticGenericInfo(myType); |
| myGenericInfo = dynamic ? new DynamicGenericInfo(this, staticInfo) : staticInfo; |
| } |
| |
| @Override |
| @NotNull |
| public final DomManagerImpl getManager() { |
| return myManager; |
| } |
| |
| public final DomElement addCollectionChild(final CollectionChildDescriptionImpl description, final Type type, int index) throws IncorrectOperationException { |
| final EvaluatedXmlName name = createEvaluatedXmlName(description.getXmlName()); |
| final XmlTag tag = addEmptyTag(name, index); |
| final CollectionElementInvocationHandler handler = new CollectionElementInvocationHandler(type, tag, description, this, null); |
| myManager.fireEvent(new DomEvent(getProxy(), false)); |
| getManager().getTypeChooserManager().getTypeChooser(description.getType()).distinguishTag(tag, type); |
| handler.addRequiredChildren(); |
| return handler.getProxy(); |
| } |
| |
| protected final void createFixedChildrenTags(EvaluatedXmlName tagName, FixedChildDescriptionImpl description, int count) { |
| final XmlTag tag = ensureTagExists(); |
| final List<XmlTag> subTags = DomImplUtil.findSubTags(tag, tagName, getFile()); |
| if (subTags.size() < count) { |
| getFixedChild(Pair.create(description, count - 1)).ensureTagExists(); |
| } |
| } |
| |
| private XmlTag addEmptyTag(final EvaluatedXmlName tagName, int index) throws IncorrectOperationException { |
| final XmlTag tag = ensureTagExists(); |
| final List<XmlTag> subTags = DomImplUtil.findSubTags(tag, tagName, getFile()); |
| if (subTags.size() < index) { |
| index = subTags.size(); |
| } |
| final boolean changing = myManager.setChanging(true); |
| try { |
| XmlTag newTag = createChildTag(tagName); |
| if (index == 0) { |
| if (subTags.isEmpty()) { |
| return (XmlTag)tag.add(newTag); |
| } |
| |
| return (XmlTag)tag.addBefore(newTag, subTags.get(0)); |
| } |
| |
| return (XmlTag)tag.addAfter(newTag, subTags.get(index - 1)); |
| } |
| finally { |
| myManager.setChanging(changing); |
| } |
| } |
| |
| @NotNull |
| public final EvaluatedXmlName createEvaluatedXmlName(final XmlName xmlName) { |
| return getXmlName().evaluateChildName(xmlName); |
| } |
| |
| public List<? extends DomElement> getCollectionChildren(final AbstractCollectionChildDescription description, final NotNullFunction<DomInvocationHandler, List<XmlTag>> tagsGetter) { |
| if (myStub != null && description.isStubbed()) { |
| if (description instanceof DomChildDescriptionImpl) { |
| XmlName xmlName = ((DomChildDescriptionImpl)description).getXmlName(); |
| List<DomStub> stubs = myStub.getChildrenByName(xmlName.getLocalName(), xmlName.getNamespaceKey()); |
| return ContainerUtil.map(stubs, new Function<DomStub, DomElement>() { |
| @Override |
| public DomElement fun(DomStub stub) { |
| return stub.getOrCreateHandler((DomChildDescriptionImpl)description, myManager).getProxy(); |
| } |
| }); |
| } |
| else if (description instanceof CustomDomChildrenDescriptionImpl) { |
| List<DomStub> stubs = myStub.getChildrenStubs(); |
| return ContainerUtil.mapNotNull(stubs, new NullableFunction<DomStub, DomElement>() { |
| @Nullable |
| @Override |
| public DomElement fun(DomStub stub) { |
| if (stub instanceof ElementStub && ((ElementStub)stub).isCustom()) { |
| EvaluatedXmlName name = new DummyEvaluatedXmlName(stub.getName(), ""); |
| return new CollectionElementInvocationHandler(name, (CustomDomChildrenDescriptionImpl)description, myManager, (ElementStub)stub).getProxy(); |
| } |
| return null; |
| } |
| }); |
| } |
| } |
| XmlTag tag = getXmlTag(); |
| if (tag == null) return Collections.emptyList(); |
| |
| final List<XmlTag> subTags = tagsGetter.fun(this); |
| if (subTags.isEmpty()) return Collections.emptyList(); |
| |
| List<DomElement> elements = new ArrayList<DomElement>(subTags.size()); |
| for (XmlTag subTag : subTags) { |
| final SemKey<? extends DomInvocationHandler> key = description instanceof CustomDomChildrenDescription ? DomManagerImpl.DOM_CUSTOM_HANDLER_KEY : DomManagerImpl.DOM_COLLECTION_HANDLER_KEY; |
| final DomInvocationHandler semElement = myManager.getSemService().getSemElement(key, subTag); |
| if (semElement == null) { |
| myManager.getSemService().getSemElement(key, subTag); |
| throw new AssertionError("No child for subTag '" + subTag.getName() + "' in tag '" + tag.getName() +"' using key " + key); |
| } |
| else { |
| elements.add(semElement.getProxy()); |
| } |
| } |
| return Collections.unmodifiableList(elements); |
| } |
| |
| private static class StableCopyFactory<T extends DomElement> implements NullableFactory<T> { |
| private final SmartPsiElementPointer<XmlTag> myPointer; |
| private final Type myType; |
| private final Class<? extends DomInvocationHandler> myHandlerClass; |
| |
| public StableCopyFactory(final SmartPsiElementPointer<XmlTag> pointer, |
| final Type type, final Class<? extends DomInvocationHandler> aClass) { |
| myPointer = pointer; |
| myType = type; |
| myHandlerClass = aClass; |
| } |
| |
| @Override |
| public T create() { |
| final XmlTag tag = myPointer.getElement(); |
| if (tag == null || !tag.isValid()) return null; |
| |
| final DomElement element = DomManager.getDomManager(tag.getProject()).getDomElement(tag); |
| if (element == null || !element.getDomElementType().equals(myType)) return null; |
| |
| final DomInvocationHandler handler = DomManagerImpl.getDomInvocationHandler(element); |
| if (handler == null || !handler.getClass().equals(myHandlerClass)) return null; |
| |
| return (T)element; |
| } |
| } |
| |
| public boolean equals(final Object o) { |
| if (this == o) return true; |
| if (o == null || !o.getClass().equals(getClass())) return false; |
| |
| final DomInvocationHandler that = (DomInvocationHandler)o; |
| if (!myChildDescription.equals(that.myChildDescription)) return false; |
| if (!getParentStrategy().equals(that.getParentStrategy())) return false; |
| |
| return true; |
| } |
| |
| public int hashCode() { |
| return myChildDescription.hashCode(); |
| } |
| } |
| |