001/**
002 *
003 * Copyright 2020 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;
021
022import org.jivesoftware.smack.util.Objects;
023
024import org.jivesoftware.smackx.xdata.FormField;
025import org.jivesoftware.smackx.xdata.TextSingleFormField;
026import org.jivesoftware.smackx.xdata.packet.DataForm;
027
028public class FormFieldRegistry {
029
030    private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>();
031
032    private static final Map<String, FormField.Type> LOOKASIDE_REGISTRY = new HashMap<>();
033
034    private static final Map<String, String> FIELD_NAME_TO_FORM_TYPE = new HashMap<>();
035
036    static {
037        register(FormField.FORM_TYPE, FormField.Type.hidden);
038    }
039
040    @SuppressWarnings("ReferenceEquality")
041    public static synchronized void register(DataForm dataForm) {
042        // TODO: Also allow forms of type 'result'?
043        if (dataForm.getType() != DataForm.Type.form) {
044            throw new IllegalArgumentException();
045        }
046
047        String formType = null;
048        TextSingleFormField hiddenFormTypeField = dataForm.getHiddenFormTypeField();
049        if (hiddenFormTypeField != null) {
050            formType = hiddenFormTypeField.getValue();
051        }
052
053        for (FormField formField : dataForm.getFields()) {
054            // Note that we can compare here by reference equality to skip the hidden form type field.
055            if (formField == hiddenFormTypeField) {
056                continue;
057            }
058
059            String fieldName = formField.getFieldName();
060            FormField.Type type = formField.getType();
061            register(formType, fieldName, type);
062        }
063    }
064
065    public static synchronized void register(String formType, String fieldName, FormField.Type type) {
066        if (formType == null) {
067            FormFieldInformation formFieldInformation = lookup(fieldName);
068            if (formFieldInformation != null) {
069                if (Objects.equals(formType, formFieldInformation.formType)
070                                && type.equals(formFieldInformation.formFieldType)) {
071                    // The field is already registered, nothing to do here.
072                    return;
073                }
074
075                String message = "There is already a field with the name'" + fieldName
076                                + "' registered with the field type '" + formFieldInformation.formFieldType
077                                + "', while this tries to register the field with the type '" + type + '\'';
078                throw new IllegalArgumentException(message);
079            }
080
081            LOOKASIDE_REGISTRY.put(fieldName, type);
082            return;
083        }
084
085        Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType);
086        if (fieldNameToType == null) {
087            fieldNameToType = new HashMap<>();
088            REGISTRY.put(formType, fieldNameToType);
089        } else {
090            FormField.Type previousType = fieldNameToType.get(fieldName);
091            if (previousType != null && previousType != type) {
092                throw new IllegalArgumentException();
093            }
094        }
095        fieldNameToType.put(fieldName, type);
096
097        FIELD_NAME_TO_FORM_TYPE.put(fieldName, formType);
098    }
099
100    public static synchronized void register(String fieldName, FormField.Type type) {
101        FormField.Type previousType = LOOKASIDE_REGISTRY.get(fieldName);
102        if (previousType != null) {
103            if (previousType == type) {
104                // Nothing to do here.
105                return;
106            }
107            throw new IllegalArgumentException("There is already a field with the name '" + fieldName
108                            + "' registered with type " + previousType
109                            + ", while trying to register this field with type '" + type + "'");
110        }
111
112        LOOKASIDE_REGISTRY.put(fieldName, type);
113    }
114
115    public static synchronized FormField.Type lookup(String formType, String fieldName) {
116        if (formType != null) {
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        } else {
125            formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName);
126            if (formType != null) {
127                FormField.Type type = lookup(formType, fieldName);
128                if (type != null) {
129                    return type;
130                }
131            }
132        }
133
134        // Fallback to lookaside registry.
135        return LOOKASIDE_REGISTRY.get(fieldName);
136    }
137
138    public static synchronized FormFieldInformation lookup(String fieldName) {
139        String formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName);
140        FormField.Type type = lookup(formType, fieldName);
141        if (type == null) {
142            return null;
143        }
144
145        return new FormFieldInformation(type, formType);
146    }
147
148    public static final class FormFieldInformation {
149        public final FormField.Type formFieldType;
150        public final String formType;
151
152
153        private FormFieldInformation(FormField.Type formFieldType, String formType) {
154            this.formFieldType = formFieldType;
155            this.formType = formType;
156        }
157    }
158}