OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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