/* FichothequeLib_Impl - Copyright (c) 2006-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package net.fichotheque.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import net.fichotheque.FichothequeEditor;
import net.fichotheque.Metadata;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.CorpusEditor;
import net.fichotheque.corpus.metadata.CorpusField;
import net.fichotheque.corpus.metadata.CorpusMetadata;
import net.fichotheque.corpus.metadata.CorpusMetadataEditor;
import net.fichotheque.corpus.metadata.ExistingFieldKeyException;
import net.fichotheque.corpus.metadata.FieldGeneration;
import net.fichotheque.corpus.metadata.FieldKey;
import net.fichotheque.corpus.metadata.FieldOptionConstants;
import net.fichotheque.corpus.metadata.FieldOptionException;
import net.fichotheque.corpus.metadata.MandatoryFieldException;
import net.fichotheque.utils.CorpusMetadataUtils;
import net.fichotheque.utils.FieldOptionUtils;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.localisation.LangsUtils;
import net.mapeadores.util.money.Currencies;
import net.mapeadores.util.money.CurrenciesUtils;
import net.mapeadores.util.money.ExtendedCurrency;
import net.mapeadores.util.text.Label;
import net.mapeadores.util.text.Labels;
import net.mapeadores.util.text.LabelsCache;
import net.mapeadores.util.text.MultiStringable;


/**
 *
 * @author Vincent Calame
 */
class CorpusMetadataImpl extends AbstractMetadata implements CorpusMetadata {

    private final CorpusImpl corpus;
    private final Map<FieldKey, CorpusFieldImpl> corpusFieldMap = new HashMap<FieldKey, CorpusFieldImpl>();
    private final List<CorpusFieldImpl> propFieldList = new ArrayList<CorpusFieldImpl>();
    private final List<CorpusFieldImpl> infoFieldList = new ArrayList<CorpusFieldImpl>();
    private final List<CorpusFieldImpl> sectionFieldList = new ArrayList<CorpusFieldImpl>();
    private CorpusField geolocalisationField;
    private List<CorpusField> propCacheList;
    private List<CorpusField> infoCacheList;
    private List<CorpusField> sectionCacheList;
    private FieldGeneration fieldGeneration = CorpusMetadataUtils.EMPTY_FIELDGENERATION;

    private CorpusMetadataImpl(CorpusImpl corpus) {
        this.corpus = corpus;
        try {
            createMandatoryCorpusField(FieldKey.ID);
            createMandatoryCorpusField(FieldKey.LANG);
            createMandatoryCorpusField(FieldKey.TITLE);
            createMandatoryCorpusField(FieldKey.OWNERS);
        } catch (ExistingFieldKeyException efke) {
        }
    }

    static CorpusMetadataImpl.InitEditor fromInit(CorpusImpl.InitEditor corpusInitEditor) {
        CorpusMetadataImpl metadata = new CorpusMetadataImpl((CorpusImpl) corpusInitEditor.getCorpus());
        return new InitEditor(metadata, corpusInitEditor);
    }

    static CorpusMetadataImpl fromNew(CorpusImpl corpus) {
        return new CorpusMetadataImpl(corpus);
    }

    @Override
    public Corpus getCorpus() {
        return corpus;
    }

    @Override
    public CorpusField getCorpusField(FieldKey fieldKey) {
        return corpusFieldMap.get(fieldKey);
    }

    @Override
    public List<CorpusField> getPropList() {
        List<CorpusField> result = propCacheList;
        if (result == null) {
            result = initPropList();
        }
        return result;
    }

    @Override
    public List<CorpusField> getInfoList() {
        List<CorpusField> result = infoCacheList;
        if (result == null) {
            result = initInfoList();
        }
        return result;
    }

    @Override
    public List<CorpusField> getSectionList() {
        List<CorpusField> result = sectionCacheList;
        if (result == null) {
            result = initSectionList();
        }
        return result;
    }

    @Override
    public CorpusField getGeolocalisationField() {
        return geolocalisationField;
    }

    @Override
    public FieldGeneration getFieldGeneration() {
        return fieldGeneration;
    }

    CorpusMetadataEditor getCorpusMetadataEditor(CorpusImpl.CorpusEditorImpl corpusEditor) {
        return new CorpusMetadataEditorImpl(corpusEditor);
    }

    void clear() {
        sectionFieldList.clear();
        propFieldList.clear();
        infoFieldList.clear();
        corpusFieldMap.clear();
        clearCache();
    }

    private Listeners getListeners() {
        return corpus.getListeners();
    }

    private void clearCache() {
        propCacheList = null;
        infoCacheList = null;
        sectionCacheList = null;
    }

    @Override
    protected void fireChange(FichothequeEditor fichothequeEditor) {
        CorpusImpl.CorpusEditorImpl corpusEditor = (CorpusImpl.CorpusEditorImpl) fichothequeEditor.getCorpusEditor(corpus);
        corpusEditor.setMetadataChange();
    }

    private CorpusFieldImpl testCorpusField(CorpusField corpusField) throws IllegalArgumentException {
        if (!(corpusField instanceof CorpusFieldImpl)) {
            throw new IllegalArgumentException("corpusField argument does not come from this CorpusMetadata instance");
        }
        CorpusFieldImpl def = (CorpusFieldImpl) corpusField;
        if (def.getCorpusMetadata() != this) {
            throw new IllegalArgumentException("corpusField argument does not come from this CorpusMetadata instance");
        }
        return def;
    }

    private synchronized List<CorpusField> initPropList() {
        if (propCacheList != null) {
            return propCacheList;
        }
        List<CorpusField> list = CorpusMetadataUtils.wrap(propFieldList.toArray(new CorpusField[propFieldList.size()]));
        propCacheList = list;
        return list;
    }

    private synchronized List<CorpusField> initInfoList() {
        if (infoCacheList != null) {
            return infoCacheList;
        }
        List<CorpusField> list = CorpusMetadataUtils.wrap(infoFieldList.toArray(new CorpusField[infoFieldList.size()]));
        infoCacheList = list;
        return list;
    }

    private synchronized List<CorpusField> initSectionList() {
        if (sectionCacheList != null) {
            return sectionCacheList;
        }
        List<CorpusField> list = CorpusMetadataUtils.wrap(sectionFieldList.toArray(new CorpusField[sectionFieldList.size()]));
        sectionCacheList = list;
        return list;
    }


    private class CorpusFieldImpl implements CorpusField {

        private final FieldKey fieldKey;
        private final String ficheItemType;
        private final LabelsCache labelsCache = new LabelsCache();
        private final SortedMap<String, Object> optionMap = new TreeMap<String, Object>();
        private boolean generated;

        private CorpusFieldImpl(FieldKey fieldKey) {
            this(fieldKey, "");
        }

        private CorpusFieldImpl(FieldKey fieldKey, String ficheItemType) {
            this.fieldKey = fieldKey;
            this.ficheItemType = ficheItemType;
        }

        @Override
        public FieldKey getFieldKey() {
            return fieldKey;
        }

        @Override
        public CorpusMetadata getCorpusMetadata() {
            return CorpusMetadataImpl.this;
        }

        @Override
        public String getFicheItemType() {
            return ficheItemType;
        }

        @Override
        public Object getOption(String optionName) {
            return optionMap.get(optionName);
        }

        @Override
        public Set<String> getOptionNameSet() {
            return Collections.unmodifiableSet(optionMap.keySet());
        }

        @Override
        public Labels getLabels() {
            return labelsCache.getLabels();
        }

        @Override
        public boolean isGenerated() {
            return generated;
        }

        private boolean putLabel(Label label) {
            return labelsCache.putLabel(label);
        }

        private boolean removeLabel(Lang lang) {
            return labelsCache.removeLabel(lang);
        }

        private boolean setOption(String optionName, Object optionValue) throws FieldOptionException {
            if (optionValue == null) {
                if (optionMap.containsKey(optionName)) {
                    optionMap.remove(optionName);
                    return true;
                } else {
                    return false;
                }
            }
            FieldOptionUtils.checkUsage(optionName, fieldKey, ficheItemType);
            switch (optionName) {
                case FieldOptionConstants.CURRENCYARRAY_OPTION:
                    return setCurrencies(optionName, optionValue);
                case FieldOptionConstants.ADDRESSFIELDARRAY_OPTION:
                    return setFieldKeys(optionName, optionValue);
                case FieldOptionConstants.LANGARRAY_OPTION:
                    return setLangs(optionName, optionValue);
                default:
                    return setString(optionName, optionValue);
            }
        }

        private boolean setString(String optionName, Object optionValue) throws FieldOptionException {
            if (!(optionValue instanceof String)) {
                throw new FieldOptionException("value must be a String");
            }
            String stringValue = (String) optionValue;
            FieldOptionUtils.checkString(optionName, stringValue);
            String currentValue = (String) optionMap.get(optionName);
            if (currentValue == null) {
                optionMap.put(optionName, optionValue);
                return true;
            } else if (currentValue.equals(optionValue)) {
                return false;
            } else {
                optionMap.put(optionName, optionValue);
                return true;
            }
        }

        private boolean setCurrencies(String optionName, Object optionValue) throws FieldOptionException {
            Set<ExtendedCurrency> currencySet = new LinkedHashSet<ExtendedCurrency>();
            if (optionValue instanceof ExtendedCurrency[]) {
                ExtendedCurrency[] currencyArray = (ExtendedCurrency[]) optionValue;
                for (ExtendedCurrency currency : currencyArray) {
                    currencySet.add(currency);
                }
            } else if (optionValue instanceof Currencies) {
                currencySet.addAll((Currencies) optionValue);
            } else {
                throw new FieldOptionException("optionValue must be an instance of ExtendedCurrency[] or Currencies");
            }
            Currencies currentCurrencies = (Currencies) optionMap.get(optionName);
            if (currencySet.isEmpty()) {
                if (currentCurrencies == null) {
                    return false;
                } else {
                    optionMap.remove(optionName);
                    return true;
                }
            }
            if ((currentCurrencies != null) && (CurrenciesUtils.areEquals(currentCurrencies, currencySet))) {
                return false;
            }
            optionMap.put(optionName, CurrenciesUtils.fromCollection(currencySet));
            return true;
        }

        private boolean setLangs(String optionName, Object optionValue) throws FieldOptionException {
            Set<Lang> langSet = new LinkedHashSet<Lang>();
            if (optionValue instanceof Lang[]) {
                Lang[] langArray = (Lang[]) optionValue;
                for (Lang lang : langArray) {
                    langSet.add(lang);
                }
            } else if (optionValue instanceof Langs) {
                langSet.addAll((Langs) optionValue);
            } else {
                throw new FieldOptionException("optionValue must be an instance of Lang[] or Langs");
            }
            Langs currentLangs = (Langs) optionMap.get(optionName);
            if (langSet.isEmpty()) {
                if (currentLangs == null) {
                    return false;
                } else {
                    optionMap.remove(optionName);
                    return true;
                }
            }
            if ((currentLangs != null) && (LangsUtils.areEquals(currentLangs, langSet))) {
                return false;
            }
            optionMap.put(optionName, LangsUtils.fromCollection(langSet));
            return true;
        }

        private boolean setFieldKeys(String optionName, Object optionValue) throws FieldOptionException {
            FieldKey[] newArray = null;
            if (optionValue instanceof FieldKey[]) {
                newArray = (FieldKey[]) optionValue;
            } else {
                throw new FieldOptionException("optionValue must be an instance of FieldKey[]");
            }
            FieldKeys fieldKeys = new FieldKeys();
            for (FieldKey newFieldKey : newArray) {
                CorpusField corpusField = CorpusMetadataImpl.this.getCorpusField(newFieldKey);
                if (corpusField != null) {
                    fieldKeys.fieldKeyList.add(corpusField.getFieldKey());
                }
            }
            FieldKeys currentFieldKeys = (FieldKeys) optionMap.get(optionName);
            if (fieldKeys.fieldKeyList.isEmpty()) {
                if (currentFieldKeys == null) {
                    return false;
                } else {
                    optionMap.remove(optionName);
                    return true;
                }
            }
            if ((currentFieldKeys != null) && (currentFieldKeys.isSame(fieldKeys))) {
                return false;
            }
            optionMap.put(optionName, fieldKeys);
            return true;
        }

    }

    private synchronized CorpusField createMandatoryCorpusField(FieldKey fieldKey) throws ExistingFieldKeyException {
        return innerCreateCorpusField(fieldKey, "");
    }

    private synchronized CorpusField innerCreateCorpusField(FieldKey fieldKey, String ficheItemType) throws ExistingFieldKeyException {
        if (corpusFieldMap.containsKey(fieldKey)) {
            throw new ExistingFieldKeyException();
        }
        CorpusFieldImpl corpusField;
        switch (fieldKey.getCategory()) {
            case FieldKey.PROP_CATEGORY:
                ficheItemType = CorpusField.checkFicheItemType(ficheItemType);
                corpusField = new CorpusFieldImpl(fieldKey, ficheItemType);
                propFieldList.add(corpusField);
                break;
            case FieldKey.INFO_CATEGORY:
                ficheItemType = CorpusField.checkFicheItemType(ficheItemType);
                corpusField = new CorpusFieldImpl(fieldKey, ficheItemType);
                infoFieldList.add(corpusField);
                break;
            case FieldKey.SECTION_CATEGORY:
                corpusField = new CorpusFieldImpl(fieldKey);
                sectionFieldList.add(corpusField);
                break;
            case FieldKey.SPECIAL_CATEGORY:
                switch (fieldKey.getKeyString()) {
                    case FieldKey.SPECIAL_ID:
                    case FieldKey.SPECIAL_TITLE:
                        corpusField = new CorpusFieldImpl(fieldKey);
                        break;
                    case FieldKey.SPECIAL_SUBTITLE:
                        corpusField = new CorpusFieldImpl(fieldKey, CorpusField.PARA_FIELD);
                        break;
                    case FieldKey.SPECIAL_LANG:
                        corpusField = new CorpusFieldImpl(fieldKey, CorpusField.LANGUAGE_FIELD);
                        break;
                    case FieldKey.SPECIAL_OWNERS:
                        corpusField = new CorpusFieldImpl(fieldKey, CorpusField.PERSON_FIELD);
                        break;
                    default:
                        throw new SwitchException("Unknown special key = " + fieldKey.getKeyString());
                }
                break;
            default:
                throw new SwitchException("unknown category = " + fieldKey.getCategory());
        }
        corpusFieldMap.put(fieldKey, corpusField);
        for (FieldGeneration.Entry entry : fieldGeneration.getEntryList()) {
            if (entry.getFieldKey().equals(fieldKey)) {
                corpusField.generated = true;
            }
        }
        clearCache();
        return corpusField;
    }

    private boolean innerPutLabel(CorpusField corpusField, Label label) {
        CorpusFieldImpl corpusFieldImpl = testCorpusField(corpusField);
        return corpusFieldImpl.putLabel(label);
    }

    private boolean innerRemoveLabel(CorpusField corpusField, Lang lang) {
        CorpusFieldImpl corpusFieldImpl = testCorpusField(corpusField);
        return corpusFieldImpl.removeLabel(lang);
    }

    private boolean innerSetFieldOption(CorpusField corpusField, String optionName, Object optionValue) throws FieldOptionException {
        CorpusFieldImpl corpusFieldImpl = testCorpusField(corpusField);
        return corpusFieldImpl.setOption(optionName, optionValue);
    }

    private synchronized void innerRemoveCorpusField(CorpusField corpusField) throws MandatoryFieldException {
        CorpusFieldImpl corpusFieldImpl = testCorpusField(corpusField);
        FieldKey fieldKey = corpusFieldImpl.getFieldKey();
        if (fieldKey.isMandatoryField()) {
            throw new MandatoryFieldException();
        }
        corpusFieldMap.remove(fieldKey);
        switch (fieldKey.getCategory()) {
            case FieldKey.PROP_CATEGORY:
                propFieldList.remove(corpusFieldImpl);
                if ((geolocalisationField != null) && (geolocalisationField.equals(corpusFieldImpl))) {
                    geolocalisationField = null;
                }
                break;
            case FieldKey.INFO_CATEGORY:
                infoFieldList.remove(corpusFieldImpl);
                break;
            case FieldKey.SECTION_CATEGORY:
                sectionFieldList.remove(corpusFieldImpl);
                break;
        }
        for (CorpusFieldImpl otherCorpusfield : propFieldList) {
            FieldKeys corpusFieldList = (FieldKeys) otherCorpusfield.optionMap.get(FieldOptionConstants.ADDRESSFIELDARRAY_OPTION);
            if (corpusFieldList != null) {
                corpusFieldList.fieldKeyList.remove(fieldKey);
                if (corpusFieldList.fieldKeyList.isEmpty()) {
                    otherCorpusfield.optionMap.remove(FieldOptionConstants.ADDRESSFIELDARRAY_OPTION);
                }
            }
        }
        clearCache();
    }

    private boolean innerSetGeolocalisationField(CorpusField corpusField) {
        boolean done = false;
        if (corpusField == null) {
            if (geolocalisationField != null) {
                geolocalisationField = null;
                done = true;
            }
        } else {
            CorpusFieldImpl defcorpusfield = testCorpusField(corpusField);
            FieldKey fieldKey = defcorpusfield.getFieldKey();
            if (!fieldKey.isProp()) {
                throw new IllegalArgumentException("not a Prop field : " + fieldKey.getKeyString());
            }
            if (!corpusField.isType(CorpusField.GEOPOINT_FIELD)) {
                throw new IllegalArgumentException("not a GeoPoint Type : " + corpusField.getFicheItemType());
            }
            if (geolocalisationField == null) {
                geolocalisationField = defcorpusfield;
                done = true;
            } else if (!geolocalisationField.equals(defcorpusfield)) {
                geolocalisationField = defcorpusfield;
                done = true;
            }
        }
        return done;
    }

    private boolean innerSetFieldGeneration(FieldGeneration newFieldGeneration) {
        if (newFieldGeneration.getRawString().equals(fieldGeneration.getRawString())) {
            return false;
        }
        fieldGeneration = newFieldGeneration;
        for (CorpusFieldImpl corpusFieldImpl : corpusFieldMap.values()) {
            corpusFieldImpl.generated = false;
        }
        for (FieldGeneration.Entry fieldGenerationEntry : newFieldGeneration.getEntryList()) {
            CorpusFieldImpl corpusFieldImpl = corpusFieldMap.get(fieldGenerationEntry.getFieldKey());
            if (corpusFieldImpl != null) {
                corpusFieldImpl.generated = true;
            }
        }
        return true;
    }


    static class InitEditor implements CorpusMetadataEditor {

        private final CorpusMetadataImpl corpusMetadata;
        private final CorpusEditor corpusEditor;

        private InitEditor(CorpusMetadataImpl corpusMetadata, CorpusEditor corpusEditor) {
            this.corpusMetadata = corpusMetadata;
            this.corpusEditor = corpusEditor;
        }

        @Override
        public Metadata getMetadata() {
            return corpusMetadata;
        }

        @Override
        public CorpusEditor getCorpusEditor() {
            return corpusEditor;
        }

        @Override
        public CorpusField createCorpusField(FieldKey fieldKey, String ficheItemType) throws ExistingFieldKeyException {
            return corpusMetadata.innerCreateCorpusField(fieldKey, ficheItemType);
        }

        @Override
        public boolean putFieldLabel(CorpusField corpusField, Label label) {
            return corpusMetadata.innerPutLabel(corpusField, label);
        }

        @Override
        public boolean removeFieldLabel(CorpusField corpusField, Lang lang) {
            return corpusMetadata.innerRemoveLabel(corpusField, lang);
        }

        @Override
        public boolean putLabel(String name, Label label) {
            return corpusMetadata.innerPutLabel(name, label);
        }

        @Override
        public boolean removeLabel(String name, Lang lang) {
            return corpusMetadata.innerRemoveLabel(name, lang);
        }

        @Override
        public boolean setFieldOption(CorpusField corpusField, String optionName, Object optionValue) throws FieldOptionException {
            return corpusMetadata.innerSetFieldOption(corpusField, optionName, optionValue);
        }

        @Override
        public void removeCorpusField(CorpusField corpusField) throws MandatoryFieldException {
            corpusMetadata.innerRemoveCorpusField(corpusField);
        }

        @Override
        public boolean setGeolocalisationField(CorpusField corpusField) {
            return corpusMetadata.innerSetGeolocalisationField(corpusField);
        }

        @Override
        public boolean setFieldGeneration(FieldGeneration fieldGeneration) {
            return corpusMetadata.innerSetFieldGeneration(fieldGeneration);
        }

    }


    private class CorpusMetadataEditorImpl implements CorpusMetadataEditor {

        private final CorpusImpl.CorpusEditorImpl corpusEditor;

        private CorpusMetadataEditorImpl(CorpusImpl.CorpusEditorImpl corpusEditor) {
            this.corpusEditor = corpusEditor;
        }

        @Override
        public Metadata getMetadata() {
            return CorpusMetadataImpl.this;
        }

        @Override
        public CorpusEditor getCorpusEditor() {
            return corpusEditor;
        }

        @Override
        public CorpusField createCorpusField(FieldKey fieldKey, String ficheItemType) throws ExistingFieldKeyException {
            CorpusField corpusField = innerCreateCorpusField(fieldKey, ficheItemType);
            fireChange();
            getListeners().fireCorpusFieldCreated(corpusEditor.getFichothequeEditor(), corpus, corpusField);
            return corpusField;
        }

        @Override
        public boolean putFieldLabel(CorpusField corpusField, Label label) {
            boolean done = innerPutLabel(corpusField, label);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public boolean removeFieldLabel(CorpusField corpusField, Lang lang) {
            boolean done = innerRemoveLabel(corpusField, lang);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public boolean putLabel(String name, Label label) {
            boolean done = innerPutLabel(name, label);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public boolean removeLabel(String name, Lang lang) {
            boolean done = innerRemoveLabel(name, lang);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public boolean setFieldOption(CorpusField corpusField, String optionName, Object optionValue) throws FieldOptionException {
            boolean done = innerSetFieldOption(corpusField, optionName, optionValue);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public void removeCorpusField(CorpusField corpusField) throws MandatoryFieldException {
            FieldKey fieldKey = corpusField.getFieldKey();
            innerRemoveCorpusField(corpusField);
            fireChange();
            getListeners().fireCorpusFieldRemoved(corpusEditor.getFichothequeEditor(), corpus, fieldKey);
        }

        @Override
        public boolean setGeolocalisationField(CorpusField corpusField) {
            boolean done = innerSetGeolocalisationField(corpusField);
            if (done) {
                fireChange();
            }
            return done;
        }

        @Override
        public boolean setFieldGeneration(FieldGeneration fieldGeneration) {
            boolean done = innerSetFieldGeneration(fieldGeneration);
            if (done) {
                fireChange();
            }
            return done;
        }

        private void fireChange() {
            corpusEditor.setMetadataChange();
        }

    }


    private static class FieldKeys implements MultiStringable {

        private List<FieldKey> fieldKeyList = new ArrayList<FieldKey>();

        private FieldKeys() {

        }

        @Override
        public String[] toStringArray() {
            int length = fieldKeyList.size();
            String[] array = new String[length];
            for (int i = 0; i < length; i++) {
                array[i] = fieldKeyList.get(i).getKeyString();
            }
            return array;
        }

        @Override
        public String toString(String separator) {
            StringBuilder buf = new StringBuilder();
            int length = fieldKeyList.size();
            for (int i = 0; i < length; i++) {
                if (i > 0) {
                    buf.append(separator);
                }
                buf.append(fieldKeyList.get(i).getKeyString());
            }
            return buf.toString();
        }

        @Override
        public int getStringSize() {
            return fieldKeyList.size();
        }

        @Override
        public String getStringValue(int index) {
            return fieldKeyList.get(index).getKeyString();
        }

        private boolean isSame(FieldKeys other) {
            int length = fieldKeyList.size();
            if (other.fieldKeyList.size() != length) {
                return false;
            }
            if (length == 0) {
                return true;
            }
            for (int i = 0; i < length; i++) {
                if (!fieldKeyList.get(i).equals(other.fieldKeyList.get(i))) {
                    return false;
                }
            }
            return true;
        }

    }

}
