/** * This file is part of Skript. * * Skript is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Skript is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Skript. If not, see <http://www.gnu.org/licenses/>. * * * Copyright 2011-2017 Peter Güttinger and contributors */ package ch.njol.skript.localization; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.Nullable; import ch.njol.skript.Skript; import ch.njol.skript.aliases.Aliases; import ch.njol.skript.localization.Language.LanguageListenerPriority; import ch.njol.util.NonNullPair; import ch.njol.util.StringUtils; /** * @author Peter Güttinger */ public class Noun extends Message { public final static String GENDERS_SECTION = "genders."; // TODO remove NO_GENDER and add boolean/flag uncountable (e.g. Luft: 'die Luft', aber nicht 'eine Luft') public final static int PLURAL = -2, NO_GENDER = -3; // -1 is sometimes used as 'not set' public final static String PLURAL_TOKEN = "x", NO_GENDER_TOKEN = "-"; @Nullable private String singular, plural; private int gender = 0; public Noun(final String key) { super(key); } @Override protected void onValueChange() { String value = getValue(); if (value == null) { plural = singular = key; gender = 0; return; } final int g = value.lastIndexOf('@'); if (g != -1) { gender = getGender("" + value.substring(g + 1).trim(), key); value = "" + value.substring(0, g).trim(); } else { gender = 0; } final NonNullPair<String, String> p = Noun.getPlural(value); singular = p.getFirst(); plural = p.getSecond(); if (gender == PLURAL && !Objects.equals(singular, plural)) Skript.warning("Noun '" + key + "' is of gender 'plural', but has different singular and plural values."); } @Override public String toString() { validate(); return "" + singular; } public String toString(final boolean plural) { validate(); return plural ? "" + this.plural : "" + singular; } public String withIndefiniteArticle() { return toString(Language.F_INDEFINITE_ARTICLE); } public String getIndefiniteArticle() { validate(); return gender == PLURAL || gender == NO_GENDER ? "" : "" + indefiniteArticles.get(gender); } public String withDefiniteArticle() { return toString(Language.F_DEFINITE_ARTICLE); } public String withDefiniteArticle(final boolean plural) { return toString(Language.F_DEFINITE_ARTICLE | (plural ? Language.F_PLURAL : 0)); } public String getDefiniteArticle() { validate(); return gender == PLURAL ? definitePluralArticle : gender == NO_GENDER ? "" : "" + definiteArticles.get(gender); } public int getGender() { validate(); return gender; } /** * Returns the article appropriate for the given gender & flags. * * @param flags * @return The article with a trailing space (as no article is possible in which case the empty string is returned) */ public static String getArticleWithSpace(final int gender, final int flags) { if (gender == PLURAL) { if ((flags & Language.F_DEFINITE_ARTICLE) != 0) return definitePluralArticle + " "; } else if (gender == NO_GENDER) { // nothing } else if ((flags & Language.F_DEFINITE_ARTICLE) != 0) { if (gender < 0 || gender >= definiteArticles.size()) { assert false : gender; return ""; } return definiteArticles.get(gender) + " "; } else if ((flags & Language.F_INDEFINITE_ARTICLE) != 0) { if (gender < 0 || gender >= indefiniteArticles.size()) { assert false : gender; return ""; } return indefiniteArticles.get(gender) + " "; } return ""; } /** * @param flags * @return <tt>{@link #getArticleWithSpace(int, int) getArticleWithSpace}(getGender(), flags)</tt> */ public final String getArticleWithSpace(final int flags) { return getArticleWithSpace(getGender(), flags); } public String toString(final int flags) { validate(); final StringBuilder b = new StringBuilder(); b.append(getArticleWithSpace(flags)); b.append((flags & Language.F_PLURAL) != 0 ? plural : singular); return "" + b.toString(); } public String withAmount(final double amount) { validate(); return Skript.toString(amount) + " " + (amount == 1 ? singular : plural); } public String withAmount(final double amount, final int flags) { validate(); if (amount == 1) { if (gender == NO_GENDER) return toString((flags & Language.F_PLURAL) != 0); if (gender == PLURAL) { if ((flags & Language.F_DEFINITE_ARTICLE) != 0) return definitePluralArticle + " " + plural; return "" + plural; } if ((flags & Language.F_DEFINITE_ARTICLE) != 0) return (flags & Language.F_PLURAL) != 0 ? definitePluralArticle + " " + plural : definiteArticles.get(gender) + " " + singular; if ((flags & Language.F_INDEFINITE_ARTICLE) != 0) return indefiniteArticles.get(gender) + " " + singular; if ((flags & Language.F_PLURAL) != 0) return "" + plural; } return Skript.toString(amount) + " " + (amount == 1 ? singular : plural); } public String toString(final Adjective a, final int flags) { validate(); final StringBuilder b = new StringBuilder(); b.append(getArticleWithSpace(flags)); b.append(a.toString(gender, flags)); b.append(" "); b.append((flags & Language.F_PLURAL) != 0 ? plural : singular); return "" + b.toString(); } public String toString(final Adjective[] adjectives, final int flags, final boolean and) { validate(); if (adjectives.length == 0) return toString(flags); final StringBuilder b = new StringBuilder(); b.append(getArticleWithSpace(flags)); b.append(Adjective.toString(adjectives, getGender(), flags, and)); b.append(" "); b.append(toString(flags)); return "" + b.toString(); } public String getSingular() { validate(); return "" + singular; } public String getPlural() { validate(); return "" + plural; } /** * @param s String with ¦ plural markers but without a @gender * @return (singular, plural) */ public static NonNullPair<String, String> getPlural(final String s) { final NonNullPair<String, String> r = new NonNullPair<>("", ""); int part = 3; // 1 = singular, 2 = plural, 3 = both int i = StringUtils.count(s, '¦'); int last = 0, c = -1; while ((c = s.indexOf('¦', c + 1)) != -1) { final String x = s.substring(last, c).trim(); if ((part & 1) != 0) r.setFirst(r.getFirst() + x); if ((part & 2) != 0) r.setSecond(r.getSecond() + x); part = i >= 2 ? (part % 3) + 1 : (part == 2 ? 3 : 2); last = c + 1; i--; } final String x = s.substring(last); if ((part & 1) != 0) r.setFirst(r.getFirst() + x); if ((part & 2) != 0) r.setSecond(r.getSecond() + x); return r; } /** * Normalizes plural markers, i.e. increases the total number of markers to a multiple of 3 without changing the string's meaning. * <p> * A @gender at the end of the string will be treated correctly. * * @param s Some string * @return The same string with normalized plural markers */ public static String normalizePluralMarkers(final String s) { final int c = StringUtils.count(s, '¦'); if (c % 3 == 0) return s; if (c % 3 == 2) { final int g = s.lastIndexOf('@'); if (g == -1) return s + "¦"; return s.substring(0, g) + "¦" + s.substring(g); } final int x = s.lastIndexOf('¦'); final int g = s.lastIndexOf('@'); if (g == -1) return s.substring(0, x) + "¦" + s.substring(x) + "¦"; return s.substring(0, x) + "¦" + s.substring(x, g) + "¦" + s.substring(g); } final static HashMap<String, Integer> genders = new HashMap<>(); /** * @param gender Gender id as defined in [language].lang (i.e. without the leading @) * @param key Key to use in error messages§ * @return The gender's id */ public static int getGender(final String gender, final String key) { if (gender.equalsIgnoreCase(PLURAL_TOKEN)) return PLURAL; if (gender.equalsIgnoreCase(NO_GENDER_TOKEN)) return NO_GENDER; final Integer i = genders.get(gender); if (i != null) return i; Skript.warning("Undefined gender '" + gender + "' at " + key); return 0; } @Nullable public static String getGenderID(final int gender) { if (gender == PLURAL) return PLURAL_TOKEN; if (gender == NO_GENDER) return NO_GENDER_TOKEN; return (Language.useLocal && Language.localized != null ? Language.localized : Language.english).get("genders." + gender + ".id"); } /** * Strips the gender identifier from given string and returns the used * gender. Used for aliases. * * @param s String. * @param key Key to report in case of error. * @return (stripped string, gender or -1 if none) */ public static NonNullPair<String, Integer> stripGender(String s, final String key) { final int c = s.lastIndexOf('@'); int g = -1; if (c != -1) { g = getGender("" + s.substring(c + 1).trim(), key); s = "" + s.substring(0, c).trim(); } return new NonNullPair<>(s, g); } final static List<String> indefiniteArticles = new ArrayList<>(3); final static List<String> definiteArticles = new ArrayList<>(3); static String definitePluralArticle = ""; final static List<String> localIndefiniteArticles = new ArrayList<>(3); final static List<String> localDefiniteArticles = new ArrayList<>(3); static String localDefinitePluralArticle = ""; static { Language.addListener(new LanguageChangeListener() { @Override public void onLanguageChange() { Map<String, String> lang = Language.useLocal ? Language.localized : Language.english; if (lang == null) lang = Language.english; genders.clear(); indefiniteArticles.clear(); definiteArticles.clear(); for (int i = 0; i < 100; i++) { final String g = lang.get(GENDERS_SECTION + i + ".id"); if (g == null) break; if (g.equalsIgnoreCase(PLURAL_TOKEN) || g.equalsIgnoreCase(NO_GENDER_TOKEN)) { Skript.error("gender #" + i + " uses a reserved character as ID, please use something different!"); continue; } genders.put(g, i); final String ia = lang.get(GENDERS_SECTION + i + ".indefinite article"); indefiniteArticles.add(ia == null ? "" : ia); final String da = lang.get(GENDERS_SECTION + i + ".definite article"); definiteArticles.add(da == null ? "" : da); } if (genders.isEmpty()) { Skript.error("No genders defined in language file " + Language.getName() + ".lang!"); indefiniteArticles.add(""); definiteArticles.add(""); } final String dpa = lang.get(GENDERS_SECTION + "plural.definite article"); if (dpa == null) Skript.error("Missing entry '" + GENDERS_SECTION + "plural.definite article' in the " + Language.getName() + " language file!"); definitePluralArticle = dpa == null ? "" : dpa; if (Language.useLocal || localIndefiniteArticles.isEmpty()) { localIndefiniteArticles.clear(); localIndefiniteArticles.addAll(indefiniteArticles); localDefiniteArticles.clear(); localDefiniteArticles.addAll(definiteArticles); localDefinitePluralArticle = definitePluralArticle; } } }, LanguageListenerPriority.EARLIEST); } public static String stripIndefiniteArticle(final String s) { for (final String a : indefiniteArticles) { if (StringUtils.startsWithIgnoreCase(s, a + " ")) return "" + s.substring(a.length() + 1); } return s; } public static boolean isIndefiniteArticle(final String s) { return indefiniteArticles.contains(s.toLowerCase()); } public static boolean isLocalIndefiniteArticle(final String s) { return localIndefiniteArticles.contains(s.toLowerCase()); } public static boolean isDefiniteArticle(final String s) { return definiteArticles.contains(s.toLowerCase()) || definitePluralArticle.equalsIgnoreCase(s); } public static boolean isLocalDefiniteArticle(final String s) { return localDefiniteArticles.contains(s.toLowerCase()) || localDefinitePluralArticle.equalsIgnoreCase(s); } public static String toString(final String singular, final String plural, final int gender, final int flags) { return getArticleWithSpace(flags, gender) + ((flags & Language.F_PLURAL) != 0 ? plural : singular); } }