OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 156 Rev 177
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.utils.i18n;
14
 package org.openconcerto.utils.i18n;
15
 
15
 
16
import org.openconcerto.utils.CollectionUtils;
16
import org.openconcerto.utils.CollectionUtils;
17
import org.openconcerto.utils.Log;
17
import org.openconcerto.utils.Log;
18
import org.openconcerto.utils.PropertiesUtils;
18
import org.openconcerto.utils.PropertiesUtils;
19
import org.openconcerto.utils.Tuple2;
19
import org.openconcerto.utils.Tuple2;
20
 
20
 
21
import java.beans.Introspector;
21
import java.beans.Introspector;
22
import java.io.IOException;
22
import java.io.IOException;
23
import java.util.ArrayList;
23
import java.util.ArrayList;
24
import java.util.Arrays;
24
import java.util.Arrays;
25
import java.util.Collections;
25
import java.util.Collections;
26
import java.util.HashMap;
26
import java.util.HashMap;
27
import java.util.LinkedHashMap;
27
import java.util.LinkedHashMap;
28
import java.util.List;
28
import java.util.List;
29
import java.util.Locale;
29
import java.util.Locale;
30
import java.util.Map;
30
import java.util.Map;
31
import java.util.MissingResourceException;
31
import java.util.MissingResourceException;
32
import java.util.Objects;
32
import java.util.Objects;
33
import java.util.Properties;
33
import java.util.Properties;
34
import java.util.regex.Matcher;
34
import java.util.regex.Matcher;
35
import java.util.regex.Pattern;
35
import java.util.regex.Pattern;
36
 
36
 
37
import com.ibm.icu.text.MessageFormat;
37
import com.ibm.icu.text.MessageFormat;
38
import com.ibm.icu.text.MessagePattern;
38
import com.ibm.icu.text.MessagePattern;
39
import com.ibm.icu.text.MessagePattern.Part;
39
import com.ibm.icu.text.MessagePattern.Part;
40
import com.ibm.icu.text.MessagePattern.Part.Type;
40
import com.ibm.icu.text.MessagePattern.Part.Type;
41
 
41
 
-
 
42
import net.jcip.annotations.ThreadSafe;
-
 
43
 
42
/**
44
/**
43
 * Translation manager. The translations are provided by {@link Translator} instances, they are
45
 * Translation manager. The translations are provided by {@link Translator} instances, they are
44
 * created either from a class ending in a language tag that implements it, or by properties files
46
 * created either from a class ending in a language tag that implements it, or by properties files
45
 * that must contain values that will be passed to {@link MessageFormat}. In the latter case,
47
 * that must contain values that will be passed to {@link MessageFormat}. In the latter case,
46
 * messages can reference {@link #createValue(Map, Object[], String) virtual named arguments}.
48
 * messages can reference {@link #createValue(Map, Object[], String) virtual named arguments}.
47
 * 
49
 * 
48
 * @author Sylvain
50
 * @author Sylvain
49
 * @see LocalizedInstances
51
 * @see LocalizedInstances
50
 */
52
 */
-
 
53
@ThreadSafe
51
public class TM {
54
public class TM {
52
 
55
 
53
    static public enum MissingMode {
56
    static public enum MissingMode {
54
        EXCEPTION {
57
        EXCEPTION {
55
            @Override
58
            @Override
56
            protected String returnMissing(TM tm, String key) throws MissingResourceException {
59
            protected String returnMissing(TM tm, String key) throws MissingResourceException {
57
                throw new MissingResourceException("Missing translation", tm.getBaseName(), key);
60
                throw new MissingResourceException("Missing translation", tm.getBaseName(), key);
58
            }
61
            }
59
        },
62
        },
60
        NULL {
63
        NULL {
61
            @Override
64
            @Override
62
            protected String returnMissing(TM tm, String key) {
65
            protected String returnMissing(TM tm, String key) {
63
                return null;
66
                return null;
64
            }
67
            }
65
        },
68
        },
66
        STRING {
69
        STRING {
67
            @Override
70
            @Override
68
            protected String returnMissing(TM tm, String key) {
71
            protected String returnMissing(TM tm, String key) {
69
                return '!' + key + '!';
72
                return '!' + key + '!';
70
            }
73
            }
71
        };
74
        };
72
 
75
 
73
        protected abstract String returnMissing(final TM tm, final String key) throws MissingResourceException;
76
        protected abstract String returnMissing(final TM tm, final String key) throws MissingResourceException;
74
 
77
 
75
        // method to avoid array allocation and Arrays.toString()
78
        // method to avoid array allocation and Arrays.toString()
76
        protected final String returnResult(final TM tm, final String res, final String key) throws MissingResourceException {
79
        protected final String returnResult(final TM tm, final String res, final String key) throws MissingResourceException {
77
            return res == null ? this.returnMissing(tm, key) : res;
80
            return res == null ? this.returnMissing(tm, key) : res;
78
        }
81
        }
79
 
82
 
80
        protected final String returnResult(final TM tm, final String res, final String... keys) throws MissingResourceException {
83
        protected final String returnResult(final TM tm, final String res, final String... keys) throws MissingResourceException {
81
            return res == null ? this.returnMissing(tm, Arrays.toString(keys)) : res;
84
            return res == null ? this.returnMissing(tm, Arrays.toString(keys)) : res;
82
        }
85
        }
83
    }
86
    }
84
 
87
 
85
    static public final String NOUN_CLASS_PROP = "nounClass";
88
    static public final String NOUN_CLASS_PROP = "nounClass";
86
    static {
89
    static {
87
        assert NOUN_CLASS_PROP.equals(Introspector.decapitalize(NounClass.class.getSimpleName()));
90
        assert NOUN_CLASS_PROP.equals(Introspector.decapitalize(NounClass.class.getSimpleName()));
88
    }
91
    }
89
 
92
 
90
    static private final MissingMode DEFAULT_MISSING_MODE = MissingMode.STRING;
93
    static private final MissingMode DEFAULT_MISSING_MODE = MissingMode.STRING;
91
 
94
 
92
    static private final TM INSTANCE = new TM();
95
    private static final TMPool<TM> POOL = new TMPool<TM>(TM::new);
93
    static private final Pattern splitPtrn = Pattern.compile("__", Pattern.LITERAL);
96
    static private final Pattern splitPtrn = Pattern.compile("__", Pattern.LITERAL);
94
    static private boolean USE_DYNAMIC_MAP = true;
97
    static private boolean USE_DYNAMIC_MAP = true;
95
 
98
 
96
    public static void setUseDynamicMap(boolean b) {
99
    public static synchronized void setUseDynamicMap(boolean b) {
97
        USE_DYNAMIC_MAP = b;
100
        USE_DYNAMIC_MAP = b;
98
    }
101
    }
99
 
102
 
100
    /**
103
    /**
101
     * Whether to use a {@link DynamicMap} or add all possible keys up front.
104
     * Whether to use a {@link DynamicMap} or add all possible keys up front.
102
     * <code>DynamicMap</code> is the default since it's faster : only required keys are computed,
105
     * <code>DynamicMap</code> is the default since it's faster : only required keys are computed,
103
     * otherwise every key that may be used must be computed. So if your pattern has a lot of
106
     * otherwise every key that may be used must be computed. So if your pattern has a lot of
104
     * plurals and choices it might make a difference. However <code>DynamicMap</code> is less
107
     * plurals and choices it might make a difference. However <code>DynamicMap</code> is less
105
     * robust, since it twists a little the definition of {@link Map}.
108
     * robust, since it twists a little the definition of {@link Map}.
106
     * 
109
     * 
107
     * @return <code>true</code> if <code>DynamicMap</code> should be used.
110
     * @return <code>true</code> if <code>DynamicMap</code> should be used.
108
     */
111
     */
109
    public static boolean useDynamicMap() {
112
    public static synchronized boolean useDynamicMap() {
110
        return USE_DYNAMIC_MAP;
113
        return USE_DYNAMIC_MAP;
111
    }
114
    }
112
 
115
 
-
 
116
    /**
-
 
117
     * The default locale for {@link #getInstance()}. Currently just {@link Locale#getDefault()}.
-
 
118
     * 
-
 
119
     * @return the default locale.
-
 
120
     */
-
 
121
    public static final Locale getDefaultLocale() {
-
 
122
        return Locale.getDefault();
-
 
123
    }
-
 
124
 
113
    static public TM getInstance() {
125
    static public TM getInstance() {
-
 
126
        return getInstance(getDefaultLocale());
-
 
127
    }
-
 
128
 
-
 
129
    static public TM getInstance(final Locale l) {
114
        return INSTANCE;
130
        return POOL.get(l);
-
 
131
    }
-
 
132
 
-
 
133
    static public String tr(final Locale l, final String key, final Object... args) {
-
 
134
        return getInstance(l).translate(key, args);
115
    }
135
    }
116
 
136
 
117
    static public String tr(final String key, final Object... args) {
137
    static public String tr(final String key, final Object... args) {
118
        return getInstance().translate(key, args);
138
        return getInstance().translate(key, args);
119
    }
139
    }
120
 
140
 
121
    private Locale locale;
141
    private Locale locale;
122
    private TranslatorChain translations;
142
    private TranslatorChain translations;
123
    private Locale translationsLocale;
143
    private Locale translationsLocale;
124
 
144
 
125
    protected TM() {
-
 
126
        init();
-
 
127
    }
-
 
128
 
-
 
129
    protected void init() {
145
    protected TM(final Locale locale) {
130
        setLocale(Locale.getDefault());
146
        setLocale(locale);
131
    }
147
    }
132
 
148
 
133
    public final void setLocale(final Locale locale) {
149
    private final void setLocale(final Locale locale) {
134
        this.locale = locale;
-
 
135
        final LocalizedInstances<Translator> localizedInstances = new LocalizedInstances<Translator>(Translator.class, TranslationManager.getControl()) {
150
        final LocalizedInstances<Translator> localizedInstances = new LocalizedInstances<Translator>(Translator.class, TranslationManager.getControl()) {
136
            @Override
151
            @Override
137
            protected Translator createInstance(final String bundleName, final Locale l, final Class<?> cl) throws IOException {
152
            protected Translator createInstance(final String bundleName, final Locale l, final Class<?> cl) throws IOException {
138
                final Properties props = PropertiesUtils.createFromResource(cl, '/' + this.getControl().toResourceName(bundleName, "properties"));
153
                final Properties props = PropertiesUtils.createFromResource(cl, '/' + this.getControl().toResourceName(bundleName, "properties"));
139
                if (props == null) {
154
                if (props == null) {
140
                    return null;
155
                    return null;
141
                } else {
156
                } else {
142
                    return new Translator() {
157
                    return new Translator() {
143
                        @Override
158
                        @Override
144
                        public String translate(final String key, final MessageArgs args) {
159
                        public String translate(final String key, final MessageArgs args) {
145
                            final String msg = props.getProperty(key);
160
                            final String msg = props.getProperty(key);
146
                            if (msg == null)
161
                            if (msg == null)
147
                                return null;
162
                                return null;
148
                            // replaceMap() handles virtual keys (e.g.
163
                            // replaceMap() handles virtual keys (e.g.
149
                            // element__pluralDefiniteArticle)
164
                            // element__pluralDefiniteArticle)
150
                            return new MessageFormat(msg, l).format(replaceMap(args, msg).getAll());
165
                            return new MessageFormat(msg, l).format(replaceMap(args, msg).getAll());
151
                        }
166
                        }
152
                    };
167
                    };
153
                }
168
                }
154
            };
169
            };
155
        };
170
        };
156
        final Tuple2<Locale, List<Translator>> createInstances = localizedInstances.createInstances(getBaseName(), locale);
171
        final Tuple2<Locale, List<Translator>> createInstances = localizedInstances.createInstances(getBaseName(), locale);
-
 
172
        synchronized (this) {
-
 
173
            this.locale = locale;
157
        this.translationsLocale = createInstances.get0();
174
            this.translationsLocale = createInstances.get0();
158
        this.translations = new TranslatorChain(createInstances.get1());
175
            this.translations = new TranslatorChain(createInstances.get1());
159
    }
176
        }
-
 
177
    }
160
 
178
 
161
    /**
179
    /**
162
     * The requested locale.
180
     * The requested locale.
163
     * 
181
     * 
164
     * @return the requested locale.
182
     * @return the requested locale.
165
     * @see #setLocale(Locale)
183
     * @see #setLocale(Locale)
166
     */
184
     */
167
    public final Locale getLocale() {
185
    public final synchronized Locale getLocale() {
168
        return this.locale;
186
        return this.locale;
169
    }
187
    }
170
 
188
 
171
    /**
189
    /**
172
     * The actual locale of the loaded translations.
190
     * The actual locale of the loaded translations.
173
     * 
191
     * 
174
     * @return the actual locale.
192
     * @return the actual locale.
175
     */
193
     */
176
    public final Locale getTranslationsLocale() {
194
    public final synchronized Locale getTranslationsLocale() {
177
        return this.translationsLocale;
195
        return this.translationsLocale;
178
    }
196
    }
179
 
197
 
-
 
198
    private final synchronized TranslatorChain getTranslations() {
-
 
199
        return this.translations;
-
 
200
    }
-
 
201
 
180
    protected String getBaseName() {
202
    protected String getBaseName() {
181
        return I18nUtils.getBaseName(this.getClass());
203
        return I18nUtils.getBaseName(this.getClass());
182
    }
204
    }
183
 
205
 
184
    // translate array
206
    // translate array
185
    public final String trA(final String key, final Object... args) {
207
    public final String trA(final String key, final Object... args) {
186
        return translate(key, args);
208
        return translate(key, args);
187
    }
209
    }
188
 
210
 
189
    public final String translate(final String key, final Object... args) {
211
    public final String translate(final String key, final Object... args) {
190
        return translate(DEFAULT_MISSING_MODE, key, args);
212
        return translate(DEFAULT_MISSING_MODE, key, args);
191
    }
213
    }
192
 
214
 
193
    // translate map
215
    // translate map
194
    public final String trM(final String key, final String name1, final Object arg1) {
216
    public final String trM(final String key, final String name1, final Object arg1) {
195
        return trM(key, Collections.singletonMap(name1, arg1));
217
        return trM(key, Collections.singletonMap(name1, arg1));
196
    }
218
    }
197
 
219
 
198
    public final String trM(final String key, final String name1, final Object arg1, final String name2, final Object arg2) {
220
    public final String trM(final String key, final String name1, final Object arg1, final String name2, final Object arg2) {
199
        return trM(key, CollectionUtils.createMap(name1, arg1, name2, arg2));
221
        return trM(key, CollectionUtils.createMap(name1, arg1, name2, arg2));
200
    }
222
    }
201
 
223
 
202
    public final String trM(final String key, final Map<String, ?> args) {
224
    public final String trM(final String key, final Map<String, ?> args) {
203
        return trM(DEFAULT_MISSING_MODE, key, args);
225
        return trM(DEFAULT_MISSING_MODE, key, args);
204
    }
226
    }
205
 
227
 
206
    public final String trM(final MissingMode mode, final String key, Map<String, ?> map) throws MissingResourceException {
228
    public final String trM(final MissingMode mode, final String key, Map<String, ?> map) throws MissingResourceException {
207
        return translate(mode, key, new MessageArgs(map));
229
        return translate(mode, key, new MessageArgs(map));
208
    }
230
    }
209
 
231
 
210
    public final String translate(final MissingMode mode, final String key, final Object... args) throws MissingResourceException {
232
    public final String translate(final MissingMode mode, final String key, final Object... args) throws MissingResourceException {
211
        return translate(mode, key, args.length == 0 ? MessageArgs.getEmpty() : new MessageArgs(args));
233
        return translate(mode, key, args.length == 0 ? MessageArgs.getEmpty() : new MessageArgs(args));
212
    }
234
    }
213
 
235
 
214
    public final String translateFirst(final MissingMode mode, final String... keys) throws MissingResourceException {
236
    public final String translateFirst(final MissingMode mode, final String... keys) throws MissingResourceException {
215
        return translateFirst(mode, MessageArgs.getEmpty(), keys);
237
        return translateFirst(mode, MessageArgs.getEmpty(), keys);
216
    }
238
    }
217
 
239
 
218
    /**
240
    /**
219
     * Return the first non-<code>null</code> result.
241
     * Return the first non-<code>null</code> result.
220
     * 
242
     * 
221
     * @param mode what to do if all keys are <code>null</code>.
243
     * @param mode what to do if all keys are <code>null</code>.
222
     * @param args the arguments.
244
     * @param args the arguments.
223
     * @param keys the keys to search for.
245
     * @param keys the keys to search for.
224
     * @return the first non-<code>null</code> result.
246
     * @return the first non-<code>null</code> result.
225
     * @throws MissingResourceException if {@link MissingMode#EXCEPTION} and all keys are
247
     * @throws MissingResourceException if {@link MissingMode#EXCEPTION} and all keys are
226
     *         <code>null</code>.
248
     *         <code>null</code>.
227
     */
249
     */
228
    public final String translateFirst(final MissingMode mode, final MessageArgs args, final String... keys) throws MissingResourceException {
250
    public final String translateFirst(final MissingMode mode, final MessageArgs args, final String... keys) throws MissingResourceException {
229
        String res = null;
251
        String res = null;
230
        for (int i = 0; i < keys.length && res == null; i++) {
252
        for (int i = 0; i < keys.length && res == null; i++) {
231
            final String key = keys[i];
253
            final String key = keys[i];
232
            if (key != null)
254
            if (key != null)
233
                res = this.translate(MissingMode.NULL, key, args);
255
                res = this.translate(MissingMode.NULL, key, args);
234
        }
256
        }
235
        return mode.returnResult(this, res, keys);
257
        return mode.returnResult(this, res, keys);
236
    }
258
    }
237
 
259
 
238
    private final String translate(final MissingMode mode, final String key, MessageArgs args) throws MissingResourceException {
260
    private final String translate(final MissingMode mode, final String key, MessageArgs args) throws MissingResourceException {
239
        Objects.requireNonNull(mode, "Null mode");
261
        Objects.requireNonNull(mode, "Null mode");
240
        Objects.requireNonNull(key, "Null key");
262
        Objects.requireNonNull(key, "Null key");
241
        Objects.requireNonNull(args, "Null arguments");
263
        Objects.requireNonNull(args, "Null arguments");
242
        final String res = this.translations.translate(key, args);
264
        final String res = this.getTranslations().translate(key, args);
243
        return mode.returnResult(this, res, key);
265
        return mode.returnResult(this, res, key);
244
    }
266
    }
245
 
267
 
246
    protected MessageArgs replaceMap(final MessageArgs args, final String msg) {
268
    protected MessageArgs replaceMap(final MessageArgs args, final String msg) {
247
        final MessageArgs res;
269
        final MessageArgs res;
248
        if (args.isMapPrimary()) {
270
        if (args.isMapPrimary()) {
249
            final Map<String, ?> map = args.getMap();
271
            final Map<String, ?> map = args.getMap();
250
            final Map<String, Object> copy;
272
            final Map<String, Object> copy;
251
            if (MessageArgs.isOrdered(map)) {
273
            if (MessageArgs.isOrdered(map)) {
252
                copy = new LinkedHashMap<String, Object>(map);
274
                copy = new LinkedHashMap<String, Object>(map);
253
            } else {
275
            } else {
254
                copy = new HashMap<String, Object>(map);
276
                copy = new HashMap<String, Object>(map);
255
            }
277
            }
256
            final Object[] array = args.getArray();
278
            final Object[] array = args.getArray();
257
            final Map<String, Object> newMap;
279
            final Map<String, Object> newMap;
258
            if (useDynamicMap()) {
280
            if (useDynamicMap()) {
259
                newMap = new DynamicMap<Object>(copy) {
281
                newMap = new DynamicMap<Object>(copy) {
260
                    @Override
282
                    @Override
261
                    protected Object createValueNonNull(String key) {
283
                    protected Object createValueNonNull(String key) {
262
                        return TM.this.createValue(this, array, key);
284
                        return TM.this.createValue(this, array, key);
263
                    }
285
                    }
264
                };
286
                };
265
            } else {
287
            } else {
266
                newMap = copy;
288
                newMap = copy;
267
                final MessagePattern messagePattern = new MessagePattern(msg);
289
                final MessagePattern messagePattern = new MessagePattern(msg);
268
                if (messagePattern.hasNamedArguments()) {
290
                if (messagePattern.hasNamedArguments()) {
269
                    final int countParts = messagePattern.countParts();
291
                    final int countParts = messagePattern.countParts();
270
                    String argName;
292
                    String argName;
271
                    for (int i = 0; i < countParts; i++) {
293
                    for (int i = 0; i < countParts; i++) {
272
                        final Part part = messagePattern.getPart(i);
294
                        final Part part = messagePattern.getPart(i);
273
                        if (part.getType() == Type.ARG_NAME && !newMap.containsKey(argName = messagePattern.getSubstring(part))) {
295
                        if (part.getType() == Type.ARG_NAME && !newMap.containsKey(argName = messagePattern.getSubstring(part))) {
274
                            final Object createValue = this.createValue(newMap, array, argName);
296
                            final Object createValue = this.createValue(newMap, array, argName);
275
                            if (createValue != null)
297
                            if (createValue != null)
276
                                newMap.put(argName, createValue);
298
                                newMap.put(argName, createValue);
277
                        }
299
                        }
278
                    }
300
                    }
279
                }
301
                }
280
            }
302
            }
281
            res = new MessageArgs(newMap);
303
            res = new MessageArgs(newMap);
282
        } else {
304
        } else {
283
            res = args;
305
            res = args;
284
        }
306
        }
285
        return res;
307
        return res;
286
    }
308
    }
287
 
309
 
288
    /**
310
    /**
289
     * Try to create a value for a missing key. The syntax of keys must be phraseName(__name)+ and
311
     * Try to create a value for a missing key. The syntax of keys must be phraseName(__name)+ and
290
     * if you need to have __ in a name it must be doubled (i.e. ____). <code>phraseName</code>, as
312
     * if you need to have __ in a name it must be doubled (i.e. ____). <code>phraseName</code>, as
291
     * its name implies, must reference an existing phrase in <code>map</code>. If this phrase is
313
     * its name implies, must reference an existing phrase in <code>map</code>. If this phrase is
292
     * suffixed by {@value #NOUN_CLASS_PROP} then the {@link NounClass#getName() name} of the noun
314
     * suffixed by {@value #NOUN_CLASS_PROP} then the {@link NounClass#getName() name} of the noun
293
     * class of the phrase is returned. Else this phrase and the list of <code>name</code> are
315
     * class of the phrase is returned. Else this phrase and the list of <code>name</code> are
294
     * passed to {@link Grammar#eval(Phrase, Number, List)}. The count is
316
     * passed to {@link Grammar#eval(Phrase, Number, List)}. The count is
295
     * <code>phraseNameCount</code> if it exists and is a {@link Number}, then <code>count</code>
317
     * <code>phraseNameCount</code> if it exists and is a {@link Number}, then <code>count</code>
296
     * else <code>null</code>.
318
     * else <code>null</code>.
297
     * 
319
     * 
298
     * @param map the current map.
320
     * @param map the current map.
299
     * @param objects the original map as an array.
321
     * @param objects the original map as an array.
300
     * @param key the missing key.
322
     * @param key the missing key.
301
     * @return its value, or <code>null</code> to leave the map unmodified.
323
     * @return its value, or <code>null</code> to leave the map unmodified.
302
     */
324
     */
303
    protected Object createValue(final Map<String, Object> map, final Object[] objects, final String key) {
325
    protected Object createValue(final Map<String, Object> map, final Object[] objects, final String key) {
304
        if (key.length() == 1 && Character.isDigit(key.charAt(0))) {
326
        if (key.length() == 1 && Character.isDigit(key.charAt(0))) {
305
            final int index = Integer.parseInt(key);
327
            final int index = Integer.parseInt(key);
306
            if (index < objects.length) {
328
            if (index < objects.length) {
307
                return objects[index];
329
                return objects[index];
308
            } else {
330
            } else {
309
                Log.get().warning("Only " + objects.length + " arguments : " + index);
331
                Log.get().warning("Only " + objects.length + " arguments : " + index);
310
                return null;
332
                return null;
311
            }
333
            }
312
        }
334
        }
313
 
335
 
314
        final Matcher m = splitPtrn.matcher(key);
336
        final Matcher m = splitPtrn.matcher(key);
315
        final String pattern = splitPtrn.pattern();
337
        final String pattern = splitPtrn.pattern();
316
        final int patternL = pattern.length();
338
        final int patternL = pattern.length();
317
        final StringBuffer sb = new StringBuffer(key.length());
339
        final StringBuffer sb = new StringBuffer(key.length());
318
        final List<String> l = new ArrayList<String>();
340
        final List<String> l = new ArrayList<String>();
319
        int pos = 0;
341
        int pos = 0;
320
        while (m.find(pos)) {
342
        while (m.find(pos)) {
321
            // double to escape pattern
343
            // double to escape pattern
322
            if (key.length() >= m.end() + patternL && pattern.equals(key.substring(m.end(), m.end() + patternL))) {
344
            if (key.length() >= m.end() + patternL && pattern.equals(key.substring(m.end(), m.end() + patternL))) {
323
                // go to the end to include one
345
                // go to the end to include one
324
                sb.append(key.substring(pos, m.end()));
346
                sb.append(key.substring(pos, m.end()));
325
                // and set pos after the second one
347
                // and set pos after the second one
326
                pos = m.end() + patternL;
348
                pos = m.end() + patternL;
327
            } else {
349
            } else {
328
                sb.append(key.substring(pos, m.start()));
350
                sb.append(key.substring(pos, m.start()));
329
                l.add(sb.toString());
351
                l.add(sb.toString());
330
                sb.setLength(0);
352
                sb.setLength(0);
331
                pos = m.end();
353
                pos = m.end();
332
            }
354
            }
333
        }
355
        }
334
        sb.append(key.substring(pos));
356
        sb.append(key.substring(pos));
335
        l.add(sb.toString());
357
        l.add(sb.toString());
336
 
358
 
337
        final String first = CollectionUtils.getFirst(l);
359
        final String first = CollectionUtils.getFirst(l);
338
        // at least the whole key
360
        // at least the whole key
339
        assert first != null;
361
        assert first != null;
340
        final Object firstObj = handleGet(map, first);
362
        final Object firstObj = handleGet(map, first);
341
        final Phrase phrase = firstObj instanceof Phrase ? (Phrase) firstObj : null;
363
        final Phrase phrase = firstObj instanceof Phrase ? (Phrase) firstObj : null;
342
        if (phrase != null && l.size() == 2 && NOUN_CLASS_PROP.equals(l.get(1))) {
364
        if (phrase != null && l.size() == 2 && NOUN_CLASS_PROP.equals(l.get(1))) {
343
            if (phrase.getNounClass() == null) {
365
            if (phrase.getNounClass() == null) {
344
                Log.get().warning("No noun class for " + phrase);
366
                Log.get().warning("No noun class for " + phrase);
345
                return phrase.getBase();
367
                return phrase.getBase();
346
            } else {
368
            } else {
347
                return phrase.getNounClass().getName();
369
                return phrase.getNounClass().getName();
348
            }
370
            }
349
        } else if (phrase != null && phrase.getGrammar() != null) {
371
        } else if (phrase != null && phrase.getGrammar() != null) {
350
            Object countObj = handleGet(map, first + "Count");
372
            Object countObj = handleGet(map, first + "Count");
351
            if (!(countObj instanceof Number))
373
            if (!(countObj instanceof Number))
352
                countObj = handleGet(map, "count");
374
                countObj = handleGet(map, "count");
353
            final Number count = countObj instanceof Number ? (Number) countObj : null;
375
            final Number count = countObj instanceof Number ? (Number) countObj : null;
354
            return phrase.getGrammar().eval(phrase, count, l.subList(1, l.size()));
376
            return phrase.getGrammar().eval(phrase, count, l.subList(1, l.size()));
355
        } else if (phrase != null) {
377
        } else if (phrase != null) {
356
            Log.get().warning("While splitting " + key + ", " + first + " is a Phrase without grammar : " + phrase);
378
            Log.get().warning("While splitting " + key + ", " + first + " is a Phrase without grammar : " + phrase);
357
            return phrase.getBase();
379
            return phrase.getBase();
358
        } else {
380
        } else {
359
            Log.get().warning("While splitting " + key + " : " + first + " isn't a Phrase");
381
            Log.get().warning("While splitting " + key + " : " + first + " isn't a Phrase");
360
            return null;
382
            return null;
361
        }
383
        }
362
    }
384
    }
363
 
385
 
364
    private final Object handleGet(final Map<String, Object> map, final String key) {
386
    private final Object handleGet(final Map<String, Object> map, final String key) {
365
        if (map instanceof DynamicMap)
387
        if (map instanceof DynamicMap)
366
            return ((DynamicMap<Object>) map).handleGet(key);
388
            return ((DynamicMap<Object>) map).handleGet(key);
367
        else
389
        else
368
            return map.get(key);
390
            return map.get(key);
369
    }
391
    }
370
}
392
}