text: /*
   * Copyright 2010 The authors
   * 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.struts2.reference.web;
  
  import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
  import com.intellij.javaee.model.CommonParamValue;
  import com.intellij.openapi.util.Pair;
  import com.intellij.openapi.util.text.StringUtil;
  import com.intellij.psi.PsiElement;
  import com.intellij.psi.PsiPackage;
  import com.intellij.psi.PsiReference;
  import com.intellij.psi.PsiReferenceBase;
  import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference;
  import com.intellij.psi.xml.XmlTag;
  import com.intellij.struts2.model.constant.StrutsConstantKey;
  import com.intellij.struts2.model.constant.StrutsConstantManager;
  import com.intellij.util.ArrayUtil;
  import com.intellij.util.ArrayUtilRt;
  import com.intellij.util.xml.*;
  import com.intellij.util.xml.impl.ConvertContextFactory;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  
  import java.util.*;
  
  /**
   * Delegates {@link com.intellij.struts2.model.constant.StrutsConstant#getConverter()} references.
   *
   * @author Yann C&eacute;bron
   */
  class StrutsConstantValueReference extends PsiReferenceBase<XmlTag> implements EmptyResolveMessageProvider {
  
    @Nullable
    private final Pair<DomElement, Converter> elementConverterPair;
  
    StrutsConstantValueReference(@NotNull final XmlTag xmlTag) {
      super(xmlTag, false);
      elementConverterPair = getElementConverterPair();
    }
  
    @Override
    public PsiElement resolve() {
      if (elementConverterPair == null) {
        return myElement;
      }
  
      final Converter converter = elementConverterPair.getSecond();
      final ConvertContext convertContext = ConvertContextFactory.createConvertContext(elementConverterPair.first);
  
      // additional variants (String only)
      if (converter instanceof ResolvingConverter) {
        final Set additionalVariants = ((ResolvingConverter) converter).getAdditionalVariants(convertContext);
        if (additionalVariants.contains(getValue())) {
          return myElement;
        }
      }
  
      // "normal" reference
      final Object resolveObject = converter.fromString(getValue(), convertContext);
      if (resolveObject == null) {
        return null;
      }
  
      // DomElement
      if (resolveObject instanceof DomElement) {
        return ((DomElement) resolveObject).getXmlTag();
      }
  
      // fake self-reference (e.g. String value from Converter)
      if (!(resolveObject instanceof PsiElement)) {
        return myElement;
      }
  
      // "real" reference
      return (PsiElement) resolveObject;
    }
  
    @Override
    @NotNull
    public String getUnresolvedMessagePattern() {
      assert elementConverterPair != null;
  
      return elementConverterPair.second
          .getErrorMessage(getValue(), ConvertContextFactory.createConvertContext(elementConverterPair.first));
    }
  
    @Override
    @SuppressWarnings({"unchecked"})
    public Object @NotNull [] getVariants() {
      if (elementConverterPair == null) {
        return ArrayUtilRt.EMPTY_OBJECT_ARRAY;
      }
  
      final Converter converter = elementConverterPair.second;
      if (!(converter instanceof ResolvingConverter)) {
        return ArrayUtilRt.EMPTY_OBJECT_ARRAY;
      }
  
      final ResolvingConverter resolvingConverter = (ResolvingConverter) converter;
  
      // merge "normal" + additional variants
      final DomElement paramValueElement = elementConverterPair.first;
      final ConvertContext convertContext = ConvertContextFactory.createConvertContext(paramValueElement);
  
      // wrap explicitly for empty list
      final Collection converterVariants = new ArrayList(resolvingConverter.getVariants(convertContext));
  
      final Collection variants;
      if (!converterVariants.isEmpty() &&
          converterVariants.iterator().next() instanceof DomElement) {
        variants = Arrays.asList(ElementPresentationManager.getInstance().createVariants(converterVariants));
      } else {
        variants = converterVariants;
      }
  
      variants.addAll(resolvingConverter.getAdditionalVariants(convertContext));
  
      // add custom created references
      if (resolvingConverter instanceof CustomReferenceConverter) {
        final PsiReference[] references = ((CustomReferenceConverter) resolvingConverter).
            createReferences((GenericDomValue) paramValueElement,
                             myElement,
                             convertContext);
        for (final PsiReference customReference : references) {
          if (customReference instanceof JavaClassReference) {
            JavaClassReference javaClassReference = (JavaClassReference)customReference;
            String[] names = javaClassReference.getExtendClassNames();
            PsiElement context = javaClassReference.getCompletionContext();
            if (names != null && context instanceof PsiPackage) {
              javaClassReference.processSubclassVariants((PsiPackage)context, names, element -> variants.add(element));
              continue;
            }
          }
          Collections.addAll(variants, customReference.getVariants());
        }
      }
  
      return ArrayUtil.toObjectArray(variants);
    }
  
    /**
     * Gets the DomElement and corresponding converter.
     *
     * @return {@code null} on errors or if one of both could not be resolved.
     */
    @Nullable
    private Pair<DomElement, Converter> getElementConverterPair() {
      final DomElement paramValueElement = DomUtil.getDomElement(myElement);
      assert paramValueElement != null;
  
      final DomElement domElement = paramValueElement.getParent();
      assert domElement instanceof CommonParamValue;
  
      final CommonParamValue initParamElement = (CommonParamValue) domElement;
      final String paramName = initParamElement.getParamName().getStringValue();
      if (StringUtil.isEmpty(paramName)) {
        return null;
      }
  
      final StrutsConstantManager constantManager = StrutsConstantManager.getInstance(myElement.getProject());
  
      final Converter converter = constantManager.findConverter(myElement, StrutsConstantKey.create(paramName));
      if (converter == null) {
        return null;
      }
  
      return Pair.create(paramValueElement, converter);
    }
  
  }