OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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

Rev Author Line No. Line
67 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.io.IOException;
19
import java.io.InputStream;
20
import java.util.ArrayList;
21
import java.util.Collections;
22
import java.util.HashMap;
23
import java.util.List;
24
import java.util.Locale;
25
import java.util.Map;
26
import java.util.ResourceBundle.Control;
27
 
174 ilm 28
import javax.xml.XMLConstants;
67 ilm 29
import javax.xml.parsers.DocumentBuilder;
30
import javax.xml.parsers.DocumentBuilderFactory;
31
 
32
import org.w3c.dom.Document;
33
import org.w3c.dom.Element;
34
import org.w3c.dom.NodeList;
35
 
174 ilm 36
import net.jcip.annotations.GuardedBy;
37
import net.jcip.annotations.ThreadSafe;
38
 
73 ilm 39
@ThreadSafe
67 ilm 40
public class TranslationManager {
41
    private static final Locale FALLBACK_LOCALE = Locale.ENGLISH;
42
 
73 ilm 43
    private static final Control CONTROL = new I18nUtils.SameLanguageControl() {
67 ilm 44
        @Override
45
        public Locale getFallbackLocale(String baseName, Locale locale) {
46
            if (!locale.equals(FALLBACK_LOCALE))
47
                return FALLBACK_LOCALE;
48
            return null;
49
        }
50
    };
51
 
73 ilm 52
    public static final Control getControl() {
53
        return CONTROL;
54
    }
55
 
67 ilm 56
    private static final String BASENAME = "translation";
57
    private static final TranslationManager instance = new TranslationManager();
58
 
59
    public static final TranslationManager getInstance() {
60
        return instance;
61
    }
62
 
73 ilm 63
    @GuardedBy("classes")
64
    private final List<Class<?>> classes;
65
    @GuardedBy("classes")
67 ilm 66
    private Locale locale;
73 ilm 67
 
174 ilm 68
    private final Object trMutex = new Object();
73 ilm 69
    @GuardedBy("trMutex")
67 ilm 70
    private Map<String, String> menuTranslation;
73 ilm 71
    @GuardedBy("trMutex")
67 ilm 72
    private Map<String, String> itemTranslation;
73 ilm 73
    @GuardedBy("trMutex")
67 ilm 74
    private Map<String, String> actionTranslation;
75
 
76
    private TranslationManager() {
174 ilm 77
        this.classes = new ArrayList<>();
67 ilm 78
    }
79
 
73 ilm 80
    public void addTranslationStreamFromClass(Class<?> c) {
81
        synchronized (this.classes) {
82
            this.classes.add(c);
83
            if (this.getLocale() != null) {
84
                loadTranslation(this.getLocale(), c);
85
            }
67 ilm 86
        }
87
    }
88
 
73 ilm 89
    public void removeTranslationStreamFromClass(Class<?> c) {
90
        synchronized (this.classes) {
91
            if (this.classes.remove(c) && this.getLocale() != null) {
92
                loadAllTranslation();
93
            }
94
        }
95
    }
96
 
97
    public final Locale getLocale() {
98
        synchronized (this.classes) {
99
            return this.locale;
100
        }
101
    }
102
 
103
    public final void setLocale(Locale l) {
67 ilm 104
        if (l == null)
105
            throw new NullPointerException("null Locale");
73 ilm 106
        synchronized (this.classes) {
107
            if (!l.equals(this.locale)) {
108
                this.locale = l;
109
                loadAllTranslation();
110
            }
67 ilm 111
        }
112
    }
113
 
114
    private void checkNulls(String id, String label) {
115
        if (id == null)
116
            throw new NullPointerException("null id");
117
        if (label == null)
118
            throw new NullPointerException("null label");
119
    }
120
 
121
    // Menus
122
 
123
    public String getTranslationForMenu(String id) {
73 ilm 124
        synchronized (this.trMutex) {
125
            return this.menuTranslation.get(id);
126
        }
67 ilm 127
    }
128
 
129
    public void setTranslationForMenu(String id, String label) {
130
        checkNulls(id, label);
73 ilm 131
        synchronized (this.trMutex) {
132
            this.menuTranslation.put(id, label);
133
        }
67 ilm 134
    }
135
 
136
    // Items
137
 
138
    public String getTranslationForItem(String id) {
73 ilm 139
        synchronized (this.trMutex) {
140
            return this.itemTranslation.get(id);
141
        }
67 ilm 142
    }
143
 
144
    public void setTranslationForItem(String id, String label) {
145
        checkNulls(id, label);
73 ilm 146
        synchronized (this.trMutex) {
147
            this.itemTranslation.put(id, label);
148
        }
67 ilm 149
    }
150
 
151
    // Actions
152
 
153
    public String getTranslationForAction(String id) {
73 ilm 154
        synchronized (this.trMutex) {
155
            return this.actionTranslation.get(id);
156
        }
67 ilm 157
    }
158
 
159
    public void setTranslationForAction(String id, String label) {
160
        checkNulls(id, label);
73 ilm 161
        synchronized (this.trMutex) {
162
            this.actionTranslation.put(id, label);
163
        }
67 ilm 164
    }
165
 
166
    private void loadAllTranslation() {
73 ilm 167
        synchronized (this.trMutex) {
174 ilm 168
            this.menuTranslation = new HashMap<>();
169
            this.itemTranslation = new HashMap<>();
170
            this.actionTranslation = new HashMap<>();
171
            if (this.classes.isEmpty()) {
73 ilm 172
                Log.get().warning("TranslationManager has no resources to load (" + this.getLocale() + ")");
173
            }
174
            for (Class<?> c : this.classes) {
83 ilm 175
                boolean loaded = loadTranslation(this.getLocale(), c);
176
                if (!loaded) {
177
                    Log.get().warning("TranslationManager was unable to load translation " + c.getCanonicalName() + " for locale " + this.getLocale());
178
                }
73 ilm 179
            }
67 ilm 180
        }
181
    }
182
 
183
    // return all existing (e.g fr_CA only specify differences with fr)
184
    private List<InputStream> findStream(final Locale locale, final Class<?> c, final boolean rootLast) {
185
        final Control cntrl = CONTROL;
174 ilm 186
        final List<InputStream> res = new ArrayList<>();
67 ilm 187
        final String baseName = c.getPackage().getName() + "." + BASENAME;
188
 
189
        // test emptiness to not mix languages
190
        for (Locale targetLocale = locale; targetLocale != null && res.isEmpty(); targetLocale = cntrl.getFallbackLocale(baseName, targetLocale)) {
191
            for (final Locale candidate : cntrl.getCandidateLocales(baseName, targetLocale)) {
192
                final InputStream ins = c.getClassLoader().getResourceAsStream(cntrl.toResourceName(cntrl.toBundleName(baseName, candidate), "xml"));
193
                if (ins != null)
194
                    res.add(ins);
195
            }
196
        }
197
        if (!rootLast)
198
            Collections.reverse(res);
199
        return res;
200
    }
201
 
83 ilm 202
    private boolean loadTranslation(final Locale l, Class<?> c) {
203
        boolean translationLoaded = false;
67 ilm 204
        // we want more specific translations to replace general ones, i.e. root Locale first
205
        for (final InputStream input : findStream(l, c, false)) {
206
            // create new instances to check if there's no duplicates in each resource
174 ilm 207
            final Map<String, String> menuTranslation = new HashMap<>(), itemTranslation = new HashMap<>(), actionTranslation = new HashMap<>();
67 ilm 208
            loadTranslation(input, menuTranslation, itemTranslation, actionTranslation);
209
            // on the other hand, it's OK for one resource to override another
210
            this.menuTranslation.putAll(menuTranslation);
211
            this.itemTranslation.putAll(itemTranslation);
212
            this.actionTranslation.putAll(actionTranslation);
83 ilm 213
            translationLoaded = true;
67 ilm 214
        }
83 ilm 215
        return translationLoaded;
67 ilm 216
    }
217
 
174 ilm 218
    private static void loadTranslation(final InputStream input, final Map<String, String> menuTranslation, final Map<String, String> itemTranslation, final Map<String, String> actionTranslation) {
219
        // FIXME : l'implementation de Java est lente
220
        // com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl : 60 ms!
221
        // On pourrait passer à 1ms avec Piccolo...
67 ilm 222
        final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
223
        try {
174 ilm 224
            dbFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
225
            dbFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
67 ilm 226
            final DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
227
            final Document doc = dBuilder.parse(input);
228
            // Menus
229
            loadTranslation(doc, "menu", menuTranslation);
230
            // Items (title, labels not related to fields...)
231
            loadTranslation(doc, "item", itemTranslation);
232
            // Actions
233
            loadTranslation(doc, "action", actionTranslation);
234
        } catch (Exception e) {
235
            e.printStackTrace();
236
        } finally {
237
            try {
238
                if (input != null) {
239
                    input.close();
240
                }
241
            } catch (IOException e) {
242
                e.printStackTrace();
243
            }
244
        }
245
    }
246
 
174 ilm 247
    private static void loadTranslation(final Document doc, final String tagName, final Map<String, String> m) {
67 ilm 248
        final NodeList menuChildren = doc.getElementsByTagName(tagName);
249
        final int size = menuChildren.getLength();
250
        for (int i = 0; i < size; i++) {
251
            final Element element = (Element) menuChildren.item(i);
252
            final String id = element.getAttributeNode("id").getValue();
253
            final String label = element.getAttributeNode("label").getValue();
254
            if (m.containsKey(id)) {
255
                throw new IllegalStateException("Duplicate " + tagName + " translation entry for " + id + " (" + label + ")");
256
            }
257
            m.put(id, label);
258
        }
259
    }
260
}