001/**
002 *
003 * Copyright 2018-2021 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022import java.util.logging.Logger;
023
024import javax.xml.namespace.QName;
025
026import org.jivesoftware.smack.packet.ExtensionElement;
027import org.jivesoftware.smack.packet.StandardExtensionElement;
028import org.jivesoftware.smack.packet.XmlElement;
029import org.jivesoftware.smack.provider.ProviderManager;
030
031import org.jxmpp.util.cache.LruCache;
032
033public class XmppElementUtil {
034
035    private static final LruCache<Class<? extends XmlElement>, QName> CLASS_TO_QNAME_CACHE = new LruCache<>(512);
036
037    public static final Logger LOGGER = Logger.getLogger(XmppElementUtil.class.getName());
038
039    public static QName getQNameFor(Class<? extends XmlElement> fullyQualifiedElement) {
040        QName qname = CLASS_TO_QNAME_CACHE.get(fullyQualifiedElement);
041        if (qname != null) {
042            return qname;
043        }
044
045        try {
046            Object qnameObject = fullyQualifiedElement.getField("QNAME").get(null);
047            if (QName.class.isAssignableFrom(qnameObject.getClass())) {
048                qname = (QName) qnameObject;
049                CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
050                return qname;
051            }
052            LOGGER.warning("The QNAME field of " + fullyQualifiedElement + " is not of type QNAME.");
053        } catch (NoSuchFieldException e) {
054            LOGGER.finer("The " + fullyQualifiedElement + " has no static QNAME field. Consider adding one.");
055            // Proceed to fallback strategy.
056        } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
057            throw new IllegalArgumentException(e);
058        }
059
060        String element, namespace;
061        try {
062            element = (String) fullyQualifiedElement.getField("ELEMENT").get(null);
063            namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null);
064        }
065        catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
066            throw new IllegalArgumentException("The " + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e);
067        }
068
069        qname = new QName(namespace, element);
070        CLASS_TO_QNAME_CACHE.put(fullyQualifiedElement, qname);
071        return qname;
072    }
073
074    @SuppressWarnings("MixedMutabilityReturnType")
075    public static <E extends ExtensionElement> List<E> getElementsFrom(
076                    MultiMap<QName, XmlElement> elementMap, Class<E> extensionElementClass) {
077        QName qname = XmppElementUtil.getQNameFor(extensionElementClass);
078
079        List<XmlElement> extensionElements = elementMap.getAll(qname);
080
081        if (extensionElements.isEmpty()) {
082            return Collections.emptyList();
083        }
084
085        List<E> res = new ArrayList<>(extensionElements.size());
086        for (XmlElement extensionElement : extensionElements) {
087            E e = castOrThrow(extensionElement, extensionElementClass);
088            res.add(e);
089        }
090        return res;
091    }
092
093    public static <E extends ExtensionElement> E castOrThrow(XmlElement extensionElement, Class<E> extensionElementClass) {
094        if (!extensionElementClass.isInstance(extensionElement)) {
095            final QName qname = getQNameFor(extensionElementClass);
096
097            final String detailMessage;
098            if (extensionElement instanceof StandardExtensionElement) {
099                detailMessage = "because there is no according extension element provider registered with ProviderManager for "
100                                + qname
101                                + ". WARNING: This indicates a serious problem with your Smack setup, probably causing Smack not being able to properly initialize itself.";
102            } else {
103                Object provider = ProviderManager.getExtensionProvider(qname);
104                detailMessage = "because there is an inconsistency with the provider registered with ProviderManager: the active provider for "
105                                + qname + " '" + provider.getClass()
106                                + "' does not return instances of type " + extensionElementClass
107                                + ", but instead returns instances of type " + extensionElement.getClass() + ".";
108            }
109
110            String message = "Extension element is not of expected class '" + extensionElementClass.getName() + "', "
111                            + detailMessage;
112            throw new IllegalStateException(message);
113        }
114
115        return extensionElementClass.cast(extensionElement);
116    }
117
118}