001/**
002 *
003 * Copyright 2020-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.smackx.formtypes;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.logging.Logger;
023
024import org.jivesoftware.smack.util.Objects;
025import org.jivesoftware.smack.util.StringUtils;
026import org.jivesoftware.smack.util.XmlUtil;
027import org.jivesoftware.smackx.xdata.FormField;
028import org.jivesoftware.smackx.xdata.TextSingleFormField;
029import org.jivesoftware.smackx.xdata.packet.DataForm;
030
031public class FormFieldRegistry {
032
033    private static final Logger LOGGER = Logger.getLogger(FormFieldRegistry.class.getName());
034
035    private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>();
036
037    private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>();
038
039    private static final Map<String, FormField.Type> LOOKASIDE_FIELD_REGISTRY = new ConcurrentHashMap<>();
040
041    @SuppressWarnings("ReferenceEquality")
042    public static void register(DataForm dataForm) {
043        // TODO: Also allow forms of type 'result'?
044        if (dataForm.getType() != DataForm.Type.form) {
045            throw new IllegalArgumentException();
046        }
047
048        String formType = null;
049        TextSingleFormField hiddenFormTypeField = dataForm.getHiddenFormTypeField();
050        if (hiddenFormTypeField != null) {
051            formType = hiddenFormTypeField.getValue();
052        }
053
054        for (FormField formField : dataForm.getFields()) {
055            // Note that we can compare here by reference equality to skip the hidden form type field.
056            if (formField == hiddenFormTypeField) {
057                continue;
058            }
059
060            FormField.Type type = formField.getType();
061            if (type == FormField.Type.fixed) {
062                continue;
063            }
064
065            String fieldName = formField.getFieldName();
066            register(formType, fieldName, type);
067        }
068    }
069
070    public static void register(String formType, FormField.Type fieldType, String... fieldNames) {
071        for (String fieldName : fieldNames) {
072            register(formType, fieldName, fieldType);
073        }
074    }
075
076    public static void register(String formType, String fieldName, FormField.Type fieldType) {
077        StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided");
078        Objects.requireNonNull(fieldType);
079
080        if (formType == null) {
081            if (XmlUtil.isClarkNotation(fieldName)) {
082                CLARK_NOTATION_FIELD_REGISTRY.put(fieldName, fieldType);
083            }
084            return;
085        }
086
087        FormField.Type previousType;
088        synchronized (REGISTRY) {
089            Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType);
090            if (fieldNameToType == null) {
091                fieldNameToType = new HashMap<>();
092                REGISTRY.put(formType, fieldNameToType);
093            } else {
094                previousType = fieldNameToType.get(fieldName);
095                if (previousType != null && previousType != fieldType) {
096                    throw new IllegalArgumentException();
097                }
098            }
099            previousType = fieldNameToType.put(fieldName, fieldType);
100        }
101        if (previousType != null && fieldType != previousType) {
102            LOGGER.warning("Form field registry inconsitency detected: Registered field '" + fieldName + "' of type " + fieldType + " but previous type was " + previousType);
103        }
104
105    }
106
107    public static FormField.Type lookup(String formType, String fieldName) {
108        if (formType == null) {
109            if (XmlUtil.isClarkNotation(fieldName)) {
110                return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
111            }
112
113            return LOOKASIDE_FIELD_REGISTRY.get(fieldName);
114        }
115
116        synchronized (REGISTRY) {
117            Map<String, FormField.Type> fieldNameToTypeMap = REGISTRY.get(formType);
118            if (fieldNameToTypeMap != null) {
119                FormField.Type type = fieldNameToTypeMap.get(fieldName);
120                if (type != null) {
121                    return type;
122                }
123            }
124        }
125
126        return null;
127    }
128
129    public static synchronized FormField.Type lookup(String fieldName) {
130        return lookup(null, fieldName);
131    }
132
133    public static void addLookasideFieldRegistryEntry(String fieldName, FormField.Type formFieldType) {
134        LOOKASIDE_FIELD_REGISTRY.put(fieldName, formFieldType);
135    }
136}