| /* |
| * Copyright 2007 Sascha Weinreuter |
| * |
| * 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 org.intellij.plugins.relaxNG.model.descriptors; |
| |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiReference; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.xml.impl.BasicXmlAttributeDescriptor; |
| import com.intellij.xml.util.XmlEnumeratedValueReference; |
| import gnu.trove.THashSet; |
| import gnu.trove.TObjectHashingStrategy; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.kohsuke.rngom.digested.DAttributePattern; |
| import org.xml.sax.Locator; |
| |
| import javax.xml.namespace.QName; |
| import java.util.*; |
| |
| public class RngXmlAttributeDescriptor extends BasicXmlAttributeDescriptor { |
| @NonNls |
| private static final QName UNKNOWN = new QName("", "#unknown"); |
| |
| private static final TObjectHashingStrategy<Locator> HASHING_STRATEGY = new TObjectHashingStrategy<Locator>() { |
| @Override |
| public int computeHashCode(Locator o) { |
| final String s = o.getSystemId(); |
| return o.getLineNumber() * 31 + o.getColumnNumber() * 23 + (s != null ? s.hashCode() * 11 : 0); |
| } |
| @Override |
| public boolean equals(Locator o, Locator o1) { |
| if ((o.getLineNumber() == o1.getLineNumber() && o.getColumnNumber() == o1.getColumnNumber())) { |
| if (Comparing.equal(o.getSystemId(), o1.getSystemId())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| }; |
| |
| private final Map<String, String> myValues; |
| private final boolean myOptional; |
| private final RngElementDescriptor myElementDescriptor; |
| private final THashSet<Locator> myDeclarations = new THashSet<Locator>(HASHING_STRATEGY); |
| private final QName myName; |
| |
| RngXmlAttributeDescriptor(RngElementDescriptor elementDescriptor, DAttributePattern pattern, Map<String, String> values, boolean optional) { |
| this(elementDescriptor, getName(pattern), values, optional, pattern.getLocation()); |
| } |
| |
| private static QName getName(DAttributePattern pattern) { |
| final Iterator<QName> iterator = pattern.getName().listNames().iterator(); |
| return iterator.hasNext() ? iterator.next() : UNKNOWN; |
| } |
| |
| private RngXmlAttributeDescriptor(RngElementDescriptor elementDescriptor, QName name, Map<String, String> values, boolean optional, Locator... locations) { |
| myElementDescriptor = elementDescriptor; |
| myValues = values; |
| myOptional = optional; |
| myName = name; |
| myDeclarations.addAll(Arrays.asList(locations)); |
| } |
| |
| public RngXmlAttributeDescriptor mergeWith(RngXmlAttributeDescriptor d) { |
| final QName name = d.myName.equals(UNKNOWN) ? myName : d.myName; |
| |
| final HashMap<String, String> values = new HashMap<String, String>(myValues); |
| values.putAll(d.myValues); |
| |
| final THashSet<Locator> locations = new THashSet<Locator>(myDeclarations, HASHING_STRATEGY); |
| locations.addAll(d.myDeclarations); |
| |
| return new RngXmlAttributeDescriptor(myElementDescriptor, name, values, myOptional || d.myOptional, locations.toArray(new Locator[locations.size()])); |
| } |
| |
| @Override |
| public boolean isRequired() { |
| return !myOptional; |
| } |
| |
| @Override |
| public boolean isFixed() { |
| return isEnumerated() && myValues.size() == 1; |
| } |
| |
| @Override |
| public boolean hasIdType() { |
| return myValues.values().contains("ID"); |
| } |
| |
| @Override |
| public boolean hasIdRefType() { |
| return myValues.values().contains("IDREF"); |
| } |
| |
| @Override |
| @Nullable |
| public String getDefaultValue() { |
| return isEnumerated() ? myValues.keySet().iterator().next() : null; |
| } |
| |
| @Override |
| public boolean isEnumerated() { |
| return myValues.size() > 0 && myValues.get(null) == null; |
| } |
| |
| @Override |
| public String[] getEnumeratedValues() { |
| if (myValues.size() > 0) { |
| final Map<String, String> copy; |
| if (myValues.get(null) != null) { |
| copy = new HashMap<String, String>(myValues); |
| copy.remove(null); |
| } else { |
| copy = myValues; |
| } |
| return copy.keySet().toArray(new String[copy.size()]); |
| } else { |
| return ArrayUtil.EMPTY_STRING_ARRAY; |
| } |
| } |
| |
| @Override |
| public PsiElement getDeclaration() { |
| final Iterator<Locator> it = myDeclarations.iterator(); |
| if (!it.hasNext()) return null; |
| |
| return myElementDescriptor.getDeclaration(it.next()); |
| } |
| |
| public Collection<PsiElement> getDeclarations() { |
| return ContainerUtil.map2List(myDeclarations, new Function<Locator, PsiElement>() { |
| @Override |
| public PsiElement fun(Locator locator) { |
| return myElementDescriptor.getDeclaration(locator); |
| } |
| }); |
| } |
| |
| @Override |
| public String getName(PsiElement context) { |
| final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class, false, PsiFile.class); |
| if (tag != null) { |
| final String uri = myName.getNamespaceURI(); |
| final String prefix = tag.getPrefixByNamespace(uri); |
| if (prefix != null) { |
| if (prefix.length() == 0) { |
| return myName.getLocalPart(); |
| } else { |
| return prefix + ":" + myName.getLocalPart(); |
| } |
| } |
| } |
| if (myName.getNamespaceURI().length() > 0) { |
| final String prefix2 = myName.getPrefix(); |
| if (prefix2 != null && prefix2.length() > 0) { |
| return prefix2 + ":" + myName.getLocalPart(); |
| } |
| } |
| return myName.getLocalPart(); |
| } |
| |
| @Override |
| @NonNls |
| public String getName() { |
| return myName.getLocalPart(); |
| } |
| |
| @Override |
| public void init(PsiElement element) { |
| |
| } |
| |
| @Override |
| public Object[] getDependences() { |
| return myElementDescriptor.getDependences(); |
| } |
| |
| @Override |
| public String validateValue(XmlElement context, String value) { |
| if (isTokenDatatype(value)) { |
| value = normalizeSpace(value); |
| } |
| return super.validateValue(context, value); |
| } |
| |
| private boolean isTokenDatatype(String value) { |
| if (myValues.containsKey(value)) { |
| return "token".equals(myValues.get(value)); |
| } |
| |
| value = normalizeSpace(value); |
| return myValues.containsKey(value) && "token".equals(myValues.get(value)); |
| } |
| |
| private static String normalizeSpace(String value) { |
| return value.replaceAll("\\s+", " ").trim(); |
| } |
| |
| @Override |
| public PsiReference[] getValueReferences(final XmlElement element, @NotNull String text) { |
| return new PsiReference[] { new XmlEnumeratedValueReference(element, this) { |
| @Nullable |
| @Override |
| public PsiElement resolve() { |
| if (isTokenDatatype(getValue())) { |
| return getElement(); |
| } |
| return super.resolve(); |
| } |
| }}; |
| } |
| } |