OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 73 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
73 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils.i18n;
15
 
16
import org.openconcerto.utils.Log;
17
 
18
import java.util.HashMap;
19
import java.util.HashSet;
20
import java.util.Map;
156 ilm 21
import java.util.Objects;
73 ilm 22
import java.util.Set;
23
 
156 ilm 24
import com.ibm.icu.text.MessageFormat;
25
import com.ibm.icu.text.MessagePattern;
26
 
73 ilm 27
import net.jcip.annotations.GuardedBy;
28
import net.jcip.annotations.ThreadSafe;
29
 
30
/**
156 ilm 31
 * A phrase and its declension. E.g. "light bulb or electrical outlet" and "light bulbs or
32
 * electrical outlets".
73 ilm 33
 *
34
 * @author Sylvain
35
 * @see <a href="Wikipedia">http://en.wikipedia.org/wiki/Declension</a>
36
 */
37
@ThreadSafe
38
public class Phrase {
39
 
40
    static public final Phrase getInvariant(final String s) {
41
        return new Phrase(null, s, null);
42
    }
43
 
44
    private final Grammar grammar;
45
    private final String base;
46
    private final NounClass nounClass;
47
    @GuardedBy("this")
48
    private final Map<Object, String> variants;
49
    @GuardedBy("this")
156 ilm 50
    private final Set<VariantKey> explicitVariants;
73 ilm 51
 
52
    public Phrase(Grammar grammar, String base, NounClass nounClass) {
53
        super();
54
        if (base == null)
55
            throw new NullPointerException("null base");
56
        this.base = base;
57
        this.grammar = grammar;
58
        this.nounClass = nounClass;
59
        if (grammar == null && nounClass == null) {
60
            this.variants = null;
61
            this.explicitVariants = null;
62
        } else {
63
            this.variants = new HashMap<Object, String>();
64
            this.variants.put(null, this.getBase());
156 ilm 65
            this.explicitVariants = new HashSet<>();
73 ilm 66
        }
67
    }
68
 
69
    public final Grammar getGrammar() {
70
        return this.grammar;
71
    }
72
 
73
    public final String getBase() {
74
        return this.base;
75
    }
76
 
77
    public final NounClass getNounClass() {
78
        return this.nounClass;
79
    }
80
 
81
    /**
82
     * Put a variant. Should only be necessary for irregular variants.
83
     *
84
     * @param key which variant, e.g. plural.
85
     * @param variant the value, e.g. feet.
86
     * @return the previous value.
87
     */
88
    public final String putVariant(final VariantKey key, final String variant) {
89
        return this.putVariant(key, variant, true);
90
    }
91
 
156 ilm 92
    /**
93
     * Put a variant only if needed, i.e. if {@link #getVariant(VariantKey)} doesn't already return
94
     * <code>variant</code>. This is useful to keep {@link #getExplicitVariants()} to a minimum.
95
     *
96
     * @param key which variant, e.g. plural.
97
     * @param variant the value, e.g. feet.
98
     * @return <code>true</code> if the variant was put.
99
     */
100
    public final synchronized boolean putVariantIfDifferent(final VariantKey key, final String variant) {
101
        final boolean diff = !variant.equals(this.getVariant(key));
102
        if (diff) {
103
            this.putVariant(key, variant);
104
        }
105
        return diff;
106
    }
107
 
73 ilm 108
    private final synchronized String putVariant(final VariantKey key, final String variant, final boolean explicit) {
109
        final String res = this.variants.put(key, variant);
110
        if (explicit) {
111
            this.explicitVariants.add(key);
112
            // remove computed variants
113
            this.variants.keySet().retainAll(this.explicitVariants);
114
        }
115
        return res;
116
    }
117
 
118
    /**
156 ilm 119
     * The variants that have been explicitly set by {@link #putVariant(VariantKey, String)}, as
120
     * opposed to variants computed automatically by the {@link #getGrammar() grammar}.
121
     *
122
     * @return all explicit variants.
123
     */
124
    public synchronized Set<VariantKey> getExplicitVariants() {
125
        return this.explicitVariants == null ? null : new HashSet<>(this.explicitVariants);
126
    }
127
 
128
    /**
73 ilm 129
     * Get a variant. If the asked variant wasn't put by {@link #putVariant(VariantKey, String)},
130
     * the {@link #getGrammar() grammar} is {@link Grammar#getVariant(Phrase, VariantKey) used}.
131
     *
132
     * @param key which variant.
133
     * @return the asked variant.
134
     */
135
    public final synchronized String getVariant(final VariantKey key) {
136
        if (this.variants == null) {
137
            return this.getBase();
138
        } else {
139
            String res = this.variants.get(key);
140
            if (res == null) {
141
                res = this.getGrammar().getVariant(this, key);
142
                if (res == null) {
143
                    Log.get().warning("No variant " + key + " for " + this);
144
                    res = this.getBase();
145
                } else {
146
                    this.putVariant(key, res, false);
147
                }
148
            }
149
            return res;
150
        }
151
    }
152
 
153
    /**
154
     * Get a variant with a number. This method calls {@link #getVariant(VariantKey)} which should
155
     * return a {@link MessagePattern pattern}, the only parameter (0) is the count.
156
     *
157
     * @param count the count, 1 or 3.
158
     * @param key which variant, e.g. {@link Grammar#INDEFINITE_NUMERAL}.
159
     * @return the asked variant, e.g. "1 foot" or "3 feet".
160
     */
161
    public final synchronized String getNumeralVariant(final int count, final VariantKey key) {
162
        if (this.variants == null) {
163
            return count + " " + this.getBase();
164
        } else {
165
            return new MessageFormat(getVariant(key), getGrammar().getLocale()).format(new Object[] { count });
166
        }
167
    }
168
 
169
    @Override
156 ilm 170
    public synchronized int hashCode() {
171
        return Objects.hash(this.base, this.explicitVariants, this.grammar, this.nounClass);
172
    }
173
 
174
    @Override
175
    public synchronized boolean equals(Object obj) {
176
        if (this == obj)
177
            return true;
178
        if (obj == null)
179
            return false;
180
        if (getClass() != obj.getClass())
181
            return false;
182
        final Phrase o = (Phrase) obj;
183
        final boolean fast = Objects.equals(this.base, o.base) && Objects.equals(this.explicitVariants, o.explicitVariants) && Objects.equals(this.grammar, o.grammar)
184
                && Objects.equals(this.nounClass, o.nounClass);
185
        if (!fast || this.variants == null)
186
            return fast;
187
        for (final VariantKey e : this.explicitVariants) {
188
            if (!Objects.equals(this.variants.get(e), o.variants.get(e)))
189
                return false;
190
        }
191
        return true;
192
    }
193
 
194
    @Override
73 ilm 195
    public String toString() {
196
        final String cl = this.getNounClass() == null ? " " : " (" + this.getNounClass().getName() + ") ";
197
        final String gr = this.getGrammar() == null ? "" : " with " + this.getGrammar();
198
        return this.getClass().getSimpleName() + cl + this.getBase() + gr;
199
    }
200
}