/**
 *
 * Copyright 2018-2020 Florian Schmaus
 *
 * 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.jivesoftware.smack.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import org.jivesoftware.smack.packet.FullyQualifiedElement;

import org.jxmpp.util.cache.LruCache;

public class XmppElementUtil {

    private static final LruCache<Class<? extends FullyQualifiedElement>, QName> CLASS_TO_QNAME_CACHE = new LruCache<>(512);

    public static final Logger LOGGER = Logger.getLogger(XmppElementUtil.class.getName());

    public static QName getQNameFor(Class<? extends FullyQualifiedElement> fullyQualifiedElement) {
        QName qname = CLASS_TO_QNAME_CACHE.get(fullyQualifiedElement);
        if (qname != null) {
            return qname;
        }

        try {
            Object qnameObject = fullyQualifiedElement.getField("QNAME").get(null);
            if (QName.class.isAssignableFrom(qnameObject.getClass())) {
                qname = (QName) qnameObject;
                CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
                return qname;
            }
            LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME.");
        } catch (NoSuchFieldException e) {
            LOGGER.finer("The " + fullyQualifiedElement + " has no static QNAME field. Consider adding one.");
            // Proceed to fallback strategy.
        } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
            throw new IllegalArgumentException(e);
        }

        String element, namespace;
        try {
            element = (String) fullyQualifiedElement.getField("ELEMENT").get(null);
            namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null);
        }
        catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
            throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e);
        }

        qname = new QName(namespace, element);
        CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
        return qname;
    }

    public static <E extends FullyQualifiedElement, R extends FullyQualifiedElement> List<R> getElementsFrom(
                    MultiMap<QName, E> elementMap, Class<R> extensionElementClass) {
        QName qname = XmppElementUtil.getQNameFor(extensionElementClass);

        List<E> extensionElements = elementMap.getAll(qname);

        if (extensionElements.isEmpty()) {
            return Collections.emptyList();
        }

        List<R> res = new ArrayList<>(extensionElements.size());
        for (E extensionElement : extensionElements) {
            R e = extensionElementClass.cast(extensionElement);
            res.add(e);
        }
        return res;
    }
}

@@ -826,37 +826,211 @@
 .packet.
-FullyQualifiedElement
+ExtensionElement;%0Aimport org.jivesoftware.smack.packet.FullyQualifiedElement;%0Aimport org.jivesoftware.smack.packet.StandardExtensionElement;%0Aimport org.jivesoftware.smack.provider.ProviderManager
 ;%0A%0Aimpor
@@ -3017,55 +3017,17 @@
 nds 
-FullyQualifiedElement, R extends FullyQualified
+Extension
 Elem
@@ -3036,17 +3036,17 @@
 t%3E List%3C
-R
+E
 %3E getEle
@@ -3093,16 +3093,31 @@
 QName, E
+xtensionElement
 %3E elemen
@@ -3128,17 +3128,17 @@
 , Class%3C
-R
+E
 %3E extens
@@ -3245,16 +3245,31 @@
   List%3CE
+xtensionElement
 %3E extens
@@ -3420,17 +3420,17 @@
    List%3C
-R
+E
 %3E res = 
@@ -3486,16 +3486,31 @@
   for (E
+xtensionElement
  extensi
@@ -3558,60 +3558,395 @@
     
-R
+E
  e = 
-extensionElementClass.cast(extensionElement
+castOrThrow(extensionElement, extensionElementClass);%0A            res.add(e);%0A        %7D%0A        return res;%0A    %7D%0A%0A    public static %3CE extends ExtensionElement%3E E castOrThrow(ExtensionElement extensionElement, Class%3CE%3E extensionElementClass) %7B%0A        if (!extensionElementClass.isInstance(extensionElement)) %7B%0A            final QName qname = getQNameFor(extensionElementClass
 );%0A
+%0A
     
@@ -3949,32 +3949,561 @@
             
-res.add(
+final String detailMessage;%0A            if (extensionElement instanceof StandardExtensionElement) %7B%0A                detailMessage = %22because there is no according extension element provider registered with ProviderManager for %22%0A                                + qname%0A                                + %22. WARNING: This indicates a serious problem with your Smack setup, probably causing Smack not being able to properly initialize itself.%22;%0A            %7D else %7B%0A                Object provider = ProviderManager.getExtensionProvider(qnam
 e);%0A        
@@ -4506,34 +4506,733 @@
     
+ 
-%7D%0A        return res;%0A    %7D
+       detailMessage = %22because there is an inconsistency with the provider registered with ProviderManager: the active provider for %22%0A                                + qname + %22 '%22 + provider.getClass()%0A                                + %22' does not return instances of type %22 + extensionElementClass%0A                                + %22, but instead returns instances of type %22 + extensionElement.getClass() + %22.%22;%0A            %7D%0A%0A            String message = %22Extension element is not of expected class '%22 + extensionElementClass.getName() + %22', %22%0A                            + detailMessage;%0A            throw new IllegalStateException(message);%0A        %7D%0A%0A        return extensionElementClass.cast(extensionElement);%0A    %7D%0A
 %0A%7D%0A

Add XmppElementUtil.castOrThrow(ExtensionElement, Class)