All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.force.i18n.grammar.LanguageDictionary Maven / Gradle / Ivy

There is a newer version: 1.2.31
Show newest version
/*
 * Copyright (c) 2017, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root  or https://opensource.org/licenses/BSD-3-Clause
 */

package com.force.i18n.grammar;

import static com.force.i18n.commons.util.settings.IniFileUtil.intern;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import com.force.i18n.HumanLanguage;
import com.force.i18n.Renameable;
import com.force.i18n.commons.text.TextUtil;
import com.force.i18n.commons.text.Uniquefy;
import com.force.i18n.grammar.GrammaticalTerm.TermType;
import com.force.i18n.grammar.Noun.NounType;
import com.force.i18n.grammar.impl.GrammaticalTermMapImpl;
import com.force.i18n.grammar.impl.LanguageDeclensionFactory;
import com.force.i18n.grammar.parser.RefTag;
import com.google.common.base.Supplier;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SortedSetMultimap;

/**
 * The base class of new label set for a language. This class is constructed by
 * LabelSet so each language would share the same instance of LabelInfo through
 * the application.
 *
 * This class does: - Construct and keep all generic entity names, nouns
 * (includes "compound noun") and modifier.
 * - format array of (String | LabelTag)+ to string
 *
 * @author yoikawa,stamm
 * @see com.force.i18n.LabelSet
 */
public class LanguageDictionary implements Serializable {
    private static final long serialVersionUID = 1L;

    private final HumanLanguage language;
    private transient LanguageDeclension declension;  // Details about noun structure

    // TODO: These could all be made lists when serialized

    // map to Noun
    protected GrammaticalTermMap nounMap; 
    // map to Noun
    protected GrammaticalTermMap  nounMapByPluralAlias;
    // map to Adjective
    protected GrammaticalTermMap adjectiveMap;
    // map to Article
    protected GrammaticalTermMap
articleMap; // Override of noun to nounOverrides private SortedSetMultimap nounVersionOverrides; // Whether or not we've been "made skinny". private transient boolean isSkinny; // TODO: Following two map/set *can* be shared across the all // instances; but it would have to be concurrent. // TODO: "TableEnumOrId" should be stored on each Noun, right? /** For UI support. keyed by TableEnumOrId to HashMap(name, NounType) */ protected transient Multimap nounsByEntityType; public LanguageDictionary(HumanLanguage language) { this.language = language; this.declension = LanguageDeclensionFactory.get().getDeclension(language); this.nounsByEntityType = ArrayListMultimap.create(); createGrammaticalTermMap(); } /** * Copy constructor. Use only if the declention for the {@code language} is proxying to the declension of * {@code from}. * * @param language language to use * @param from the source {@code LanguageDictionary} to copy from */ public LanguageDictionary(HumanLanguage language, LanguageDictionary from) { this.language = language; this.declension = LanguageDeclensionFactory.get().getDeclension(language); this.nounsByEntityType = from.nounsByEntityType; this.nounMap = from.nounMap; this.nounMapByPluralAlias = from.nounMapByPluralAlias; this.adjectiveMap = from.adjectiveMap; this.articleMap = from.articleMap; this.nounVersionOverrides = from.nounVersionOverrides; this.isSkinny = from.isSkinny; } protected void createGrammaticalTermMap() { this.nounMap = new GrammaticalTermMapImpl<>(); this.nounMapByPluralAlias = new GrammaticalTermMapImpl<>(); this.adjectiveMap = new GrammaticalTermMapImpl<>(); this.articleMap = new GrammaticalTermMapImpl<>(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o instanceof LanguageDictionary) { LanguageDictionary l = (LanguageDictionary)o; return this.language == l.language && this.nounMap.equals(l.nounMap) && this.adjectiveMap.equals(l.adjectiveMap) && this.articleMap.equals(l.articleMap) && this.nounMapByPluralAlias.equals(l.nounMapByPluralAlias) && this.nounsByEntityType.equals(l.nounsByEntityType); } return false; } @Override public int hashCode() { int hash = super.hashCode(); hash = 37 * hash + ((null != language) ? language.hashCode() : 0); hash = 37 * hash + ((null != nounMap) ? nounMap.hashCode() : 0); hash = 37 * hash + ((null != adjectiveMap) ? adjectiveMap.hashCode() : 0); hash = 37 * hash + ((null != articleMap) ? articleMap.hashCode() : 0); hash = 37 * hash + ((null != nounMapByPluralAlias) ? nounMapByPluralAlias.hashCode() : 0); hash = 37 * hash + ((null != nounsByEntityType) ? nounsByEntityType.hashCode() : 0); return hash; } public HumanLanguage getLanguage() { return language; } public LanguageDeclension getDeclension() { return declension; } public Noun createNoun(String name, String pluralAlias, NounType type, String entityName, LanguageStartsWith startsWith, LanguageGender gender, String access, boolean isStandardField, boolean isCopied) { if (entityName != null && entityName.isEmpty()) entityName = null; Noun n = getDeclension().createNoun(name, pluralAlias, type, entityName, startsWith, gender, access, isStandardField, isCopied); // Add to the noun map if (entityName != null) { nounsByEntityType.put(intern(entityName.toLowerCase()), n); } return n; } /** * construct new noun from result set. Assumed the column name is STARTS_WITH, GENDER, and NAME * @param tableEnumOrId the "DB" name of the entity * @param type field, entity, or other * @param rs the result set to read. * @param uniquefy required to prevent reused strings from being duplicated * @throws SQLException if there's a problem reading from the database * @return a noun with no values, yet. */ public Noun createNoun(String tableEnumOrId, NounType type, ResultSet rs, Uniquefy uniquefy) throws SQLException { LanguageStartsWith starts = LanguageStartsWith.fromDbValue(rs.getString("STARTS_WITH")); LanguageGender gen = LanguageGender.fromDbValue(rs.getString("GENDER")); String name = rs.getString("NAME"); return createNoun(name, null, type, tableEnumOrId, starts, gen, null, false, false); } /** * get termMap of Noun */ public GrammaticalTermMap getNounMap() { return nounMap; } /** * get termMap of NounByPluralAlias */ public GrammaticalTermMap getNounByPluralAlias() { return nounMapByPluralAlias; } /** * get termMap of Adjective */ public GrammaticalTermMap getAdjectiveMap() { return adjectiveMap; } /** * get termMap of Article */ public GrammaticalTermMap
getArticleMap() { return articleMap; } private void forAllTerms (TermType type, BiConsumer f) { switch (type) { case Noun: for(Map.Entry e : nounMap.entrySet()) f.accept(e.getKey(), e.getValue()); break; case Adjective: for(Map.Entry e : adjectiveMap.entrySet()) f.accept(e.getKey(), e.getValue()); break; case Article: for(Map.Entry e : articleMap.entrySet()) f.accept(e.getKey(), e.getValue()); break; default: throw new AssertionError("Invalid term type " + type); } } public Set getAllTermNames(TermType type) { switch (type) { case Noun: return Collections.unmodifiableSet(nounMap.keySet()); case Adjective: return Collections.unmodifiableSet(adjectiveMap.keySet()); case Article: return Collections.unmodifiableSet(articleMap.keySet()); default: throw new AssertionError("Invalid term type"); } } /** * @param type the term type to search * @return the names of all the terms of the given type that are copied from the default language */ public Set getAllInheritedTermNames(TermType type) { Set result = new HashSet(); forAllTerms(type, (k,v) -> { if (v.isCopiedFromDefault()) result.add(k); }); return result; } public Adjective createAdjective(String name, LanguageStartsWith startsWith, LanguagePosition position, boolean copiedFromDefault) { Adjective result = getDeclension().createAdjective(name, startsWith, position); result.setInheritedFromDefault(copiedFromDefault); return result; } public Article createArticle(String name, LanguageArticle articleType, boolean copiedFromDefault) { if (!getDeclension().hasArticle()) { throw new RuntimeException("Language doesn't support articles"); } Article result = getDeclension().createArticle(name, articleType); result.setInheritedFromDefault(copiedFromDefault); return result; } /** * Construct String value from the given Label structure, which could be * either String or LabelTag.
* @param obj the object to parse * @param entities the renameable objects that are the parameters for the value * @param vals the values to use when resolving choiceformats. Mostly for numbers * @param forMessageFormat if the MessageFormat values need to be preserved, such as {0} * @param overrideForms if true, it means that the forms in the term refs in the objects do not match the current dictionary and need to be recalculated * @return the label with all the values replaced */ public String format(Object obj, Renameable[] entities, Object[] vals, boolean overrideForms, boolean forMessageFormat) { if (obj instanceof List) { StringBuilder sb = new StringBuilder(); for (Object o : (List< ? >)obj) { assert o != null; if (!(o instanceof RefTag)) { if (forMessageFormat) { TextUtil.escapeForMessageFormat(o.toString(), sb, false); } else { sb.append(o); } } else { String s = ((RefTag)o).toString(this, overrideForms, vals, entities); if (forMessageFormat) { TextUtil.escapeForMessageFormat(s, sb, false); } else { sb.append(s); } } } return sb.toString(); } else if (obj instanceof RefTag) { String s = ((RefTag)obj).toString(this, overrideForms, vals, entities); return (!forMessageFormat) ? s : TextUtil.escapeForMessageFormat(s, new StringBuilder(s.length() + 8), false).toString(); } // nothing related to LabelInfo, just return as String return obj.toString(); } private RenamingProvider getRenamingProvider() { return RenamingProviderFactory.get().getProvider(); } /** * used to resolve custom object name at runtime. For the most case, this is * for resolving label like: <entity entity="0"/> <entity_xxxx entity="0"/> * @param name the default name of the noun * @param ei the renameable entity * @param getRenamedValue return the renamed value if the renaming provider includes a renamed noun * @param doFormat if {0}'s in the renameable noun should be replaced * @return the noun that should be used for the given renameable entity, or the default under name if not applicable. */ public Noun getDynamicNoun(String name, Renameable ei, boolean getRenamedValue, boolean doFormat) { String resolvedDbName = ei.getEntitySpecificDbLabelKey(name); if (getRenamedValue) { Noun n = getRenamingProvider().getRenamedNoun(getLanguage(), resolvedDbName); if (n != null) return n; } else if (!ei.hasStandardLabel()) { // i.e. custom object // Try the original noun if necessary. Noun n = getRenamingProvider().getPackagedNoun(getLanguage(), resolvedDbName); if (n != null) return n; // OK, if we don't have a packaged one, try the renamed one anyway n = getRenamingProvider().getRenamedNoun(getLanguage(), resolvedDbName); if (n != null) return n; } Noun n; // okay, we need to construct Noun from template. if (!ei.hasStandardLabel()) { // The name field's in the translation table or on the object itself // if its custom, it normally returns the value above. Only gets // here if renaming in another language n = getNoun(name, false); if (n != null) { n = n.clone(); Renameable.StandardField f = ei.getRenameableFieldForKey(name); if (f != null) { String value = ei.getStandardFieldLabel(getLanguage(), f); if (value != null) { for (NounForm form : getDeclension().getAllNounForms()) { n.setString(intern(value), form); } } } else if (doFormat) { // Create the forms based on the plural vs. not plural of the "default" noun for (NounForm form : n.getNounType() == NounType.ENTITY? getDeclension().getAllNounForms() : getDeclension().getFieldForms()) { // Usually, the strings are of the form "{0} View" or something like that. If the string's filled in, assume it's a MessageFormat String str = n.getString(form); if (str == null) { // Display something, at least n.setString(intern(form.getNumber().isPlural() ? ei.getLabelPlural() : ei.getLabel()), form); } else { // Escape the string of { and ' before using MessageFormat // because they are special characters java.text.MessageFormat formatter = new java.text.MessageFormat(TextUtil.escapeForMessageFormat(str)); formatter.setLocale(getLanguage().getLocale()); n.setString(intern(formatter.format(new String[] {form.getNumber().isPlural() ? ei.getLabelPlural() : ei.getLabel()})), form); } } } } } else { // OK, so it is custom. Try and get it from the renaming provider. n = getNoun(resolvedDbName, false); } return n; } public Noun getNoun(String name, boolean getRenamedValue) { // check cache first. // Note that any calls from setup screen, this condition always fails. if (getRenamedValue && getRenamingProvider().useRenamedNouns()) { Noun n = getRenamingProvider().getRenamedNoun(getLanguage(), name); if (n != null) return n; } Noun n = nounMap.get(name); return getNounOverride(n); } public Noun getNounByPluralAlias(String name, boolean getRenamedValue) { Noun n = this.nounMapByPluralAlias.get(name); // check cache first. // Note that any calls from setup screen, this condition always fails. if (n != null && getRenamedValue && getRenamingProvider().useRenamedNouns()) { Noun renamed = getRenamingProvider().getRenamedNoun(getLanguage(), n.getName()); if (renamed != null) return renamed; } return getNounOverride(n); } public Adjective getAdjective(String name) { return adjectiveMap.get(name); } public Article getArticle(String name) { return articleMap.get(name); } public Multimap getNounsByEntity() { return Multimaps.unmodifiableMultimap(this.nounsByEntityType); } public void put(String name, GrammaticalTerm term) { if (term instanceof Noun) { Noun noun = (Noun) term; assert name.equals(((Noun)term).getName()) : "Trying to put a noun into the map at the wrong name"; nounMap.put(intern(name), noun); if (noun.getPluralAlias() != null) nounMapByPluralAlias.put(intern(noun.getPluralAlias().toLowerCase()), noun); } else if (term instanceof Article) { articleMap.put(intern(name), (Article)term); } else { adjectiveMap.put(intern(name), (Adjective)term); } } /** * Copy the terms from the other dictionary into this one. * Note, this will *not* clone the Grammatical Terms, so changes made to the nouns in the child * label set will override the parent. * @param otherDictionary the other dictionary which contains all of the values. * */ public void putAll(LanguageDictionary otherDictionary) { if (otherDictionary.getLanguage() != this.getLanguage()) { throw new IllegalArgumentException("Language mismatch: " + this.getLanguage() + " != " + otherDictionary.getLanguage()); } nounMap.putAll(otherDictionary.nounMap); nounMapByPluralAlias.putAll(otherDictionary.nounMapByPluralAlias); articleMap.putAll(otherDictionary.articleMap); adjectiveMap.putAll(otherDictionary.adjectiveMap); nounsByEntityType.putAll(otherDictionary.nounsByEntityType); if (otherDictionary.nounVersionOverrides != null) { if (this.nounVersionOverrides == null) { this.nounVersionOverrides = makeNounVersionOverrideMap(); } this.nounVersionOverrides.putAll(otherDictionary.nounVersionOverrides); } } // validate all modifiers public void validateAll() { nounMap.validate(); adjectiveMap.validate(); articleMap.validate(); if (this.nounVersionOverrides != null) { for (NounVersionOverride nvo : this.nounVersionOverrides.values()) { nvo.getNoun().validate(nvo.getNoun().getName()); nvo.getNoun().makeSkinny(); } } } /** * @return a sorted list of noun names for the given entity. * @param tableEnum the entity name to get the group * @param includeEntity if the entity noun itself should be included, or if false, just the fields. */ public final SortedSet getNames(String tableEnum, boolean includeEntity) { Collection m = nounsByEntityType.get(tableEnum.toLowerCase()); TreeSet list = new TreeSet<>(); for (Noun noun : m) { String s = noun.getName(); // skip for the custom dummy name if (s.equalsIgnoreCase(Renameable.ENTITY_NAME)) continue; if (noun.getNounType() != NounType.ENTITY || includeEntity) list.add(s); } return list; } /** * @return true if the given name has entity dictionary data * @param name the name of the entity to lookup */ public boolean isEntity(String name) { Noun n = this.nounMap.get(name.toLowerCase()); if (n == null) return false; return n.getNounType() == NounType.ENTITY; } public boolean isEntityPlural(String name) { Noun n = this.nounMapByPluralAlias.get(name.toLowerCase()); if (n == null) return false; return n.getNounType() == NounType.ENTITY; } public boolean isAdjective(String name) { return this.adjectiveMap.containsKey(name.toLowerCase()); } public boolean isArticle(String name) { return this.articleMap.containsKey(name.toLowerCase()); } public boolean isNoun(String name) { return this.nounMap.containsKey(name.toLowerCase()) || this.nounMapByPluralAlias.containsKey(name.toLowerCase()); } /** * For the given term (which should be in lowercase), return the grammatical term it may be * @param name the name of the term (which must be in lowercase * @return the term associated with the name, or null if there is no such term */ public GrammaticalTerm getTerm(String name) { Noun n = this.nounMap.get(name); if (n != null) return getNounOverride(n); n = this.nounMapByPluralAlias.get(name); if (n != null) return getNounOverride(n); Adjective adj = this.adjectiveMap.get(name); if (adj != null) return adj; Article art = this.articleMap.get(name); if (art != null) return art; return null; } /** * @return true if the given name is for custom entity * @param name the name of the entity to lookup */ public boolean isCustom(String name) { Collection nouns = nounsByEntityType.get(Renameable.ENTITY_NAME); for (Noun n : nouns) { if (n.getName().equalsIgnoreCase(name)) { return true; } } return false; } // ------------------------------------------------------------- // LanguageDictionaryParser support methods // // TODO: We shouldn't allow duplicates. (Although the way that zh_CN works, means we need to support "overrides" public Noun getOrCreateNoun(String tableEnum, String name, String pluralAlias, NounType type, LanguageGender gender, LanguageStartsWith startsWith, String access, boolean isStandardField) { // final stage. create Noun if it does not exist yet. // note that if localized dictionary is loaded, this is // called more than once against the same name. In that // case, it should reuse and overwrite the attributes only. Noun n = nounMap.get(name); if (n == null) n = createNoun(name, pluralAlias, type, tableEnum, startsWith == null ? getDeclension().getDefaultStartsWith() : startsWith, gender == null ? getDeclension().getDefaultGender() : gender, access, isStandardField, false); else { if (n.isCopiedFromDefault()) { // If the noun was copied, we must be overwriting it with new stuff n = createNoun(name, pluralAlias, type, tableEnum, startsWith == null ? getDeclension().getDefaultStartsWith() : startsWith, gender == null ? getDeclension().getDefaultGender() : gender, access, isStandardField, false); } else if (n.getGender() != gender // Validate that it's the same || n.getStartsWith() != startsWith || n.isStandardField() != isStandardField) { // We go in here when a label file is overriden with another // Ex: when processing Mexican Spanish over regular Spanish OR when using the label renderer n.setGender(gender); n.setStartsWith(startsWith); } } return n; } public Adjective getOrCreateAdjective(String name, LanguageStartsWith startsWith, LanguagePosition position) { Adjective adjective = adjectiveMap.get(name); if (adjective == null) { adjective = createAdjective(name, startsWith, position, false); } return adjective; } public Article getOrCreateArticle(String name, LanguageArticle articleType) { Article article = articleMap.get(name); if (article == null) { article = getDeclension().createArticle(name, articleType); } else { if (article.getArticleType() != articleType) { throw new RuntimeException("Parsing the article again with different values?"); } } return article; } // -------------------------------------------------------------------------------------------- // Utility methods // // Provides access to the language dictionary during the parsing phase. After parsing is over, this is "shut off" public void setString(Noun n, NounForm form, String value) { if (isSkinny) throw new UnsupportedOperationException("Trying to modify noun " + n + " after made skinny."); n.setString(intern(value), form); } public void setString(Adjective m, AdjectiveForm form, String value) { if (isSkinny) throw new UnsupportedOperationException("Trying to modify adjective " + m + " after made skinny."); m.setString(form, intern(value)); } public void setString(Article m, ArticleForm form, String value) { if (isSkinny) throw new UnsupportedOperationException("Trying to modify article " + m + " after made skinny."); m.setString(form, intern(value)); } @Override public String toString() { return "LanguageDictionary:" + getLanguage(); } /** * Write the grammatical terms associated with the given terms * @param appendable the appendable to write to * @param useRenamedNouns if true, use the renamed nouns instead of the default ones. If you're doing work around * renaming nouns, you often want to pass in false for this * @param termsToInclude optional set of term to include, if omitted all terms are included * @throws IOException if an error happens during append */ /** * Write the grammatical terms associated with the given terms * @param appendable the appendable to write to * @param useRenamedNouns if true, use the renamed nouns instead of the default ones. If you're doing work around * renaming nouns, you often want to pass in false for this * @param termsToInclude optional set of term to include, if omitted all terms are included * @throws IOException if an error happens during append */ public void writeJson(Appendable appendable, boolean useRenamedNouns, Collection termsToInclude) throws IOException { RenamingProvider renamingProvider = useRenamedNouns ? RenamingProviderFactory.get().getProvider() : null; appendable.append("{\"n\":"); nounMap.writeJson(appendable, renamingProvider, this, termsToInclude); appendable.append(",\"a\":"); adjectiveMap.writeJson(appendable, renamingProvider, this, termsToInclude); if (!this.articleMap.isEmpty()) { appendable.append(",\"d\":"); articleMap.writeJson(appendable, renamingProvider, this, termsToInclude); } appendable.append("}"); } /** * Write the grammatical terms associated with * @param appendable the appendable to write to * @param useRenamedNouns if true, use the renamed nouns instead of the default ones. If you're doing work around * renaming nouns, you often want to pass in false for this * @param termsToInclude optional set of term to include, if omitted all terms are included * @throws IOException if an error happens during append */ public void writeJsonTerms(Appendable out, boolean useRenamedNouns, Collection terms) throws IOException { Collection termsToInclude = terms == null ? null : terms.stream().map((t)->t.getName()).collect(Collectors.toSet()); writeJson(out, useRenamedNouns, termsToInclude); } private void writeObject(ObjectOutputStream out) throws IOException { makeSkinny(); out.defaultWriteObject(); writeObjectInternal(out); } protected void writeObjectInternal(ObjectOutputStream out) throws IOException { // do nothing here - for hook } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.declension = LanguageDeclensionFactory.get().getDeclension(this.language); this.readObjectInternal(in); } protected void readObjectInternal(ObjectInputStream in) throws IOException, ClassNotFoundException { this.nounsByEntityType = ArrayListMultimap.create(); for (Noun n : this.nounMap.values()) { if (n.getEntityName() != null) this.nounsByEntityType.put(intern(n.getEntityName().toLowerCase()), n); } this.isSkinny = true; } public Noun getNounOverride(Noun n) { if (n == null) return null; if (this.nounVersionOverrides == null) return n; SortedSet overrides = this.nounVersionOverrides.get(n); if (overrides == null) return n; RenamingProvider renamingProvider = getRenamingProvider(); double labelVersion = renamingProvider.getLabelVersion(); for (NounVersionOverride nvo : overrides) { if (labelVersion >= nvo.getAtLeast()) { return nvo.getNoun(); } } return n; } private static class TreeSetSupplier implements Supplier>, Serializable { private static final long serialVersionUID = 1L; @Override public SortedSet get() { return new TreeSet(); } } private SortedSetMultimap makeNounVersionOverrideMap() { return Multimaps.newSortedSetMultimap(new IdentityHashMap<>(), new TreeSetSupplier()); } public void setNounOverride(Noun n, Noun override, double version) { if (this.nounVersionOverrides == null) { // IdentityHashMap to keep it quick and prevent any funny business this.nounVersionOverrides = makeNounVersionOverrideMap(); } if (!this.nounVersionOverrides.put(n, new NounVersionOverride(override, version))) { throw new IllegalArgumentException( "Noun " + n + " in language " + this.getLanguage() + " has duplicate versions for " + version); } } /** * {@link LanguageDictionary} store metadata in {@link Map}'s that have a high memory per element cost. The various * dictionaries are static in nature and would not change after the initial load. {@link ImmutableSortedMap}'s have * a 8 byte overhead cost which is attractive to keep the required space of {@link LanguageDictionary} to a minimum. * Since {@link LanguageDictionary}'s are static, this make them a perfect candidate for {@link ImmutableSortedMap}. */ public void makeSkinny() { nounMap = nounMap.makeSkinny(); nounMapByPluralAlias = nounMapByPluralAlias.makeSkinny(); adjectiveMap = adjectiveMap.makeSkinny(); articleMap = articleMap.makeSkinny(); // don't convert nounsByEntityType here. it may be updated by #createNoun isSkinny = true; // Prevent adding anything to this dictionary set. By assumption, the nouns are skinny. } /** * Has the override for a noun that has a different value for for different API versions. * * Note: as of 0.7.0 (226), the "newer" version is the override, and the old version will be the * generic basis for overrides. * * The reasoning is that overriding the "old" values causes the least surprise if you don't * override every value. * If this reasoning is faulty, the "atLeast" should probably switch to "lessThan" so that * the default noun is the latest one and the oldest/original one should be the override. * * This means the comparison is *reversed* * * @author stamm * @since 226.0 */ static final class NounVersionOverride implements Serializable, Comparable { private static final long serialVersionUID = 1L; private final Noun noun; private final double atLeast; public NounVersionOverride(Noun noun, double atLeast) { this.noun = noun; this.atLeast = atLeast; } /** * @return the noun override. */ public Noun getNoun() { return noun; } /** * @return the version that should be returned if the api version on RenmaingProvider is * at least this value. */ public double getAtLeast() { return atLeast; } @Override public int hashCode() { return Objects.hash(atLeast, noun); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; NounVersionOverride other = (NounVersionOverride) obj; return Double.doubleToLongBits(atLeast) == Double.doubleToLongBits(other.atLeast) && Objects.equals(noun, other.noun); } @Override public int compareTo(NounVersionOverride o) { // comparison is reversed... so you can go in order from newest to oldest and ask atLeast return Double.compare(o.getAtLeast(), atLeast); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy