KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > i18n > form > I18nServiceImpl


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.i18n.form;
21
22 import java.awt.Component JavaDoc;
23 import java.awt.Dialog JavaDoc;
24 import java.awt.event.ActionEvent JavaDoc;
25 import java.awt.event.ActionListener JavaDoc;
26 import java.beans.PropertyEditor JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.util.*;
29 import org.openide.ErrorManager;
30 import org.openide.DialogDisplayer;
31 import org.openide.filesystems.FileObject;
32 import org.openide.filesystems.FileUtil;
33 import org.openide.loaders.*;
34 import org.openide.cookies.SaveCookie;
35 import org.openide.cookies.EditorCookie;
36 import org.openide.DialogDescriptor;
37 import org.openide.util.NbBundle;
38 import org.netbeans.api.java.classpath.ClassPath;
39 import org.netbeans.api.java.project.JavaProjectConstants;
40 import org.netbeans.api.project.FileOwnerQuery;
41 import org.netbeans.api.project.Project;
42 import org.netbeans.api.project.ProjectUtils;
43 import org.netbeans.api.project.SourceGroup;
44 import org.netbeans.api.project.Sources;
45 import org.netbeans.modules.properties.*;
46 import org.netbeans.modules.i18n.*;
47 import org.netbeans.modules.i18n.java.JavaResourceHolder;
48 import org.netbeans.modules.form.I18nService;
49 import org.netbeans.modules.form.I18nValue;
50
51 /**
52  * Implementation of form module's I18nService - used by form editor to control
53  * internationalization of forms while i18n module owns all the technical means
54  * (i18n values, property editors, bundle files).
55  */

56 public class I18nServiceImpl implements I18nService {
57
58     // remembered original state for changes made for given source data objects
59
// mapping source DO to a map of properties DO to ChangeInfo
60
private Map/*<DataObject, Map<DataObject, ChangeInfo>>*/ changesMap = new HashMap();
61
62     private static class ChangeInfo {
63         Map/*<String, Object]>*/ changed = new HashMap(); // for each key holds all data across all locales
64
Set/*<String>*/ added = new HashSet(); // holds keys that were not originally present
65
}
66
67     /**
68      * Creates I18nValue object for given key and value. Should not be added
69      * to the bundle file yet. (For that purpose 'update' method is called later.)
70      */

71     public I18nValue create(String JavaDoc key, String JavaDoc value, DataObject srcDataObject) {
72         FormI18nString i18nString = new FormI18nString(srcDataObject);
73         i18nString.setKey(key);
74         i18nString.setValue(value);
75         return i18nString;
76     }
77
78     /**
79      * Creates a new I18nValue object with a new key. Should do no changes to
80      * the bundle file at this moment.
81      */

82     public I18nValue changeKey(I18nValue prev, String JavaDoc newKey) {
83         FormI18nString oldI18nString = (FormI18nString) prev;
84         FormI18nString changedI18nString;
85         if (oldI18nString.getKey() == I18nValue.COMPUTE_AUTO_KEY) {
86             // set key which was unset so far
87
changedI18nString = oldI18nString;
88         }
89         else { // create a new value for the new key
90
changedI18nString = new FormI18nString(oldI18nString);
91             changedI18nString.allData = oldI18nString.allData;
92         }
93         changedI18nString.setKey(newKey);
94         return changedI18nString;
95     }
96
97     /**
98      * Creates a new I18nValue object with changed value. Should not do any
99      * changes to the bundle file.
100      */

101     public I18nValue changeValue(I18nValue prev, String JavaDoc value) {
102         FormI18nString i18nString = new FormI18nString((FormI18nString)prev);
103         i18nString.setValue(value);
104         return i18nString;
105     }
106
107     /**
108      * Creates a new I18nValue refering to given locale (both for reading and
109      * writing from now).
110      */

111     public I18nValue switchLocale(I18nValue value, String JavaDoc localeSuffix) {
112         if (value == null || value.getKey() == null)
113             return value;
114
115         FormI18nString i18nString = new FormI18nString((FormI18nString)value);
116         JavaResourceHolder rh = (JavaResourceHolder) i18nString.getSupport().getResourceHolder();
117         rh.setLocalization(localeSuffix);
118         i18nString.setValue(rh.getValueForKey(i18nString.getKey()));
119         i18nString.setComment(rh.getCommentForKey(i18nString.getKey()));
120         return i18nString;
121     }
122
123     /**
124      * Updates bundle file according to given I18nValue objects - oldValue is
125      * removed, newValue added. Update goes into given locale - parent files
126      * are updated too if given key is not present in them. New properties file
127      * is created if needed.
128      */

129     public void update(I18nValue oldValue, I18nValue newValue,
130                        DataObject srcDataObject, String JavaDoc bundleName, String JavaDoc localeSuffix,
131                        boolean canRemove)
132         throws IOException JavaDoc
133     {
134         FormI18nString oldI18nString = (FormI18nString) oldValue;
135         FormI18nString newI18nString = (FormI18nString) newValue;
136
137         if (oldI18nString != null) {
138             ResourceHolder oldRH = oldI18nString.getSupport().getResourceHolder();
139             DataObject oldRes = oldRH.getResource();
140             DataObject newRes = null;
141             if (newI18nString != null) {
142                 ResourceHolder newRH = newI18nString.getSupport().getResourceHolder();
143                 newRes = newRH.getResource();
144                 if (newRes == null) { // use same resource bundle as old value
145
newRH.setResource(oldRes);
146                     newRes = oldRes;
147                 }
148             }
149
150             String JavaDoc oldKey = oldI18nString.getKey();
151             if (canRemove && oldKey != null) {
152                 JavaResourceHolder jrh = (JavaResourceHolder) oldRH;
153                 Object JavaDoc allData = jrh.getAllData(oldKey);
154                 registerChange(srcDataObject, oldRes, oldKey, allData);
155
156                 if (newI18nString == null
157                     || newI18nString.getKey() == null
158                     || !newI18nString.getKey().equals(oldKey)
159                     || newRes != oldRes)
160                 { // removing i18n value, changing key, or moving to another properties file
161
oldI18nString.allData = allData;
162                     jrh.removeProperty(oldKey);
163                     if (newI18nString != null)
164                         newI18nString.allData = oldI18nString.allData;
165                 }
166                 else if (localeSuffix != null && !localeSuffix.equals("")) { // NOI18N
167
// remember all locale data (to be able to undo adding new specific value to a locale)
168
oldI18nString.allData = allData;
169                 }
170
171                 if (newI18nString == null
172                     && oldRes == getPropertiesDataObject(srcDataObject, bundleName))
173                 { // forget the resource bundle file - may want different next time
174
oldRH.setResource(null);
175                 }
176             }
177         }
178
179         if (newI18nString != null && newI18nString.getKey() != null
180                 && newI18nString.getKey() != I18nValue.NOI18N_KEY)
181         { // valid new value - make sure it is up-to-date in the properties file
182
JavaResourceHolder rh = (JavaResourceHolder) newI18nString.getSupport().getResourceHolder();
183
184             if (rh.getResource() == null) { // find or create properties file
185
DataObject propertiesDO = getPropertiesDataObject(srcDataObject, bundleName);
186                 if (propertiesDO == null) { // create new properties file
187
propertiesDO = createPropertiesDataObject(srcDataObject, bundleName);
188                     if (propertiesDO == null)
189                         return;
190                 }
191                 rh.setResource(propertiesDO);
192
193                 // make sure we use free (unique) key
194
newI18nString.setKey(rh.findFreeKey(newI18nString.getKey()));
195             }
196
197             rh.setLocalization(localeSuffix);
198             String JavaDoc key = newI18nString.getKey();
199             if (!isValueUpToDate(rh, newI18nString)) {
200                 if (newI18nString.allData != null) { // restore complete data across all locales
201
rh.setAllData(key, newI18nString.allData);
202                     newI18nString.allData = null;
203                     // update also the current value - might have come from a different locale
204
newI18nString.setValue(rh.getValueForKey(key));
205                     newI18nString.setComment(rh.getCommentForKey(key));
206                 }
207                 else {
208                     rh.addProperty(key, newI18nString.getValue(), newI18nString.getComment(), true);
209                 }
210                 registerChange(srcDataObject, rh.getResource(), newI18nString.getKey(), null);
211             }
212         }
213     }
214
215     private static boolean isValueUpToDate(ResourceHolder rh, I18nString i18nString) {
216         String JavaDoc storedValue = rh.getValueForKey(i18nString.getKey());
217         String JavaDoc storedComment = rh.getCommentForKey(i18nString.getKey());
218         if ("".equals(storedComment)) // NOI18N
219
storedComment = null;
220         String JavaDoc newValue = i18nString.getValue();
221         String JavaDoc newComment = i18nString.getComment();
222         if ("".equals(newComment)) // NOI18N
223
newComment = null;
224         return (storedValue == newValue || (storedValue != null && storedValue.equals(newValue)))
225             && (storedComment == newComment || (storedComment != null && storedComment.equals(newComment)));
226     }
227
228     /**
229      * Returns property editor to be used for editing internationalized
230      * property of given type (e.g. String). If an existing suitable editor is
231      * passed then it is returned and no new property editor is created.
232      */

233     public PropertyEditor JavaDoc getPropertyEditor(Class JavaDoc type, PropertyEditor JavaDoc existing) {
234         return existing instanceof FormI18nStringEditor ? existing : new FormI18nStringEditor();
235     }
236
237     /**
238      * Evaluates the effect of changing a property editor. The property editor
239      * determines whether a property can hold internationalized value.
240      * @return -1 if an i18n editor is changed to plain type editor,
241      * 0 if the type of editor does no change,
242      * 1 if a plain type editor is changed to i18n one
243      */

244     public int analyzePropertyEditorChange(PropertyEditor JavaDoc oldPE, PropertyEditor JavaDoc newPE) {
245         if (oldPE instanceof FormI18nStringEditor)
246             return isPlainStringEditor(newPE) ? -1 : 0;
247         if (isPlainStringEditor(oldPE))
248             return newPE instanceof FormI18nStringEditor ? 1 : 0;
249         return 0;
250     }
251
252     private static boolean isPlainStringEditor(PropertyEditor JavaDoc pe) {
253         return pe != null && pe.getClass().getName().endsWith(".StringEditor"); // NOI18N
254
}
255
256     /**
257      * Provides a component usable as property customizer (so typically a modal
258      * dialog) that allows to choose (or create) a properties bundle file within
259      * the project of given form data object. The selected file should be
260      * written to the given property editor (via setValue) as a resource name
261      * string.
262      */

263     public Component JavaDoc getBundleSelectionComponent(final PropertyEditor JavaDoc prEd, DataObject srcDataObject) {
264         try {
265             final FileSelector fs = new FileSelector(srcDataObject.getPrimaryFile(), JavaResourceHolder.getTemplate());
266             return fs.getDialog(NbBundle.getMessage(I18nServiceImpl.class, "CTL_SELECT_BUNDLE_TITLE"), // NOI18N
267
new ActionListener JavaDoc()
268             {
269                 public void actionPerformed(ActionEvent JavaDoc ev) {
270                     DataObject bundleDO = fs.getSelectedDataObject();
271                     if (bundleDO != null) {
272                         ClassPath cp = ClassPath.getClassPath(bundleDO.getPrimaryFile(), ClassPath.SOURCE);
273                         if (cp != null) {
274                             String JavaDoc bundleName = cp.getResourceName(bundleDO.getPrimaryFile(), '/', false);
275                             prEd.setValue(bundleName);
276                         }
277                     }
278                 }
279             });
280         }
281         catch (IOException JavaDoc ex) {
282             // means that template for properties file was not found - unlikely
283
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
284         }
285         return null;
286     }
287
288     /**
289      * Returns all currently available locales for given bundle in two arrays
290      * os strings. The first one containes locale suffixes, the second one
291      * corresponding display names for the user (should be unique).
292      */

293     public String JavaDoc[][] getAvailableLocales(DataObject srcDataObject, String JavaDoc bundleName) {
294         PropertiesDataObject dobj = null;
295         try {
296             dobj = getPropertiesDataObject(srcDataObject, bundleName);
297         }
298         catch (IOException JavaDoc ex) {
299             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
300         }
301         if (dobj == null)
302             return null;
303
304         List list = new ArrayList();
305         list.add(dobj.getPrimaryEntry());
306         list.addAll(dobj.secondaryEntries());
307         Collections.sort(list, new Comparator() {
308             public int compare(Object JavaDoc o1, Object JavaDoc o2) {
309                 MultiDataObject.Entry e1 = (MultiDataObject.Entry) o1;
310                 MultiDataObject.Entry e2 = (MultiDataObject.Entry) o2;
311                 return e1.getFile().getName().compareTo(e2.getFile().getName());
312             }
313         });
314
315         String JavaDoc[] locales = new String JavaDoc[list.size()];
316         String JavaDoc[] displays = new String JavaDoc[list.size()];
317         for (int i=0; i < list.size(); i++) {
318             MultiDataObject.Entry entry = (MultiDataObject.Entry) list.get(i);
319             locales[i] = org.netbeans.modules.properties.Util.getLocaleSuffix(entry);
320             displays[i] = org.netbeans.modules.properties.Util.getLocaleLabel(entry);
321         }
322         return new String JavaDoc[][] { locales, displays };
323     }
324
325     /**
326      * Provides a visual component (modal dialog) usable as a property
327      * customizer that allows create a new locale file for given bundle (default
328      * bundle name provided). The created locale should be written as a string
329      * (locale suffix) to the given propery editor.
330      */

331     public Component JavaDoc getCreateLocaleComponent(final PropertyEditor JavaDoc prEd, DataObject srcDataObject, String JavaDoc bundleName) {
332         final PropertiesDataObject propertiesDO;
333         try {
334             propertiesDO = getPropertiesDataObject(srcDataObject, bundleName);
335         }
336         catch (DataObjectNotFoundException ex) {
337             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
338             return null;
339         }
340         final Dialog JavaDoc[] dialog = new Dialog JavaDoc[1];
341         final LocalePanel localePanel = new LocalePanel();
342
343         DialogDescriptor dialogDescriptor = new DialogDescriptor(
344             localePanel,
345             NbBundle.getBundle(PropertiesDataObject.class).getString("CTL_NewLocaleTitle"), // NOI18N
346
true,
347             DialogDescriptor.OK_CANCEL_OPTION,
348             DialogDescriptor.OK_OPTION,
349             new ActionListener JavaDoc() {
350                 public void actionPerformed(ActionEvent JavaDoc evt) {
351                     if (evt.getSource() == DialogDescriptor.OK_OPTION) {
352                         String JavaDoc locale = localePanel.getLocale().toString();
353                         org.netbeans.modules.properties.Util.createLocaleFile(
354                                 propertiesDO, locale, false);
355                         prEd.setValue("_" + locale); // NOI18N
356
}
357                     dialog[0].setVisible(false);
358                     dialog[0].dispose();
359                 }
360             }
361         );
362         dialog[0] = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
363         return dialog[0];
364     }
365
366     /**
367      * Saves properties files edited for given source object (form). This method
368      * is called when a form is being saved - so the corresponding bundle is
369      * saved as well.
370      */

371     public void autoSave(DataObject srcDataObject) {
372         Map/*<DataObject, ChangeInfo>*/ relatedMap = (Map) changesMap.remove(srcDataObject);
373         if (relatedMap != null) {
374             for (Iterator it=relatedMap.keySet().iterator(); it.hasNext(); ) {
375                 DataObject propertiesDO = (DataObject) it.next();
376                 // [not sure: should we auto-save only bundles not opened in the editor?
377
// perhaps it's OK to save always...]
378
SaveCookie save = (SaveCookie) propertiesDO.getCookie(SaveCookie.class);
379                 if (save != null) {
380                     try {
381                         save.save();
382                     }
383                     catch (IOException JavaDoc ex) {
384                         ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
385                     }
386                 }
387             }
388         }
389     }
390
391     /**
392      * Called when a form is closed without saving changes. The changes in
393      * corresponding properties file need to be discarded (reverted) as well.
394      */

395     public void close(DataObject srcDataObject) {
396         Map/*<DataObject, ChangeInfo>*/ relatedMap = (Map) changesMap.remove(srcDataObject);
397         if (relatedMap != null) {
398             for (Iterator it=relatedMap.entrySet().iterator(); it.hasNext(); ) {
399                 Map.Entry/*<DataObject, ChangeInfo>*/ e = (Map.Entry) it.next();
400                 PropertiesDataObject propertiesDO = (PropertiesDataObject) e.getKey();
401                 ChangeInfo changes = (ChangeInfo) e.getValue();
402                 JavaResourceHolder rh = new JavaResourceHolder();
403                 rh.setResource(propertiesDO);
404                 for (Iterator/*<Map.Entry<String, Object>>*/ it2=changes.changed.entrySet().iterator(); it2.hasNext(); ) {
405                     Map.Entry/*<String, Object>*/ e2 = (Map.Entry) it2.next();
406                     String JavaDoc key = (String JavaDoc) e2.getKey();
407                     Object JavaDoc allData = e2.getValue();
408                     rh.setAllData(key, allData);
409                 }
410                 for (Iterator it2=changes.added.iterator(); it2.hasNext(); ) {
411                     String JavaDoc key = (String JavaDoc) it2.next();
412                     rh.removeProperty(key);
413                 }
414                 // [not sure: should we save the bundle for consistency?
415
// perhaps not necessary...]
416
// SaveCookie save = (SaveCookie) propertiesDO.getCookie(SaveCookie.class);
417
// if (save != null) {
418
// try {
419
// save.save();
420
// }
421
// catch (IOException ex) {
422
// ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
423
// }
424
// }
425
}
426         }
427     }
428
429     /**
430      * Checks project of given form whether it is suitable to be automatically
431      * internationalized by default. Currently new forms in module projects
432      * should be set to auto i18n, while standard user (J2SE) projects not.
433      * [If we decide all projects should be internationalized, we can remove
434      * this method.]
435      */

436     public boolean isDefaultInternationalizableProject(DataObject srcDataObject) {
437         return org.netbeans.modules.i18n.Util.isNbBundleAvailable(srcDataObject);
438     }
439
440     // -----
441

442     private static PropertiesDataObject getPropertiesDataObject(DataObject srcDataObject, String JavaDoc bundleName)
443         throws DataObjectNotFoundException
444     {
445         if (bundleName.startsWith("/")) // NOI18N
446
bundleName = bundleName.substring(1);
447         if (!bundleName.toLowerCase().endsWith(".properties")) // NOI18N
448
bundleName = bundleName + ".properties"; // NOI18N
449
FileObject bundleFile = org.netbeans.modules.i18n.Util
450                 .getResource(srcDataObject.getPrimaryFile(), bundleName);
451         if (bundleFile != null) {
452             DataObject dobj = DataObject.find(bundleFile);
453             if (dobj instanceof PropertiesDataObject)
454                 return (PropertiesDataObject) dobj;
455         }
456         return null;
457     }
458
459     private static DataObject createPropertiesDataObject(DataObject srcDataObject,
460                                                          String JavaDoc filePath)
461         throws IOException JavaDoc
462     {
463         if (filePath == null)
464             return null;
465
466         FileObject folder;
467         ClassPath cp = ClassPath.getClassPath(srcDataObject.getPrimaryFile(), ClassPath.SOURCE);
468         FileObject root = cp.getRoots()[0];
469         String JavaDoc fileName;
470         int idx = filePath.lastIndexOf('/');
471         boolean hasCustomRes = false;
472         Project owner = FileOwnerQuery.getOwner(srcDataObject.getPrimaryFile());
473         if (owner != null) {
474             // this is for projects that have split sources/resources folder structures.
475
Sources srcs = ProjectUtils.getSources(owner);
476             SourceGroup[] grps = srcs.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES);
477             if (grps != null && grps.length > 0) {
478                 hasCustomRes = true;
479                 root = grps[0].getRootFolder();
480             }
481         }
482         if (idx < 0) { // default package
483
folder = root;
484             fileName = filePath;
485         }
486         else {
487             String JavaDoc folderPath = filePath.substring(0, idx);
488             folder = cp.findResource(folderPath);
489             if (folder == null || hasCustomRes) {
490                 folder = FileUtil.createFolder(root, folderPath);
491             }
492             fileName = filePath.substring(idx + 1);
493         }
494         if (folder != null) {
495             DataObject template = JavaResourceHolder.getTemplate();
496             return template.createFromTemplate(DataFolder.findFolder(folder), fileName);
497             // [set auto-create attribute?]
498
}
499         return null; // [throw exception - can't create properties file?]
500
}
501
502     /**
503      * Keeps original data from properties file when first change is done in
504      * given properties file for given source file (form). If the source file
505      * is discarded later, all relevant changes in the properties file are reverted.
506      */

507     private void registerChange(DataObject srcDO, DataObject propertiesDO, String JavaDoc key, Object JavaDoc allData) {
508         Map/*<DataObject, ChangeInfo>*/ relatedMap = (Map) changesMap.get(srcDO);
509         if (relatedMap == null) {
510             relatedMap = new HashMap();
511             changesMap.put(srcDO, relatedMap);
512         }
513         ChangeInfo changes = (ChangeInfo) relatedMap.get(propertiesDO);
514         if (changes == null) {
515             changes = new ChangeInfo();
516             relatedMap.put(propertiesDO, changes);
517         }
518         if (!changes.changed.containsKey(key) && !changes.added.contains(key)) {
519             // original state of this key not registered yet
520
if (allData != null) // something changed in existing data
521
changes.changed.put(key, allData); // allData contains original data
522
else // new key added
523
changes.added.add(key);
524         }
525     }
526 }
527
Popular Tags