Initial check-in of contribution of Stefan Muecke
diff --git a/org.eclipse.babel.editor/.classpath b/org.eclipse.babel.editor/.classpath
index ce73933..5f03640 100644
--- a/org.eclipse.babel.editor/.classpath
+++ b/org.eclipse.babel.editor/.classpath
@@ -3,5 +3,6 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="tests"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/org.eclipse.babel.editor/META-INF/MANIFEST.MF b/org.eclipse.babel.editor/META-INF/MANIFEST.MF
index 9f3b0aa..522a82c 100644
--- a/org.eclipse.babel.editor/META-INF/MANIFEST.MF
+++ b/org.eclipse.babel.editor/META-INF/MANIFEST.MF
@@ -1,19 +1,23 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
-Bundle-Name: Messages Editor Plug-in
+Bundle-Name: %plugin.name
Bundle-SymbolicName: org.eclipse.babel.editor;singleton:=true
-Bundle-Version: 1.0.0
+Bundle-Version: 1.0.1
Bundle-Activator: org.eclipse.babel.editor.plugin.MessagesEditorPlugin
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.eclipse.jface.text,
- org.eclipse.core.resources,
org.eclipse.ui.editors,
org.eclipse.ui.ide,
org.eclipse.ui.workbench.texteditor,
org.eclipse.ui.views,
+ org.eclipse.ui.forms;bundle-version="3.2.0",
+ org.eclipse.core.resources;bundle-version="3.2.0",
+ org.eclipse.jdt.core;bundle-version="3.2.0",
+ org.eclipse.pde.core;bundle-version="3.2.0",
+ org.junit;resolution:=optional,
org.eclipse.babel.core
-Eclipse-LazyStart: true
-Bundle-Vendor: Eclipse
-Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Bundle-ActivationPolicy: lazy
+Bundle-Vendor: %plugin.provider
+Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-Localization: plugin
diff --git a/org.eclipse.babel.editor/icons/elcl16/clear_co.gif b/org.eclipse.babel.editor/icons/elcl16/clear_co.gif
new file mode 100644
index 0000000..af30a42
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/elcl16/clear_co.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/icons/elcl16/conf_columns.gif b/org.eclipse.babel.editor/icons/elcl16/conf_columns.gif
new file mode 100644
index 0000000..5ef0ed7
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/elcl16/conf_columns.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/icons/elcl16/export.gif b/org.eclipse.babel.editor/icons/elcl16/export.gif
new file mode 100644
index 0000000..3465699
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/elcl16/export.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/icons/elcl16/filter_obj.gif b/org.eclipse.babel.editor/icons/elcl16/filter_obj.gif
new file mode 100644
index 0000000..ef51bd5
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/elcl16/filter_obj.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/icons/elcl16/refresh.gif b/org.eclipse.babel.editor/icons/elcl16/refresh.gif
new file mode 100644
index 0000000..3ca04d0
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/elcl16/refresh.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/icons/obj16/nls_editor.gif b/org.eclipse.babel.editor/icons/obj16/nls_editor.gif
new file mode 100644
index 0000000..e6913d0
--- /dev/null
+++ b/org.eclipse.babel.editor/icons/obj16/nls_editor.gif
Binary files differ
diff --git a/org.eclipse.babel.editor/plugin.properties b/org.eclipse.babel.editor/plugin.properties
index 161b7d5..980946e 100644
--- a/org.eclipse.babel.editor/plugin.properties
+++ b/org.eclipse.babel.editor/plugin.properties
@@ -1,7 +1,7 @@
editor.title = Messages Editor
plugin.name = Messages Editor Plug-in
-plugin.provider = Eclipse
+plugin.provider = Eclipse.org
prefs.formatting = Formatting
prefs.performance = Reports
@@ -16,3 +16,7 @@
removeNatureAction.label = Disable &localization properties validator
removeNatureAction.tooltip = Remove the localization validator builder
+
+localizationEditorName = Localization Editor
+command_openLocalizationEditor_name = Open Localization Editor
+command_openLocalizationEditor_mnemonic = L
\ No newline at end of file
diff --git a/org.eclipse.babel.editor/plugin.xml b/org.eclipse.babel.editor/plugin.xml
index a94f711..013f9a3 100644
--- a/org.eclipse.babel.editor/plugin.xml
+++ b/org.eclipse.babel.editor/plugin.xml
@@ -161,4 +161,44 @@
</description>
</wizard>
</extension>
+
+
+
+ <extension
+ point="org.eclipse.ui.elementFactories">
+ <factory
+ class="org.eclipse.pde.nls.internal.ui.editor.LocalizationEditorInputFactory"
+ id="org.eclipse.pde.nls.ui.LocalizationEditorInputFactory"/>
+ </extension>
+
+ <extension
+ point="org.eclipse.ui.editors">
+ <editor
+ class="org.eclipse.pde.nls.internal.ui.editor.LocalizationEditor"
+ icon="icons/obj16/nls_editor.gif"
+ id="org.eclipse.pde.nls.ui.LocalizationEditor"
+ name="%localizationEditorName"/>
+ </extension>
+
+ <extension
+ point="org.eclipse.ui.commands">
+ <command
+ defaultHandler="org.eclipse.pde.nls.internal.ui.OpenLocalizationEditorHandler"
+ id="org.eclipse.pde.nls.ui.OpenLocalizationEditor"
+ name="%command_openLocalizationEditor_name">
+ </command>
+ </extension>
+
+ <extension
+ point="org.eclipse.ui.menus">
+ <menuContribution
+ locationURI="menu:edit?after=additions">
+ <command
+ commandId="org.eclipse.pde.nls.ui.OpenLocalizationEditor"
+ mnemonic="%command_openLocalizationEditor_mnemonic"
+ style="push">
+ </command>
+ </menuContribution>
+ </extension>
+
</plugin>
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/MessagesEditor.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/MessagesEditor.java
index 7f3866d..67571ed 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/MessagesEditor.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/MessagesEditor.java
@@ -38,7 +38,6 @@
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.util.IPropertyChangeListener;
-import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/builder/Builder.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/builder/Builder.java
index b054e94..b7ce85c 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/builder/Builder.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/builder/Builder.java
@@ -17,10 +17,8 @@
import java.util.Map;
import java.util.Set;
-//import org.apache.lucene.index.CorruptIndexException;
import org.eclipse.babel.core.message.MessagesBundle;
import org.eclipse.babel.core.message.MessagesBundleGroup;
-//import org.eclipse.babel.editor.builder.indexer.Indexer;
import org.eclipse.babel.editor.bundle.MessagesBundleGroupFactory;
import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
import org.eclipse.babel.editor.resource.validator.FileMarkerStrategy;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/I18NPage.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/I18NPage.java
index 803cae7..e87b6a7 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/I18NPage.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/I18NPage.java
@@ -20,7 +20,6 @@
import org.eclipse.babel.editor.MessagesEditor;
import org.eclipse.babel.editor.MessagesEditorChangeAdapter;
import org.eclipse.babel.editor.util.UIUtils;
-import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/SideNavTextBoxComposite.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/SideNavTextBoxComposite.java
index 9d1a946..f97ace2 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/SideNavTextBoxComposite.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/SideNavTextBoxComposite.java
@@ -10,6 +10,11 @@
******************************************************************************/
package org.eclipse.babel.editor.i18n;
+import org.eclipse.babel.core.message.tree.KeyTreeNode;
+import org.eclipse.babel.core.message.tree.visitor.NodePathRegexVisitor;
+import org.eclipse.babel.editor.MessagesEditor;
+import org.eclipse.babel.editor.MessagesEditorChangeAdapter;
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
@@ -23,13 +28,6 @@
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
-
-import org.eclipse.babel.core.message.tree.KeyTreeNode;
-import org.eclipse.babel.core.message.tree.visitor.NodePathRegexVisitor;
-import org.eclipse.babel.editor.MessagesEditor;
-import org.eclipse.babel.editor.MessagesEditorChangeAdapter;
-import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
-
/**
* Tree for displaying and navigating through resource bundle keys.
* @author Pascal Essiembre
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowDuplicateAction.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowDuplicateAction.java
index 5bdbaf8..a9586d7 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowDuplicateAction.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowDuplicateAction.java
@@ -12,8 +12,6 @@
import java.util.Locale;
-import org.eclipse.babel.core.message.MessagesBundleGroup;
-import org.eclipse.babel.core.message.checks.DuplicateValueCheck;
import org.eclipse.babel.editor.util.UIUtils;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowSimilarAction.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowSimilarAction.java
index 0ebfd03..c4910be 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowSimilarAction.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/actions/ShowSimilarAction.java
@@ -12,7 +12,6 @@
import java.util.Locale;
-import org.eclipse.babel.core.message.MessagesBundleGroup;
import org.eclipse.babel.editor.util.UIUtils;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/MessagesEditorPlugin.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/MessagesEditorPlugin.java
index dce8283..b5b451d 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/MessagesEditorPlugin.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/MessagesEditorPlugin.java
@@ -30,7 +30,12 @@
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleModel;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
@@ -64,6 +69,8 @@
//private Map<String,Set<SimpleResourceChangeListners>> resourceChangeSubscribers;
private Map resourceChangeSubscribers;
+ private ResourceBundleModel model;
+
/**
* The constructor
*/
@@ -228,4 +235,65 @@
protected ResourceBundle getResourceBundle() {
return resourceBundle;
}
+
+ // Stefan's activator methods:
+
+ /**
+ * Returns an image descriptor for the given icon filename.
+ *
+ * @param filename the icon filename relative to the icons path
+ * @return the image descriptor
+ */
+ public static ImageDescriptor getImageDescriptor(String filename) {
+ String iconPath = "icons/"; //$NON-NLS-1$
+ return imageDescriptorFromPlugin(PLUGIN_ID, iconPath + filename);
+ }
+
+ public static ResourceBundleModel getModel(IProgressMonitor monitor) {
+ if (plugin.model == null) {
+ plugin.model = new ResourceBundleModel(monitor);
+ }
+ return plugin.model;
+ }
+
+ public static void disposeModel() {
+ if (plugin != null) {
+ plugin.model = null;
+ }
+ }
+
+ // Logging
+
+ /**
+ * Adds the given exception to the log.
+ *
+ * @param e the exception to log
+ * @return the logged status
+ */
+ public static IStatus log(Throwable e) {
+ return log(new Status(IStatus.ERROR, PLUGIN_ID, 0, "Internal error.", e));
+ }
+
+ /**
+ * Adds the given exception to the log.
+ *
+ * @param exception the exception to log
+ * @return the logged status
+ */
+ public static IStatus log(String message, Throwable exception) {
+ return log(new Status(IStatus.ERROR, PLUGIN_ID, -1, message, exception));
+ }
+
+ /**
+ * Adds the given <code>IStatus</code> to the log.
+ *
+ * @param status the status to log
+ * @return the logged status
+ */
+ public static IStatus log(IStatus status) {
+ getDefault().getLog().log(status);
+ return status;
+ }
+
+
}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/EclipsePropertiesEditorResource.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/EclipsePropertiesEditorResource.java
index eef42e7..928dec2 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/EclipsePropertiesEditorResource.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/EclipsePropertiesEditorResource.java
@@ -12,14 +12,10 @@
import java.util.Locale;
-import org.eclipse.babel.core.message.AbstractIFileChangeListener;
import org.eclipse.babel.core.message.resource.AbstractPropertiesResource;
import org.eclipse.babel.core.message.resource.ser.PropertiesDeserializer;
import org.eclipse.babel.core.message.resource.ser.PropertiesSerializer;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.IResourceChangeEvent;
-import org.eclipse.core.resources.IResourceChangeListener;
-import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/validator/FileMarkerStrategy.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/validator/FileMarkerStrategy.java
index 98f3258..317c8a7 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/validator/FileMarkerStrategy.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/resource/validator/FileMarkerStrategy.java
@@ -10,16 +10,14 @@
******************************************************************************/
package org.eclipse.babel.editor.resource.validator;
-import org.eclipse.core.resources.IMarker;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-
-
import org.eclipse.babel.core.message.checks.DuplicateValueCheck;
import org.eclipse.babel.core.message.checks.MissingValueCheck;
import org.eclipse.babel.core.util.BabelUtils;
import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
import org.eclipse.babel.editor.preferences.MsgEditorPreferences;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
/**
* @author Pascal Essiembre
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/tree/KeyTreeLabelProvider.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/tree/KeyTreeLabelProvider.java
index 8d1d787..88f31c8 100644
--- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/tree/KeyTreeLabelProvider.java
+++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/tree/KeyTreeLabelProvider.java
@@ -11,25 +11,18 @@
package org.eclipse.babel.editor.tree;
import java.util.Collection;
-import java.util.Iterator;
import org.eclipse.babel.core.message.MessagesBundleGroup;
import org.eclipse.babel.core.message.tree.IKeyTreeModel;
import org.eclipse.babel.core.message.tree.KeyTreeNode;
import org.eclipse.babel.editor.MessagesEditor;
import org.eclipse.babel.editor.MessagesEditorMarkers;
-import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
-import org.eclipse.babel.editor.resource.validator.ValidationFailureEvent;
import org.eclipse.babel.editor.util.OverlayImageIcon;
import org.eclipse.babel.editor.util.UIUtils;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.IColorProvider;
-import org.eclipse.jface.viewers.IDecorationContext;
import org.eclipse.jface.viewers.IFontProvider;
-import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProvider;
-import org.eclipse.jface.viewers.ILabelProviderListener;
-import org.eclipse.jface.viewers.LabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/OpenLocalizationEditorHandler.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/OpenLocalizationEditorHandler.java
new file mode 100644
index 0000000..683b5b4
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/OpenLocalizationEditorHandler.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.pde.nls.internal.ui.editor.LocalizationEditor;
+import org.eclipse.pde.nls.internal.ui.editor.LocalizationEditorInput;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+public class OpenLocalizationEditorHandler extends AbstractHandler {
+
+ public OpenLocalizationEditorHandler() {
+ }
+
+ /*
+ * @see org.eclipse.core.commands.IHandler#execute(org.eclipse.core.commands.ExecutionEvent)
+ */
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ try {
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ IWorkbenchPage page = window.getActivePage();
+ page.openEditor(new LocalizationEditorInput(), LocalizationEditor.ID);
+ } catch (PartInitException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/ConfigureColumnsDialog.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/ConfigureColumnsDialog.java
new file mode 100644
index 0000000..6318c6a
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/ConfigureColumnsDialog.java
@@ -0,0 +1,197 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.dialogs;
+
+import java.util.ArrayList;
+
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.pde.nls.internal.ui.parser.LocaleUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+public class ConfigureColumnsDialog extends Dialog {
+
+ private class ColumnField {
+ Text text;
+ ToolItem clearButton;
+ }
+
+ private ArrayList<ColumnField> fields = new ArrayList<ColumnField>();
+
+ private ArrayList<String> result = new ArrayList<String>();
+ private String[] initialValues;
+ private Color errorColor;
+
+ private Image clearImage;
+
+ public ConfigureColumnsDialog(Shell parentShell, String[] initialValues) {
+ super(parentShell);
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ this.initialValues = initialValues;
+ }
+
+ /*
+ * @see org.eclipse.jface.window.Window#open()
+ */
+ @Override
+ public int open() {
+ return super.open();
+ }
+
+ /*
+ * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+ */
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText("Configure Columns");
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+ GridLayout gridLayout = (GridLayout) composite.getLayout();
+ gridLayout.numColumns = 3;
+
+ Label label = new Label(composite, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ label.setText("Enter \"key\", \"default\" or locale (e.g. \"de\" or \"zh_TW\"):");
+ label.setLayoutData(GridDataFactory.fillDefaults().hint(300, SWT.DEFAULT).span(3, 1).create());
+
+ fields.add(createLanguageField(composite, "Column &1:"));
+ fields.add(createLanguageField(composite, "Column &2:"));
+ fields.add(createLanguageField(composite, "Column &3:"));
+ fields.add(createLanguageField(composite, "Column &4:"));
+
+ if (initialValues != null) {
+ for (int i = 0; i < 4 && i < initialValues.length; i++) {
+ fields.get(i).text.setText(initialValues[i]);
+ }
+ }
+
+ ModifyListener modifyListener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ validate();
+ }
+ };
+ for (ColumnField field : fields) {
+ field.text.addModifyListener(modifyListener);
+ }
+ errorColor = new Color(Display.getCurrent(), 0xff, 0x7f, 0x7f);
+
+ return composite;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#createContents(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createContents(Composite parent) {
+ Control contents = super.createContents(parent);
+ validate();
+ return contents;
+ }
+
+ private ColumnField createLanguageField(Composite parent, String labelText) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ label.setText(labelText);
+
+ Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
+ text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ if (clearImage == null)
+ clearImage = MessagesEditorPlugin.getImageDescriptor("elcl16/clear_co.gif").createImage(); //$NON-NLS-1$
+
+ ToolBar toolbar = new ToolBar(parent, SWT.FLAT);
+ ToolItem item = new ToolItem(toolbar, SWT.PUSH);
+ item.setImage(clearImage);
+ item.setToolTipText("Clear");
+ item.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ for (ColumnField field : fields) {
+ if (field.clearButton == e.widget) {
+ field.text.setText(""); //$NON-NLS-1$
+ }
+ }
+ }
+ });
+
+ ColumnField field = new ColumnField();
+ field.text = text;
+ field.clearButton = item;
+ return field;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#okPressed()
+ */
+ @Override
+ protected void okPressed() {
+ for (ColumnField field : fields) {
+ String text = field.text.getText().trim();
+ if (text.length() > 0) {
+ result.add(text);
+ }
+ }
+ super.okPressed();
+ errorColor.dispose();
+ clearImage.dispose();
+ }
+
+ public String[] getResult() {
+ return result.toArray(new String[result.size()]);
+ }
+
+ protected void validate() {
+ boolean isValid = true;
+ for (ColumnField field : fields) {
+ String text = field.text.getText();
+ if (text.equals("") || text.equals("key") || text.equals("default")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ field.text.setBackground(null);
+ } else {
+ try {
+ LocaleUtil.parseLocale(text);
+ field.text.setBackground(null);
+ } catch (IllegalArgumentException e) {
+ field.text.setBackground(errorColor);
+ isValid = false;
+ }
+ }
+ }
+ Button okButton = getButton(IDialogConstants.OK_ID);
+ okButton.setEnabled(isValid);
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditMultiLineEntryDialog.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditMultiLineEntryDialog.java
new file mode 100644
index 0000000..62947de
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditMultiLineEntryDialog.java
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.dialogs;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class EditMultiLineEntryDialog extends Dialog {
+
+ private Text textWidget;
+ private String text;
+ private boolean readOnly;
+
+ protected EditMultiLineEntryDialog(Shell parentShell, String initialInput, boolean readOnly) {
+ super(parentShell);
+ this.readOnly = readOnly;
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ text = initialInput;
+ }
+
+ /*
+ * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+ */
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText("Edit Resource Bundle Entry");
+ }
+
+ public String getValue() {
+ return text;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ int readOnly = this.readOnly ? SWT.READ_ONLY : 0;
+ Text text = new Text(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | readOnly);
+ text.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).hint(350, 150).create());
+ text.setText(text == null ? "" : this.text);
+
+ textWidget = text;
+
+ return composite;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#okPressed()
+ */
+ @Override
+ protected void okPressed() {
+ text = textWidget.getText();
+ super.okPressed();
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditResourceBundleEntriesDialog.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditResourceBundleEntriesDialog.java
new file mode 100644
index 0000000..cec1e05
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/EditResourceBundleEntriesDialog.java
@@ -0,0 +1,391 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.dialogs;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Locale;
+
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.window.Window;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundle;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleKey;
+import org.eclipse.pde.nls.internal.ui.parser.RawBundle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class EditResourceBundleEntriesDialog extends Dialog {
+
+ private class LocaleField {
+ ResourceBundle bundle;
+ Label label;
+ Text text;
+ Locale locale;
+ String oldValue;
+ boolean isReadOnly;
+ Button button;
+ }
+
+ private ResourceBundleKey resourceBundleKey;
+ protected ArrayList<LocaleField> fields = new ArrayList<LocaleField>();
+ private final Locale[] locales;
+ private Color errorColor;
+
+ /**
+ * @param locales the locales to edit
+ */
+ public EditResourceBundleEntriesDialog(Shell parentShell, Locale[] locales) {
+ super(parentShell);
+ this.locales = locales;
+ setShellStyle(getShellStyle() | SWT.RESIZE);
+ }
+
+ public void setResourceBundleKey(ResourceBundleKey resourceBundleKey) {
+ this.resourceBundleKey = resourceBundleKey;
+ }
+
+ /*
+ * @see org.eclipse.jface.window.Window#open()
+ */
+ @Override
+ public int open() {
+ if (resourceBundleKey == null)
+ throw new RuntimeException("Resource bundle key not set.");
+ return super.open();
+ }
+
+ /*
+ * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+ */
+ @Override
+ protected void configureShell(Shell newShell) {
+ super.configureShell(newShell);
+ newShell.setText("Edit Resource Bundle Entries");
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+ GridLayout gridLayout = (GridLayout) composite.getLayout();
+ gridLayout.numColumns = 3;
+
+ Label keyLabel = new Label(composite, SWT.NONE);
+ keyLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ keyLabel.setText("&Key:");
+
+ int style = SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.READ_ONLY;
+ Text keyText = new Text(composite, style);
+ keyText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+ keyText.setText(resourceBundleKey.getName());
+
+ new Label(composite, SWT.NONE); // spacer
+
+ for (Locale locale : locales) {
+ if (locale.getLanguage().equals("")) { //$NON-NLS-1$
+ fields.add(createLocaleField(composite, locale, "&Default Bundle:"));
+ } else {
+ fields.add(createLocaleField(composite, locale, "&" + locale.getDisplayName() + ":"));
+ }
+ }
+
+ // Set focus on first editable field
+ if (fields.size() > 0) {
+ for (int i = 0; i < fields.size(); i++) {
+ if (fields.get(i).text.getEditable()) {
+ fields.get(i).text.setFocus();
+ break;
+ }
+ }
+ }
+
+ Label label = new Label(composite, SWT.NONE);
+ label.setLayoutData(GridDataFactory.fillDefaults().span(3, 1).create());
+ label.setText("Note: The following escape sequences are allowed: \\r, \\n, \\t, \\\\");
+
+ ModifyListener modifyListener = new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ validate();
+ }
+ };
+ for (LocaleField field : fields) {
+ field.text.addModifyListener(modifyListener);
+ }
+ errorColor = new Color(Display.getCurrent(), 0xff, 0x7f, 0x7f);
+
+ return composite;
+ }
+
+ private LocaleField createLocaleField(Composite parent, Locale locale, String localeLabel) {
+ ResourceBundle bundle = resourceBundleKey.getFamily().getBundle(locale);
+
+ Label label = new Label(parent, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ label.setText(localeLabel);
+
+ boolean readOnly = bundle == null || bundle.isReadOnly();
+ int style = SWT.SINGLE | SWT.LEAD | SWT.BORDER | (readOnly ? SWT.READ_ONLY : 0);
+ Text text = new Text(parent, style);
+ text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
+
+ String value = null;
+ if (bundle != null) {
+ try {
+ value = bundle.getString(resourceBundleKey.getName());
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ if (value == null) {
+ if (readOnly) {
+ value = "(Key does not exist)";
+ } else {
+ value = ""; // TODO Indicate that the entry is missing: perhaps red background
+ }
+ }
+ text.setText(escape(value));
+ } else {
+ text.setText("(Resource bundle not found)");
+ }
+
+ Button button = new Button(parent, SWT.PUSH);
+ button.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ button.setText("..."); //$NON-NLS-1$
+ button.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ for (LocaleField field : fields) {
+ if (e.widget == field.button) {
+ EditMultiLineEntryDialog dialog = new EditMultiLineEntryDialog(
+ getShell(),
+ unescape(field.text.getText()),
+ field.isReadOnly);
+ if (dialog.open() == Window.OK) {
+ field.text.setText(escape(dialog.getValue()));
+ }
+ }
+ }
+ }
+ });
+
+ LocaleField field = new LocaleField();
+ field.bundle = bundle;
+ field.label = label;
+ field.text = text;
+ field.locale = locale;
+ field.oldValue = value;
+ field.isReadOnly = readOnly;
+ field.button = button;
+ return field;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#okPressed()
+ */
+ @Override
+ protected void okPressed() {
+ for (LocaleField field : fields) {
+ if (field.isReadOnly)
+ continue;
+ String key = resourceBundleKey.getName();
+ String value = unescape(field.text.getText());
+ boolean hasChanged = (field.oldValue == null && !value.equals("")) //$NON-NLS-1$
+ || (field.oldValue != null && !field.oldValue.equals(value));
+ if (hasChanged) {
+ try {
+ Object resource = field.bundle.getUnderlyingResource();
+ InputStream inputStream;
+ if (resource instanceof IFile) {
+ IFile file = (IFile) resource;
+ inputStream = file.getContents();
+ RawBundle rawBundle;
+ try {
+ rawBundle = RawBundle.createFrom(inputStream);
+ rawBundle.put(key, value);
+ } catch (Exception e) {
+ openError("Value could not be saved: " + value, e);
+ return;
+ }
+ StringWriter stringWriter = new StringWriter();
+ rawBundle.writeTo(stringWriter);
+ byte[] bytes = stringWriter.toString().getBytes("ISO-8859-1"); //$NON-NLS-1$
+ ByteArrayInputStream newContents = new ByteArrayInputStream(bytes);
+ file.setContents(newContents, false, false, new NullProgressMonitor());
+ } else {
+ // Unexpected type of resource
+ throw new RuntimeException("Not yet implemented."); //$NON-NLS-1$
+ }
+ field.bundle.put(key, value);
+ } catch (Exception e) {
+ openError("Value could not be saved: " + value, e);
+ return;
+ }
+ }
+ }
+ super.okPressed();
+ errorColor.dispose();
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsStrategy()
+ */
+ @Override
+ protected int getDialogBoundsStrategy() {
+ return DIALOG_PERSISTLOCATION | DIALOG_PERSISTSIZE;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#getDialogBoundsSettings()
+ */
+ @Override
+ protected IDialogSettings getDialogBoundsSettings() {
+ IDialogSettings settings = MessagesEditorPlugin.getDefault().getDialogSettings();
+ String sectionName = "EditResourceBundleEntriesDialog"; //$NON-NLS-1$
+ IDialogSettings section = settings.getSection(sectionName);
+ if (section == null)
+ section = settings.addNewSection(sectionName);
+ return section;
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#getInitialSize()
+ */
+ @Override
+ protected Point getInitialSize() {
+ Point initialSize = super.getInitialSize();
+ // Make sure that all locales are visible
+ Point size = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
+ if (initialSize.y < size.y)
+ initialSize.y = size.y;
+ return initialSize;
+ }
+
+ protected void validate() {
+ boolean isValid = true;
+ for (LocaleField field : fields) {
+ try {
+ unescape(field.text.getText());
+ field.text.setBackground(null);
+ } catch (IllegalArgumentException e) {
+ field.text.setBackground(errorColor);
+ isValid = false;
+ }
+ }
+ Button okButton = getButton(IDialogConstants.OK_ID);
+ okButton.setEnabled(isValid);
+ }
+
+ private void openError(String message, Exception e) {
+ IStatus status;
+ if (e instanceof CoreException) {
+ CoreException coreException = (CoreException) e;
+ status = coreException.getStatus();
+ } else {
+ status = new Status(IStatus.ERROR, "<dummy>", e.getMessage(), e); //$NON-NLS-1$
+ }
+ e.printStackTrace();
+ ErrorDialog.openError(getParentShell(), "Error", message, status);
+ }
+
+ /**
+ * Escapes line separators, tabulators and double backslashes.
+ *
+ * @param str
+ * @return the escaped string
+ */
+ public static String escape(String str) {
+ StringBuilder builder = new StringBuilder(str.length() + 10);
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ switch (c) {
+ case '\r' :
+ builder.append("\\r"); //$NON-NLS-1$
+ break;
+ case '\n' :
+ builder.append("\\n"); //$NON-NLS-1$
+ break;
+ case '\t' :
+ builder.append("\\t"); //$NON-NLS-1$
+ break;
+ case '\\' :
+ builder.append("\\\\"); //$NON-NLS-1$
+ break;
+ default :
+ builder.append(c);
+ break;
+ }
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Unescapes line separators, tabulators and double backslashes.
+ *
+ * @param str
+ * @return the unescaped string
+ * @throws IllegalArgumentException when an invalid or unexpected escape is encountered
+ */
+ public static String unescape(String str) {
+ StringBuilder builder = new StringBuilder(str.length() + 10);
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '\\') {
+ switch (str.charAt(i + 1)) {
+ case 'r' :
+ builder.append('\r');
+ break;
+ case 'n' :
+ builder.append('\n');
+ break;
+ case 't' :
+ builder.append('\t');
+ break;
+ case '\\' :
+ builder.append('\\');
+ break;
+ default :
+ throw new IllegalArgumentException("Invalid escape sequence.");
+ }
+ i++;
+ } else {
+ builder.append(c);
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptions.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptions.java
new file mode 100644
index 0000000..7b6ff5b
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptions.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.dialogs;
+
+public class FilterOptions {
+
+ public boolean filterPlugins;
+ public String[] pluginPatterns;
+ public boolean keysWithMissingEntriesOnly;
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptionsDialog.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptionsDialog.java
new file mode 100644
index 0000000..ae4e038
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/dialogs/FilterOptionsDialog.java
@@ -0,0 +1,112 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.dialogs;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+public class FilterOptionsDialog extends Dialog {
+
+ private static final String SEPARATOR = ","; //$NON-NLS-1$
+
+ private Button enablePluginPatterns;
+ private Button keysWithMissingEntriesOnly;
+ private Text pluginPatterns;
+
+ private FilterOptions initialOptions;
+ private FilterOptions result;
+
+ public FilterOptionsDialog(Shell shell) {
+ super(shell);
+ }
+
+ public void setInitialFilterOptions(FilterOptions initialfilterOptions) {
+ this.initialOptions = initialfilterOptions;
+ }
+
+ /*
+ * @see org.eclipse.ui.dialogs.SelectionDialog#configureShell(org.eclipse.swt.widgets.Shell)
+ */
+ protected void configureShell(Shell shell) {
+ super.configureShell(shell);
+ shell.setText("Filters");
+ }
+
+ /*
+ * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
+ */
+ protected Control createDialogArea(Composite parent) {
+ Composite composite = (Composite) super.createDialogArea(parent);
+
+ // Checkbox
+ enablePluginPatterns = new Button(composite, SWT.CHECK);
+ enablePluginPatterns.setFocus();
+ enablePluginPatterns.setText("Filter by plug-in id patterns (separated by comma):");
+ enablePluginPatterns.addSelectionListener(new SelectionAdapter() {
+ public void widgetSelected(SelectionEvent e) {
+ pluginPatterns.setEnabled(enablePluginPatterns.getSelection());
+ }
+ });
+ enablePluginPatterns.setSelection(initialOptions.filterPlugins);
+
+ // Pattern field
+ pluginPatterns = new Text(composite, SWT.SINGLE | SWT.BORDER);
+ GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
+ data.widthHint = convertWidthInCharsToPixels(59);
+ pluginPatterns.setLayoutData(data);
+ pluginPatterns.setEnabled(initialOptions.filterPlugins);
+ if (initialOptions.pluginPatterns != null) {
+ StringBuilder builder = new StringBuilder();
+ String[] patterns = initialOptions.pluginPatterns;
+ for (String pattern : patterns) {
+ if (builder.length() > 0) {
+ builder.append(SEPARATOR);
+ builder.append(" ");
+ }
+ builder.append(pattern);
+ }
+ pluginPatterns.setText(builder.toString());
+ }
+
+ keysWithMissingEntriesOnly = new Button(composite, SWT.CHECK);
+ keysWithMissingEntriesOnly.setText("Keys with missing entries only");
+ keysWithMissingEntriesOnly.setSelection(initialOptions.keysWithMissingEntriesOnly);
+
+ applyDialogFont(parent);
+ return parent;
+ }
+
+ protected void okPressed() {
+ String patterns = pluginPatterns.getText();
+ result = new FilterOptions();
+ result.filterPlugins = enablePluginPatterns.getSelection();
+ String[] split = patterns.split(SEPARATOR);
+ for (int i = 0; i < split.length; i++) {
+ split[i] = split[i].trim();
+ }
+ result.pluginPatterns = split;
+ result.keysWithMissingEntriesOnly = keysWithMissingEntriesOnly.getSelection();
+ super.okPressed();
+ }
+
+ public FilterOptions getResult() {
+ return result;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditor.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditor.java
new file mode 100644
index 0000000..e0f5ef6
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditor.java
@@ -0,0 +1,1088 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.editor;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.dialogs.ErrorDialog;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ILazyContentProvider;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.pde.nls.internal.ui.dialogs.ConfigureColumnsDialog;
+import org.eclipse.pde.nls.internal.ui.dialogs.EditResourceBundleEntriesDialog;
+import org.eclipse.pde.nls.internal.ui.dialogs.FilterOptions;
+import org.eclipse.pde.nls.internal.ui.dialogs.FilterOptionsDialog;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundle;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleFamily;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleKey;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleKeyList;
+import org.eclipse.pde.nls.internal.ui.model.ResourceBundleModel;
+import org.eclipse.pde.nls.internal.ui.parser.LocaleUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.events.KeyAdapter;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorSite;
+import org.eclipse.ui.IPropertyListener;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.actions.ContributionItemFactory;
+import org.eclipse.ui.forms.widgets.ExpandableComposite;
+import org.eclipse.ui.forms.widgets.Form;
+import org.eclipse.ui.forms.widgets.FormToolkit;
+import org.eclipse.ui.forms.widgets.Section;
+import org.eclipse.ui.internal.misc.StringMatcher;
+import org.eclipse.ui.part.EditorPart;
+import org.eclipse.ui.part.IShowInSource;
+import org.eclipse.ui.part.ShowInContext;
+
+// TODO Fix restriction and remove warning
+@SuppressWarnings("restriction")
+public class LocalizationEditor extends EditorPart {
+
+ private final class LocalizationLabelProvider extends ColumnLabelProvider {
+
+ private final Object columnConfig;
+
+ public LocalizationLabelProvider(Object columnConfig) {
+ this.columnConfig = columnConfig;
+ }
+
+ public String getText(Object element) {
+ ResourceBundleKey key = (ResourceBundleKey) element;
+ if (columnConfig == KEY)
+ return key.getName();
+ Locale locale = (Locale) columnConfig;
+ String value;
+ try {
+ value = key.getValue(locale);
+ } catch (CoreException e) {
+ value = null;
+ MessagesEditorPlugin.log(e);
+ }
+ if (value == null)
+ value = "";
+ return value;
+ }
+ }
+
+ private class EditEntryAction extends Action {
+ public EditEntryAction() {
+ super("&Edit", IAction.AS_PUSH_BUTTON);
+ }
+
+ /*
+ * @see org.eclipse.jface.action.Action#run()
+ */
+ public void run() {
+ ResourceBundleKey key = getSelectedEntry();
+ if (key == null) {
+ return;
+ }
+ Shell shell = Display.getCurrent().getActiveShell();
+ Locale[] locales = getLocales();
+ EditResourceBundleEntriesDialog dialog = new EditResourceBundleEntriesDialog(shell, locales);
+ dialog.setResourceBundleKey(key);
+ if (dialog.open() == Window.OK) {
+ updateLabels();
+ }
+ }
+ }
+
+ private class ConfigureColumnsAction extends Action {
+ public ConfigureColumnsAction() {
+ super(null, IAction.AS_PUSH_BUTTON); //$NON-NLS-1$
+ setImageDescriptor(MessagesEditorPlugin.getImageDescriptor("elcl16/conf_columns.gif"));
+ setToolTipText("Configure Columns");
+ }
+
+ /*
+ * @see org.eclipse.jface.action.Action#run()
+ */
+ public void run() {
+ Shell shell = Display.getCurrent().getActiveShell();
+ String[] values = new String[columnConfigs.length];
+ for (int i = 0; i < columnConfigs.length; i++) {
+ String config = columnConfigs[i].toString();
+ if (config.equals("")) //$NON-NLS-1$
+ config = "default"; //$NON-NLS-1$
+ values[i] = config;
+ }
+ ConfigureColumnsDialog dialog = new ConfigureColumnsDialog(shell, values);
+ if (dialog.open() == Window.OK) {
+ String[] result = dialog.getResult();
+ Object[] newConfigs = new Object[result.length];
+ for (int i = 0; i < newConfigs.length; i++) {
+ if (result[i].equals("key")) { //$NON-NLS-1$
+ newConfigs[i] = KEY;
+ } else if (result[i].equals("default")) { //$NON-NLS-1$
+ newConfigs[i] = new Locale("");
+ } else {
+ newConfigs[i] = LocaleUtil.parseLocale(result[i]);
+ }
+ }
+ setColumns(newConfigs);
+ }
+ }
+ }
+
+ private class EditFilterOptionsAction extends Action {
+ public EditFilterOptionsAction() {
+ super(null, IAction.AS_PUSH_BUTTON); //$NON-NLS-1$
+ setImageDescriptor(MessagesEditorPlugin.getImageDescriptor("elcl16/filter_obj.gif"));
+ setToolTipText("Edit Filter Options");
+ }
+
+ /*
+ * @see org.eclipse.jface.action.Action#run()
+ */
+ public void run() {
+ Shell shell = Display.getCurrent().getActiveShell();
+ FilterOptionsDialog dialog = new FilterOptionsDialog(shell);
+ dialog.setInitialFilterOptions(filterOptions);
+ if (dialog.open() == Window.OK) {
+ filterOptions = dialog.getResult();
+ refresh();
+ updateFilterLabel();
+ }
+ }
+ }
+
+ private class RefreshAction extends Action {
+ public RefreshAction() {
+ super(null, IAction.AS_PUSH_BUTTON); //$NON-NLS-1$
+ setImageDescriptor(MessagesEditorPlugin.getImageDescriptor("elcl16/refresh.gif"));
+ setToolTipText("Refresh");
+ }
+
+ /*
+ * @see org.eclipse.jface.action.Action#run()
+ */
+ public void run() {
+ MessagesEditorPlugin.disposeModel();
+ entryList = new ResourceBundleKeyList(new ResourceBundleKey[0]);
+ tableViewer.getTable().setItemCount(0);
+ updateLabels();
+ refresh();
+ }
+ }
+
+ private class BundleStringComparator implements Comparator<ResourceBundleKey> {
+ private final Locale locale;
+ public BundleStringComparator(Locale locale) {
+ this.locale = locale;
+ }
+ public int compare(ResourceBundleKey o1, ResourceBundleKey o2) {
+ String value1 = null;
+ String value2 = null;
+ try {
+ value1 = o1.getValue(locale);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ try {
+ value2 = o2.getValue(locale);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ if (value1 == null)
+ value1 = ""; //$NON-NLS-1$
+ if (value2 == null)
+ value2 = ""; //$NON-NLS-1$
+ return value1.compareToIgnoreCase(value2);
+ }
+ }
+
+ private class ExportAction extends Action {
+ public ExportAction() {
+ super(null, IAction.AS_PUSH_BUTTON); //$NON-NLS-1$
+ setImageDescriptor(MessagesEditorPlugin.getImageDescriptor("elcl16/export.gif"));
+ setToolTipText("Export Current View to CSV or HTML File");
+ }
+
+ /*
+ * @see org.eclipse.jface.action.Action#run()
+ */
+ public void run() {
+ Shell shell = Display.getCurrent().getActiveShell();
+ FileDialog dialog = new FileDialog(shell);
+ dialog.setText("Export File");
+ dialog.setFilterExtensions(new String[] {"*.*", "*.htm; *.html", "*.txt; *.csv"});
+ dialog.setFilterNames(new String[] {"All Files (*.*)", "HTML File (*.htm; *.html)",
+ "Tabulator Separated File (*.txt; *.csv)"});
+ final String filename = dialog.open();
+ if (filename != null) {
+ BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {
+ public void run() {
+ File file = new File(filename);
+ try {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(file),
+ "UTF8")); //$NON-NLS-1$
+ boolean isHtml = filename.endsWith(".htm") || filename.endsWith(".html"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (isHtml) {
+ writer.write("" //$NON-NLS-1$
+ + "<html>\r\n" //$NON-NLS-1$
+ + "<head>\r\n" //$NON-NLS-1$
+ + "<meta http-equiv=Content-Type content=\"text/html; charset=UTF-8\">\r\n" //$NON-NLS-1$
+ + "<style>\r\n" //$NON-NLS-1$
+ + "table {width:100%;}\r\n" //$NON-NLS-1$
+ + "td.sep {height:10px;background:#C0C0C0;}\r\n" //$NON-NLS-1$
+ + "</style>\r\n" //$NON-NLS-1$
+ + "</head>\r\n" //$NON-NLS-1$
+ + "<body>\r\n" //$NON-NLS-1$
+ + "<table width=\"100%\" border=\"1\">\r\n"); //$NON-NLS-1$
+ }
+
+ int size = entryList.getSize();
+ Object[] configs = LocalizationEditor.this.columnConfigs;
+ int valueCount = 0;
+ int missingCount = 0;
+ for (int i = 0; i < size; i++) {
+ ResourceBundleKey key = entryList.getKey(i);
+ if (isHtml) {
+ writer.write("<table border=\"1\">\r\n"); //$NON-NLS-1$
+ }
+ for (int j = 0; j < configs.length; j++) {
+ if (isHtml) {
+ writer.write("<tr><td>"); //$NON-NLS-1$
+ }
+ Object config = configs[j];
+ if (!isHtml && j > 0)
+ writer.write("\t"); //$NON-NLS-1$
+ if (config == KEY) {
+ writer.write(key.getName());
+ } else {
+ Locale locale = (Locale) config;
+ String value;
+ try {
+ value = key.getValue(locale);
+ } catch (CoreException e) {
+ value = null;
+ MessagesEditorPlugin.log(e);
+ }
+ if (value == null) {
+ value = ""; //$NON-NLS-1$
+ missingCount++;
+ } else {
+ valueCount++;
+ }
+ writer.write(EditResourceBundleEntriesDialog.escape(value));
+ }
+ if (isHtml) {
+ writer.write("</td></tr>\r\n"); //$NON-NLS-1$
+ }
+ }
+ if (isHtml) {
+ writer.write("<tr><td class=\"sep\"> </td></tr>\r\n"); //$NON-NLS-1$
+ writer.write("</table>\r\n"); //$NON-NLS-1$
+ } else {
+ writer.write("\r\n"); //$NON-NLS-1$
+ }
+ }
+ if (isHtml) {
+ writer.write("</body>\r\n" + "</html>\r\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ writer.close();
+ Shell shell = Display.getCurrent().getActiveShell();
+ MessageDialog.openInformation(
+ shell,
+ "Finished",
+ "File written successfully.\n\nNumber of entries written: "
+ + entryList.getSize()
+ + "\nNumber of translations: "
+ + valueCount
+ + " ("
+ + missingCount
+ + " missing)");
+ } catch (IOException e) {
+ Shell shell = Display.getCurrent().getActiveShell();
+ ErrorDialog.openError(shell, "Error", "Error saving file.", new Status(
+ IStatus.ERROR,
+ MessagesEditorPlugin.PLUGIN_ID,
+ e.getMessage(),
+ e));
+ }
+ }
+ });
+ }
+ }
+ }
+
+ public static final String ID = "org.eclipse.pde.nls.ui.LocalizationEditor"; //$NON-NLS-1$
+
+ protected static final Object KEY = "key"; // used to indicate the key column
+
+ private static final String PREF_SECTION_NAME = "org.eclipse.pde.nls.ui.LocalizationEditor"; //$NON-NLS-1$
+ private static final String PREF_SORT_ORDER = "sortOrder"; //$NON-NLS-1$
+ private static final String PREF_COLUMNS = "columns"; //$NON-NLS-1$
+ private static final String PREF_FILTER_OPTIONS_FILTER_PLUGINS = "filterOptions_filterPlugins"; //$NON-NLS-1$
+ private static final String PREF_FILTER_OPTIONS_PLUGIN_PATTERNS = "filterOptions_pluginPatterns"; //$NON-NLS-1$
+ private static final String PREF_FILTER_OPTIONS_MISSING_ONLY = "filterOptions_missingOnly"; //$NON-NLS-1$
+
+ // Actions
+ private EditFilterOptionsAction editFiltersAction;
+ private ConfigureColumnsAction selectLanguagesAction;
+ private RefreshAction refreshAction;
+ private ExportAction exportAction;
+
+ // Form
+ protected FormToolkit toolkit = new FormToolkit(Display.getCurrent());
+ private Form form;
+ private Image formImage;
+
+ // Query
+ protected Composite queryComposite;
+ protected Text queryText;
+
+ // Results
+ private Section resultsSection;
+ private Composite tableComposite;
+ protected TableViewer tableViewer;
+ protected Table table;
+ protected ArrayList<TableColumn> columns = new ArrayList<TableColumn>();
+
+ // Data and configuration
+ protected LocalizationEditorInput input;
+ protected ResourceBundleKeyList entryList;
+ protected FilterOptions filterOptions;
+
+ /**
+ * Column configuration. Values may be either <code>KEY</code> or a {@link Locale}.
+ */
+ protected Object[] columnConfigs;
+ /**
+ * Either <code>KEY</code> or a {@link Locale}.
+ */
+ protected Object sortOrder;
+
+ private String lastQuery = "";
+ protected Job searchJob;
+
+ private ISchedulingRule mutexRule = new ISchedulingRule() {
+ public boolean contains(ISchedulingRule rule) {
+ return rule == this;
+ }
+ public boolean isConflicting(ISchedulingRule rule) {
+ return rule == this;
+ }
+ };
+
+ private Label filteredLabel;
+
+ public LocalizationEditor() {
+ }
+
+ public ResourceBundleKey getSelectedEntry() {
+ IStructuredSelection selection = (IStructuredSelection) tableViewer.getSelection();
+ if (selection.size() == 1) {
+ return (ResourceBundleKey) selection.getFirstElement();
+ }
+ return null;
+ }
+
+ public String getQueryText() {
+ return queryText.getText();
+ }
+
+ @Override
+ public void createPartControl(Composite parent) {
+ GridData gd;
+ GridLayout gridLayout;
+
+ form = toolkit.createForm(parent);
+ form.setSeparatorVisible(true);
+ form.setText("Localization");
+
+ form.setImage(formImage = MessagesEditorPlugin.getImageDescriptor("obj16/nls_editor.gif").createImage()); //$NON-NLS-1$
+ toolkit.adapt(form);
+ toolkit.paintBordersFor(form);
+ final Composite body = form.getBody();
+ gridLayout = new GridLayout();
+ gridLayout.numColumns = 1;
+ body.setLayout(gridLayout);
+ toolkit.paintBordersFor(body);
+ toolkit.decorateFormHeading(form);
+
+ queryComposite = toolkit.createComposite(body);
+ gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
+ queryComposite.setLayoutData(gd);
+ gridLayout = new GridLayout(5, false);
+ gridLayout.marginHeight = 0;
+ queryComposite.setLayout(gridLayout);
+ toolkit.paintBordersFor(queryComposite);
+
+ // Form toolbar
+ editFiltersAction = new EditFilterOptionsAction();
+ selectLanguagesAction = new ConfigureColumnsAction();
+ refreshAction = new RefreshAction();
+ exportAction = new ExportAction();
+ IToolBarManager toolBarManager = form.getToolBarManager();
+ toolBarManager.add(refreshAction);
+ toolBarManager.add(editFiltersAction);
+ toolBarManager.add(selectLanguagesAction);
+ toolBarManager.add(exportAction);
+ form.updateToolBar();
+
+ toolkit.createLabel(queryComposite, "Search:");
+
+ // Query text
+ queryText = toolkit.createText(queryComposite, "", SWT.WRAP | SWT.SINGLE); //$NON-NLS-1$
+ queryText.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent e) {
+ if (e.keyCode == SWT.ARROW_DOWN) {
+ table.setFocus();
+ } else if (e.keyCode == SWT.ESC) {
+ queryText.setText(""); //$NON-NLS-1$
+ }
+ }
+ });
+ queryText.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ executeQuery();
+
+ Object[] listeners = getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ IPropertyListener listener = (IPropertyListener) listeners[i];
+ listener.propertyChanged(this, PROP_TITLE);
+ }
+ }
+ });
+ gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
+ queryText.setLayoutData(gd);
+ toolkit.adapt(queryText, true, true);
+
+ ToolBarManager toolBarManager2 = new ToolBarManager(SWT.FLAT);
+ toolBarManager2.createControl(queryComposite);
+ ToolBar control = toolBarManager2.getControl();
+ toolkit.adapt(control);
+
+ // Results section
+ resultsSection = toolkit.createSection(body, ExpandableComposite.TITLE_BAR
+ | ExpandableComposite.LEFT_TEXT_CLIENT_ALIGNMENT);
+ resultsSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ resultsSection.setText("Localization Strings");
+ toolkit.adapt(resultsSection);
+
+ final Composite resultsComposite = toolkit.createComposite(resultsSection, SWT.NONE);
+ toolkit.adapt(resultsComposite);
+ final GridLayout gridLayout2 = new GridLayout();
+ gridLayout2.marginTop = 1;
+ gridLayout2.marginWidth = 1;
+ gridLayout2.marginHeight = 1;
+ gridLayout2.horizontalSpacing = 0;
+ resultsComposite.setLayout(gridLayout2);
+
+ filteredLabel = new Label(resultsSection, SWT.NONE);
+ filteredLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
+ filteredLabel.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
+ filteredLabel.setText(""); //$NON-NLS-1$
+
+ toolkit.paintBordersFor(resultsComposite);
+ resultsSection.setClient(resultsComposite);
+ resultsSection.setTextClient(filteredLabel);
+
+ tableComposite = toolkit.createComposite(resultsComposite, SWT.NONE);
+ tableComposite.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
+ toolkit.adapt(tableComposite);
+ tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+
+ // Table
+ createTableViewer();
+
+ registerContextMenu();
+
+ // Set default configuration
+ filterOptions = new FilterOptions();
+ filterOptions.filterPlugins = false;
+ filterOptions.pluginPatterns = new String[0];
+ filterOptions.keysWithMissingEntriesOnly = false;
+ sortOrder = KEY;
+ columnConfigs = new Object[] {KEY, new Locale(""), new Locale("de")}; //$NON-NLS-1$ //$NON-NLS-2$
+
+ // Load configuration
+ try {
+ loadSettings();
+ } catch (Exception e) {
+ // Ignore
+ }
+
+ updateColumns();
+ updateFilterLabel();
+ table.setSortDirection(SWT.UP);
+ }
+
+ protected void updateFilterLabel() {
+ if (filterOptions.filterPlugins || filterOptions.keysWithMissingEntriesOnly) {
+ filteredLabel.setText("(filtered)");
+ } else {
+ filteredLabel.setText(""); //$NON-NLS-1$
+ }
+ filteredLabel.getParent().layout(true);
+ }
+
+ private void loadSettings() {
+ // TODO Move this to the preferences?
+ IDialogSettings dialogSettings = MessagesEditorPlugin.getDefault().getDialogSettings();
+ IDialogSettings section = dialogSettings.getSection(PREF_SECTION_NAME);
+ if (section == null)
+ return;
+
+ // Sort order
+ String sortOrderString = section.get(PREF_SORT_ORDER);
+ if (sortOrderString != null) {
+ if (sortOrderString.equals(KEY)) {
+ sortOrder = KEY;
+ } else {
+ try {
+ sortOrder = LocaleUtil.parseLocale(sortOrderString);
+ } catch (IllegalArgumentException e) {
+ // Should never happen
+ }
+ }
+ }
+
+ // Columns
+ String columns = section.get(PREF_COLUMNS);
+ if (columns != null) {
+ String[] cols = columns.substring(1, columns.length() - 1).split(","); //$NON-NLS-1$
+ columnConfigs = new Object[cols.length];
+ for (int i = 0; i < cols.length; i++) {
+ String value = cols[i].trim();
+ if (value.equals(KEY)) {
+ columnConfigs[i] = KEY;
+ } else if (value.equals("default")) { //$NON-NLS-1$
+ columnConfigs[i] = new Locale(""); //$NON-NLS-1$
+ } else {
+ try {
+ columnConfigs[i] = LocaleUtil.parseLocale(value);
+ } catch (IllegalArgumentException e) {
+ columnConfigs[i] = null;
+ }
+ }
+ }
+ }
+
+ // Filter options
+ String filterOptions = section.get(PREF_FILTER_OPTIONS_FILTER_PLUGINS);
+ this.filterOptions.filterPlugins = "true".equals(filterOptions); //$NON-NLS-1$
+ String patterns = section.get(PREF_FILTER_OPTIONS_PLUGIN_PATTERNS);
+ if (patterns != null) {
+ String[] split = patterns.substring(1, patterns.length() - 1).split(","); //$NON-NLS-1$
+ for (int i = 0; i < split.length; i++) {
+ split[i] = split[i].trim();
+ }
+ this.filterOptions.pluginPatterns = split;
+ }
+ this.filterOptions.keysWithMissingEntriesOnly = "true".equals(section.get(PREF_FILTER_OPTIONS_MISSING_ONLY)); //$NON-NLS-1$
+
+ // TODO Save column widths
+ }
+
+ private void saveSettings() {
+ IDialogSettings dialogSettings = MessagesEditorPlugin.getDefault().getDialogSettings();
+ IDialogSettings section = dialogSettings.getSection(PREF_SECTION_NAME);
+ if (section == null) {
+ section = dialogSettings.addNewSection(PREF_SECTION_NAME);
+ }
+ // Sort order
+ section.put(PREF_SORT_ORDER, sortOrder.toString());
+
+ // Columns
+ section.put(PREF_COLUMNS, Arrays.toString(columnConfigs));
+
+ // Filter options
+ section.put(PREF_FILTER_OPTIONS_FILTER_PLUGINS, filterOptions.filterPlugins);
+ section.put(PREF_FILTER_OPTIONS_PLUGIN_PATTERNS, Arrays.toString(filterOptions.pluginPatterns));
+ section.put(PREF_FILTER_OPTIONS_MISSING_ONLY, filterOptions.keysWithMissingEntriesOnly);
+ }
+
+ private void createTableViewer() {
+ table = new Table(tableComposite, SWT.VIRTUAL | SWT.FULL_SELECTION | SWT.MULTI);
+ tableViewer = new TableViewer(table);
+ table.setHeaderVisible(true);
+ toolkit.adapt(table);
+ toolkit.paintBordersFor(table);
+ toolkit.adapt(table, true, true);
+
+ tableViewer.setContentProvider(new ILazyContentProvider() {
+ public void updateElement(int index) {
+ tableViewer.replace(entryList.getKey(index), index);
+ }
+ public void dispose() {
+ }
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+ });
+ tableViewer.addDoubleClickListener(new IDoubleClickListener() {
+ public void doubleClick(DoubleClickEvent event) {
+ new EditEntryAction().run();
+ }
+ });
+ }
+
+ private void registerContextMenu() {
+ MenuManager menuManager = new MenuManager("#PopupMenu"); //$NON-NLS-1$
+ menuManager.setRemoveAllWhenShown(true);
+ menuManager.addMenuListener(new IMenuListener() {
+ public void menuAboutToShow(IMenuManager menu) {
+ fillContextMenu(menu);
+ }
+ });
+ Menu contextMenu = menuManager.createContextMenu(table);
+ table.setMenu(contextMenu);
+ getSite().registerContextMenu(menuManager, getSite().getSelectionProvider());
+ }
+
+ protected void fillContextMenu(IMenuManager menu) {
+ int selectionCount = table.getSelectionCount();
+ if (selectionCount == 1) {
+ menu.add(new EditEntryAction());
+ menu.add(new Separator());
+ }
+ MenuManager showInSubMenu = new MenuManager("&Show In");
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ IContributionItem item = ContributionItemFactory.VIEWS_SHOW_IN.create(window);
+ showInSubMenu.add(item);
+ menu.add(showInSubMenu);
+ }
+
+ @Override
+ public void setFocus() {
+ queryText.setFocus();
+ }
+
+ @Override
+ public void doSave(IProgressMonitor monitor) {
+ }
+
+ @Override
+ public void doSaveAs() {
+ }
+
+ @Override
+ public void init(IEditorSite site, IEditorInput input) throws PartInitException {
+ setSite(site);
+ setInput(input);
+ this.input = (LocalizationEditorInput) input;
+ }
+
+ @Override
+ public boolean isDirty() {
+ return false;
+ }
+
+ @Override
+ public boolean isSaveAsAllowed() {
+ return false;
+ }
+
+ /*
+ * @see org.eclipse.ui.part.WorkbenchPart#dispose()
+ */
+ @Override
+ public void dispose() {
+ saveSettings();
+ if (formImage != null) {
+ formImage.dispose();
+ formImage = null;
+ }
+ MessagesEditorPlugin.disposeModel();
+ }
+
+ protected void executeQuery() {
+ String pattern = queryText.getText();
+ lastQuery = pattern;
+ executeQuery(pattern);
+ }
+
+ protected void executeQuery(final String pattern) {
+ if (searchJob != null) {
+ searchJob.cancel();
+ }
+ searchJob = new Job("Localization Editor Search...") {
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ Display.getDefault().syncExec(new Runnable() {
+ public void run() {
+ form.setBusy(true);
+ }
+ });
+
+ String keyPattern = pattern;
+ if (!pattern.endsWith("*")) { //$NON-NLS-1$
+ keyPattern = pattern.concat("*"); //$NON-NLS-1$
+ }
+ String strPattern = keyPattern;
+ if (strPattern.length() > 0 && !strPattern.startsWith("*")) { //$NON-NLS-1$
+ strPattern = "*".concat(strPattern); //$NON-NLS-1$
+ }
+
+ ResourceBundleModel model = MessagesEditorPlugin.getModel(new NullProgressMonitor());
+ Locale[] locales = getLocales();
+
+ // Collect keys
+ ResourceBundleKey[] keys;
+ if (!filterOptions.filterPlugins
+ || filterOptions.pluginPatterns == null
+ || filterOptions.pluginPatterns.length == 0) {
+
+ // Ensure the bundles are loaded
+ for (Locale locale : locales) {
+ try {
+ model.loadBundles(locale);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ }
+
+ try {
+ keys = model.getAllKeys();
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ keys = new ResourceBundleKey[0];
+ }
+ } else {
+ String[] patterns = filterOptions.pluginPatterns;
+ StringMatcher[] matchers = new StringMatcher[patterns.length];
+ for (int i = 0; i < matchers.length; i++) {
+ matchers[i] = new StringMatcher(patterns[i], true, false);
+ }
+
+ int size = 0;
+ ResourceBundleFamily[] allFamilies = model.getFamilies();
+ ArrayList<ResourceBundleFamily> families = new ArrayList<ResourceBundleFamily>();
+ for (int i = 0; i < allFamilies.length; i++) {
+ ResourceBundleFamily family = allFamilies[i];
+ String pluginId = family.getPluginId();
+ for (StringMatcher matcher : matchers) {
+ if (matcher.match(pluginId)) {
+ families.add(family);
+ break;
+ }
+ }
+
+ }
+ for (ResourceBundleFamily family : families) {
+ size += family.getKeyCount();
+ }
+
+ ArrayList<ResourceBundleKey> filteredKeys = new ArrayList<ResourceBundleKey>(size);
+ for (ResourceBundleFamily family : families) {
+ // Ensure the bundles are loaded
+ for (Locale locale : locales) {
+ try {
+ ResourceBundle bundle = family.getBundle(locale);
+ if (bundle != null)
+ bundle.load();
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ }
+
+ ResourceBundleKey[] familyKeys = family.getKeys();
+ for (ResourceBundleKey key : familyKeys) {
+ filteredKeys.add(key);
+ }
+ }
+ keys = filteredKeys.toArray(new ResourceBundleKey[filteredKeys.size()]);
+ }
+
+ // Filter keys
+ ArrayList<ResourceBundleKey> filtered = new ArrayList<ResourceBundleKey>();
+
+ StringMatcher keyMatcher = new StringMatcher(keyPattern, true, false);
+ StringMatcher strMatcher = new StringMatcher(strPattern, true, false);
+ for (ResourceBundleKey key : keys) {
+ if (monitor.isCanceled())
+ return Status.OK_STATUS;
+
+ // Missing entries
+ if (filterOptions.keysWithMissingEntriesOnly) {
+ boolean hasMissingEntry = false;
+ // Check all columns for missing values
+ for (Object config : columnConfigs) {
+ if (config == KEY)
+ continue;
+ Locale locale = (Locale) config;
+ String value = null;
+ try {
+ value = key.getValue(locale);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ if (value == null || value.length() == 0) {
+ hasMissingEntry = true;
+ break;
+ }
+ }
+ if (!hasMissingEntry)
+ continue;
+ }
+
+ // Match key
+ if (keyMatcher.match(key.getName())) {
+ filtered.add(key);
+ continue;
+ }
+
+ // Match entries
+ for (Object config : columnConfigs) {
+ if (config == KEY)
+ continue;
+ Locale locale = (Locale) config;
+ String value = null;
+ try {
+ value = key.getValue(locale);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ if (strMatcher.match(value)) {
+ filtered.add(key);
+ break;
+ }
+ }
+ }
+
+ ResourceBundleKey[] array = filtered.toArray(new ResourceBundleKey[filtered.size()]);
+ if (sortOrder == KEY) {
+ Arrays.sort(array, new Comparator<ResourceBundleKey>() {
+ public int compare(ResourceBundleKey o1, ResourceBundleKey o2) {
+ return o1.getName().compareToIgnoreCase(o2.getName());
+ }
+ });
+ } else {
+ Locale locale = (Locale) sortOrder;
+ Arrays.sort(array, new BundleStringComparator(locale));
+ }
+ entryList = new ResourceBundleKeyList(array);
+
+ if (monitor.isCanceled())
+ return Status.OK_STATUS;
+
+ final ResourceBundleKeyList entryList2 = entryList;
+ Display.getDefault().syncExec(new Runnable() {
+ public void run() {
+ form.setBusy(false);
+ if (entryList2 != null) {
+ entryList = entryList2;
+ }
+ setSearchResult(entryList);
+ }
+ });
+ return Status.OK_STATUS;
+ }
+ };
+ searchJob.setSystem(true);
+ searchJob.setRule(mutexRule);
+ searchJob.schedule();
+ }
+
+ protected void updateTableLayout() {
+ table.getParent().layout(true, true);
+ }
+
+ protected void setSearchResult(ResourceBundleKeyList entryList) {
+ table.removeAll();
+ if (entryList != null) {
+ table.setItemCount(entryList.getSize());
+ } else {
+ table.setItemCount(0);
+ }
+ updateTableLayout();
+ }
+
+ public void refresh() {
+ executeQuery(lastQuery);
+ }
+
+ public void updateLabels() {
+ table.redraw();
+ table.update();
+ }
+
+ /**
+ * @param columnConfigs an array containing <code>KEY</code> and {@link Locale} values
+ */
+ public void setColumns(Object[] columnConfigs) {
+ this.columnConfigs = columnConfigs;
+ updateColumns();
+ }
+
+ public void updateColumns() {
+ for (TableColumn column : columns) {
+ column.dispose();
+ }
+ columns.clear();
+
+ TableColumnLayout tableColumnLayout = new TableColumnLayout();
+ tableComposite.setLayout(tableColumnLayout);
+
+ HashSet<Locale> localesToUnload = new HashSet<Locale>(4);
+ Locale[] currentLocales = getLocales();
+ for (Locale locale : currentLocales) {
+ localesToUnload.add(locale);
+ }
+
+ // Create columns
+ for (Object config : columnConfigs) {
+ if (config == null)
+ continue;
+
+ final TableViewerColumn viewerColumn = new TableViewerColumn(tableViewer, SWT.NONE);
+ TableColumn column = viewerColumn.getColumn();
+ if (config == KEY) {
+ column.setText("Key");
+ } else {
+ Locale locale = (Locale) config;
+ if (locale.getLanguage().equals("")) { //$NON-NLS-1$
+ column.setText("Default Bundle");
+ } else {
+ String displayName = locale.getDisplayName();
+ if (displayName.equals("")) //$NON-NLS-1$
+ displayName = locale.toString();
+ column.setText(displayName);
+ localesToUnload.remove(locale);
+ }
+ }
+
+ viewerColumn.setLabelProvider(new LocalizationLabelProvider(config));
+ tableColumnLayout.setColumnData(column, new ColumnWeightData(33));
+ columns.add(column);
+ column.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ int size = columns.size();
+ for (int i = 0; i < size; i++) {
+ TableColumn column = columns.get(i);
+ if (column == e.widget) {
+ Object config = columnConfigs[i];
+ sortOrder = config;
+ table.setSortColumn(column);
+ table.setSortDirection(SWT.UP);
+ refresh();
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ // Update sort order
+ List<Object> configs = Arrays.asList(columnConfigs);
+ if (!configs.contains(sortOrder)) {
+ sortOrder = KEY; // fall back to default sort order
+ }
+ int index = configs.indexOf(sortOrder);
+ if (index != -1)
+ table.setSortColumn(columns.get(index));
+
+ refresh();
+ }
+
+ /*
+ * @see org.eclipse.ui.part.WorkbenchPart#getAdapter(java.lang.Class)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getAdapter(Class adapter) {
+ if (IShowInSource.class == adapter) {
+ return new IShowInSource() {
+ public ShowInContext getShowInContext() {
+ ResourceBundleKey entry = getSelectedEntry();
+ if (entry == null)
+ return null;
+ ResourceBundle bundle = entry.getParent().getBundle(new Locale(""));
+ if (bundle == null)
+ return null;
+ Object resource = bundle.getUnderlyingResource();
+ return new ShowInContext(resource, new StructuredSelection(resource));
+ }
+ };
+ }
+ return super.getAdapter(adapter);
+ }
+
+ /**
+ * Returns the currently displayed locales.
+ *
+ * @return the currently displayed locales
+ */
+ public Locale[] getLocales() {
+ ArrayList<Locale> locales = new ArrayList<Locale>(columnConfigs.length);
+ for (Object config : columnConfigs) {
+ if (config instanceof Locale) {
+ Locale locale = (Locale) config;
+ locales.add(locale);
+ }
+ }
+ return locales.toArray(new Locale[locales.size()]);
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInput.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInput.java
new file mode 100644
index 0000000..2ad8c7e
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInput.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.editor;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IMemento;
+import org.eclipse.ui.IPersistableElement;
+
+public class LocalizationEditorInput implements IEditorInput, IPersistableElement {
+
+ public LocalizationEditorInput() {
+ }
+
+ /*
+ * @see org.eclipse.ui.IEditorInput#exists()
+ */
+ public boolean exists() {
+ return true;
+ }
+
+ /*
+ * @see org.eclipse.ui.IEditorInput#getImageDescriptor()
+ */
+ public ImageDescriptor getImageDescriptor() {
+ return ImageDescriptor.getMissingImageDescriptor();
+ }
+
+ /*
+ * @see org.eclipse.ui.IEditorInput#getName()
+ */
+ public String getName() {
+ return "Localization Editor";
+ }
+
+ /*
+ * @see org.eclipse.ui.IEditorInput#getPersistable()
+ */
+ public IPersistableElement getPersistable() {
+ return this;
+ }
+
+ /*
+ * @see org.eclipse.ui.IEditorInput#getToolTipText()
+ */
+ public String getToolTipText() {
+ return getName();
+ }
+
+ /*
+ * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
+ */
+ @SuppressWarnings("unchecked")
+ public Object getAdapter(Class adapter) {
+ return null;
+ }
+
+ /*
+ * @see org.eclipse.ui.IPersistableElement#getFactoryId()
+ */
+ public String getFactoryId() {
+ return LocalizationEditorInputFactory.FACTORY_ID;
+ }
+
+ /*
+ * @see org.eclipse.ui.IPersistable#saveState(org.eclipse.ui.IMemento)
+ */
+ public void saveState(IMemento memento) {
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInputFactory.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInputFactory.java
new file mode 100644
index 0000000..698c218
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/editor/LocalizationEditorInputFactory.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.editor;
+
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.ui.IElementFactory;
+import org.eclipse.ui.IMemento;
+
+public class LocalizationEditorInputFactory implements IElementFactory {
+
+ public static final String FACTORY_ID = "org.eclipse.pde.nls.ui.LocalizationEditorInputFactory"; //$NON-NLS-1$
+
+ public LocalizationEditorInputFactory() {
+ }
+
+ /*
+ * @see org.eclipse.ui.IElementFactory#createElement(org.eclipse.ui.IMemento)
+ */
+ public IAdaptable createElement(IMemento memento) {
+ return new LocalizationEditorInput();
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundle.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundle.java
new file mode 100644
index 0000000..63f8719
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundle.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJarEntryResource;
+
+/**
+ * A <code>ResourceBundle</code> represents a single <code>.properties</code> file.
+ * <p>
+ * <code>ResourceBundle</code> implements lazy loading. A bundle will be loaded
+ * automatically when its entries are accessed. It can through the parent model by
+ * calling {@link ResourceBundleModel#unloadBundles(Locale)} with the proper locale.
+ * </p>
+ */
+public class ResourceBundle extends ResourceBundleElement {
+
+ private static boolean debug = false;
+
+ /**
+ * The bundle's locale.
+ */
+ private Locale locale;
+ /**
+ * The underlying resource. Either an {@link IFile} or an {@link IJarEntryResource}.
+ */
+ private Object resource;
+
+ private HashMap<String, String> entries;
+
+ public ResourceBundle(ResourceBundleFamily parent, Object resource, Locale locale) {
+ super(parent);
+ this.resource = resource;
+ this.locale = locale;
+ if (locale == null)
+ throw new IllegalArgumentException("Locale may not be null.");
+ }
+
+ /**
+ * Returns the family to which this bundle belongs.
+ *
+ * @return the family to which this bundle belongs
+ */
+ public ResourceBundleFamily getFamily() {
+ return (ResourceBundleFamily) super.getParent();
+ }
+
+ /**
+ * Returns the locale.
+ *
+ * @return the locale
+ */
+ public Locale getLocale() {
+ return locale;
+ }
+
+ public String getString(String key) throws CoreException {
+ load();
+ return entries.get(key);
+ }
+
+ /**
+ * Returns the underlying resource. This may be an {@link IFile}
+ * or an {@link IJarEntryResource}.
+ *
+ * @return the underlying resource (an {@link IFile} or an {@link IJarEntryResource})
+ */
+ public Object getUnderlyingResource() {
+ return resource;
+ }
+
+ protected boolean isLoaded() {
+ return entries != null;
+ }
+
+ public void load() throws CoreException {
+ if (isLoaded())
+ return;
+ entries = new HashMap<String, String>();
+
+ if (resource instanceof IFile) {
+ if (debug) {
+ System.out.println("Loading " + resource + "...");
+ }
+ IFile file = (IFile) resource;
+ InputStream inputStream = file.getContents();
+ Properties properties = new Properties();
+ try {
+ properties.load(inputStream);
+ putAll(properties);
+ } catch (IOException e) {
+ MessagesEditorPlugin.log("Error reading property file.", e);
+ }
+ } else if (resource instanceof IJarEntryResource) {
+ IJarEntryResource jarEntryResource = (IJarEntryResource) resource;
+ InputStream inputStream = jarEntryResource.getContents();
+ Properties properties = new Properties();
+ try {
+ properties.load(inputStream);
+ putAll(properties);
+ } catch (IOException e) {
+ MessagesEditorPlugin.log("Error reading property file.", e);
+ }
+ } else {
+ MessagesEditorPlugin.log("Unknown resource type.", new RuntimeException());
+ }
+ }
+
+ protected void unload() {
+ entries = null;
+ }
+
+ public boolean isReadOnly() {
+ if (resource instanceof IJarEntryResource)
+ return true;
+ if (resource instanceof IFile) {
+ IFile file = (IFile) resource;
+ return file.isReadOnly() || file.isLinked();
+ }
+ return false;
+ }
+
+ protected void putAll(Properties properties) throws CoreException {
+ Set<Entry<Object, Object>> entrySet = properties.entrySet();
+ Iterator<Entry<Object, Object>> iter = entrySet.iterator();
+ ResourceBundleFamily family = getFamily();
+ while (iter.hasNext()) {
+ Entry<Object, Object> next = iter.next();
+ Object key = next.getKey();
+ Object value = next.getValue();
+ if (key instanceof String && value instanceof String) {
+ String stringKey = (String) key;
+ entries.put(stringKey, (String) value);
+ family.addKey(stringKey);
+ }
+ }
+ }
+
+ public void put(String key, String value) throws CoreException {
+ load();
+ ResourceBundleFamily family = getFamily();
+ entries.put(key, value);
+ family.addKey(key);
+ }
+
+ public String[] getKeys() throws CoreException {
+ load();
+ Set<String> keySet = entries.keySet();
+ return keySet.toArray(new String[keySet.size()]);
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleElement.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleElement.java
new file mode 100644
index 0000000..4025bda
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleElement.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+public abstract class ResourceBundleElement {
+
+ private final ResourceBundleElement parent;
+
+ public ResourceBundleElement(ResourceBundleElement parent) {
+ this.parent = parent;
+ }
+
+ public ResourceBundleElement getParent() {
+ return parent;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleFamily.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleFamily.java
new file mode 100644
index 0000000..dedb937
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleFamily.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A <code>ResourceBundleGroup</code> represents a group of resource bundles
+ * that belong together. Member resource bundles may reside in the same project as the
+ * default resource bundle, or in case of a plugin project, in a separate fragment
+ * project.
+ */
+public class ResourceBundleFamily extends ResourceBundleElement {
+
+ /**
+ * The project name of the default bundle.
+ */
+ private String projectName;
+ /**
+ * The plugin id of the default bundle, or <code>null</code> if not a plugin or fragment project.
+ */
+ private String pluginId;
+ /**
+ * The package name or path.
+ */
+ private String packageName;
+ /**
+ * The base name that all family members have in common.
+ */
+ private String baseName;
+ /**
+ * The members that belong to this resource bundle family (excluding the default bundle).
+ */
+ private ArrayList<ResourceBundle> members = new ArrayList<ResourceBundle>();
+ /**
+ * A collection of known keys.
+ */
+ private HashMap<String, ResourceBundleKey> keys = new HashMap<String, ResourceBundleKey>();
+
+ public ResourceBundleFamily(ResourceBundleModel parent, String projectName, String pluginId,
+ String packageName, String baseName) {
+ super(parent);
+ this.projectName = projectName;
+ this.pluginId = pluginId;
+ this.packageName = packageName;
+ this.baseName = baseName;
+ }
+
+ public String getProjectName() {
+ return projectName;
+ }
+
+ public String getPluginId() {
+ return pluginId;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public String getBaseName() {
+ return baseName;
+ }
+
+ public ResourceBundle[] getBundles() {
+ return members.toArray(new ResourceBundle[members.size()]);
+ }
+
+ public ResourceBundle getBundle(Locale locale) {
+ for (ResourceBundle bundle : members) {
+ if (bundle.getLocale().equals(locale)) {
+ return bundle;
+ }
+ }
+ return null;
+ }
+
+ /*
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ if (pluginId != null) {
+ return baseName.hashCode() ^ pluginId.hashCode();
+ } else {
+ return baseName.hashCode() ^ projectName.hashCode();
+ }
+ }
+
+ protected void addBundle(ResourceBundle bundle) throws CoreException {
+ Assert.isTrue(bundle.getParent() == this);
+ members.add(bundle);
+ }
+
+ protected void addKey(String key) {
+ if (keys.get(key) == null) {
+ keys.put(key, new ResourceBundleKey(this, key));
+ }
+ }
+
+ public ResourceBundleKey[] getKeys() {
+ Collection<ResourceBundleKey> values = keys.values();
+ return values.toArray(new ResourceBundleKey[values.size()]);
+ }
+
+ public int getKeyCount() {
+ return keys.size();
+ }
+
+ /*
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "projectName=" + projectName + ", packageName=" + packageName + ", baseName=" + baseName;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKey.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKey.java
new file mode 100644
index 0000000..04cff98
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKey.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+import java.util.Locale;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A <code>ResourceBundleKey</code> represents a key used in one or more bundles of
+ * a {@link ResourceBundleFamily}.
+ */
+public class ResourceBundleKey extends ResourceBundleElement {
+
+ private String key;
+
+ public ResourceBundleKey(ResourceBundleFamily parent, String key) {
+ super(parent);
+ this.key = key;
+ }
+
+ /*
+ * @see org.eclipse.nls.ui.model.ResourceBundleElement#getParent()
+ */
+ @Override
+ public ResourceBundleFamily getParent() {
+ return (ResourceBundleFamily) super.getParent();
+ }
+
+ public ResourceBundleFamily getFamily() {
+ return getParent();
+ }
+
+ public String getName() {
+ return key;
+ }
+
+ public String getValue(Locale locale) throws CoreException {
+ ResourceBundle bundle = getFamily().getBundle(locale);
+ if (bundle == null)
+ return null;
+ return bundle.getString(key);
+ }
+
+ public boolean hasValue(Locale locale) throws CoreException {
+ return getValue(locale) != null;
+ }
+
+ /*
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "ResourceBundleKey {" + key + "}";
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKeyList.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKeyList.java
new file mode 100644
index 0000000..8683638
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleKeyList.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+public class ResourceBundleKeyList {
+
+ private final ResourceBundleKey[] keys;
+
+ public ResourceBundleKeyList(ResourceBundleKey[] keys) {
+ this.keys = keys;
+ }
+
+ public ResourceBundleKey getKey(int index) {
+ return keys[index];
+ }
+
+ public int getSize() {
+ return keys.length;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleModel.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleModel.java
new file mode 100644
index 0000000..500ce6a
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/model/ResourceBundleModel.java
@@ -0,0 +1,520 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.model;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Locale;
+
+import org.eclipse.babel.editor.plugin.MessagesEditorPlugin;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJarEntryResource;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.pde.core.plugin.IFragmentModel;
+import org.eclipse.pde.core.plugin.IPluginModelBase;
+import org.eclipse.pde.core.plugin.PluginRegistry;
+
+/**
+ * A <code>ResourceBundleModel</code> is the host for all {@link ResourceBundleFamily} elements.
+ */
+public class ResourceBundleModel extends ResourceBundleElement {
+
+ private static final String PROPERTIES_SUFFIX = ".properties"; //$NON-NLS-1$
+
+ private static final String JAVA_NATURE = "org.eclipse.jdt.core.javanature"; //$NON-NLS-1$
+
+ private ArrayList<ResourceBundleFamily> bundleFamilies = new ArrayList<ResourceBundleFamily>();
+
+ /**
+ * The locales for which all bundles have been loaded.
+ */
+ // TODO Perhaps we should add reference counting to prevent unexpected unloading of bundles
+ private HashSet<Locale> loadedLocales = new HashSet<Locale>();
+
+ public ResourceBundleModel(IProgressMonitor monitor) {
+ super(null);
+ try {
+ populateFromWorkspace(monitor);
+ } catch (CoreException e) {
+ MessagesEditorPlugin.log(e);
+ }
+ }
+
+ /**
+ * Returns all resource bundle families contained in this model.
+ *
+ * @return all resource bundle families contained in this model
+ */
+ public ResourceBundleFamily[] getFamilies() {
+ return bundleFamilies.toArray(new ResourceBundleFamily[bundleFamilies.size()]);
+ }
+
+ public ResourceBundleFamily[] getFamiliesForPluginId(String pluginId) {
+ ArrayList<ResourceBundleFamily> found = new ArrayList<ResourceBundleFamily>();
+ for (ResourceBundleFamily family : bundleFamilies) {
+ if (family.getPluginId().equals(pluginId)) {
+ found.add(family);
+ }
+ }
+ return found.toArray(new ResourceBundleFamily[found.size()]);
+ }
+
+ public ResourceBundleFamily[] getFamiliesForProjectName(String projectName) {
+ ArrayList<ResourceBundleFamily> found = new ArrayList<ResourceBundleFamily>();
+ for (ResourceBundleFamily family : bundleFamilies) {
+ if (family.getProjectName().equals(projectName)) {
+ found.add(family);
+ }
+ }
+ return found.toArray(new ResourceBundleFamily[found.size()]);
+ }
+
+ public ResourceBundleFamily[] getFamiliesForProject(IProject project) {
+ return getFamiliesForProjectName(project.getName());
+ }
+
+ /**
+ * Returns an array of all currently known bundle keys. This always includes
+ * the keys from the default bundles and may include some additional keys
+ * from bundles that have been loaded sometime and that contain keys not found in
+ * a bundle's default bundle. When a bundle is unloaded, these additional keys
+ * will not be removed from the model.
+ *
+ * @return the array of bundles keys
+ * @throws CoreException
+ */
+ public ResourceBundleKey[] getAllKeys() throws CoreException {
+ Locale root = new Locale("", "", "");
+
+ // Ensure default bundle is loaded and count keys
+ int size = 0;
+ for (ResourceBundleFamily family : bundleFamilies) {
+ ResourceBundle bundle = family.getBundle(root);
+ if (bundle != null)
+ bundle.load();
+ size += family.getKeyCount();
+ }
+
+ ArrayList<ResourceBundleKey> allKeys = new ArrayList<ResourceBundleKey>(size);
+ for (ResourceBundleFamily family : bundleFamilies) {
+ ResourceBundleKey[] keys = family.getKeys();
+ for (ResourceBundleKey key : keys) {
+ allKeys.add(key);
+ }
+ }
+
+ return allKeys.toArray(new ResourceBundleKey[allKeys.size()]);
+ }
+
+ /**
+ * Loads all the bundles for the given locale into memory.
+ *
+ * @param locale the locale of the bundles to load
+ * @throws CoreException
+ */
+ public void loadBundles(Locale locale) throws CoreException {
+ ResourceBundleFamily[] families = getFamilies();
+ for (ResourceBundleFamily family : families) {
+ ResourceBundle bundle = family.getBundle(locale);
+ if (bundle != null)
+ bundle.load();
+ }
+ loadedLocales.add(locale);
+ }
+
+ /**
+ * Unloads all the bundles for the given locale from this model. The default
+ * bundle cannot be unloaded. Such a request will be ignored.
+ *
+ * @param locale the locale of the bundles to unload
+ */
+ public void unloadBundles(Locale locale) {
+ if ("".equals(locale.getLanguage()))
+ return; // never unload the default bundles
+
+ ResourceBundleFamily[] families = getFamilies();
+ for (ResourceBundleFamily family : families) {
+ ResourceBundle bundle = family.getBundle(locale);
+ if (bundle != null)
+ bundle.unload();
+ }
+ loadedLocales.remove(locale);
+ }
+
+ private void populateFromWorkspace(IProgressMonitor monitor) throws CoreException {
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot root = workspace.getRoot();
+ IProject[] projects = root.getProjects();
+ for (IProject project : projects) {
+ try {
+ if (!project.isOpen())
+ continue;
+
+ IJavaProject javaProject = (IJavaProject) project.getNature(JAVA_NATURE);
+
+ // Plugin and fragment projects
+ IPluginModelBase pluginModel = PluginRegistry.findModel(project);
+ String pluginId = null;
+ if (pluginModel != null) {
+ // Get plugin id
+ pluginId = pluginModel.getBundleDescription().getName(); // OSGi bundle name
+ if (pluginId == null) {
+ pluginId = pluginModel.getPluginBase().getId(); // non-OSGi plug-in id
+ }
+ boolean isFragment = pluginModel instanceof IFragmentModel;
+ if (isFragment) {
+ IFragmentModel fragmentModel = (IFragmentModel) pluginModel;
+ pluginId = fragmentModel.getFragment().getPluginId();
+ }
+
+ // Look for additional 'nl' resources
+ IFolder nl = project.getFolder("nl"); //$NON-NLS-1$
+ if (isFragment && nl.exists()) {
+ IResource[] members = nl.members();
+ for (IResource member : members) {
+ if (member instanceof IFolder) {
+ IFolder langFolder = (IFolder) member;
+ String language = langFolder.getName();
+
+ // Collect property files
+ IFile[] propertyFiles = collectPropertyFiles(langFolder);
+ for (IFile file : propertyFiles) {
+ // Compute path name
+ IPath path = file.getProjectRelativePath();
+ String country = ""; //$NON-NLS-1$
+ String packageName = null;
+ int segmentCount = path.segmentCount();
+ if (segmentCount > 1) {
+ StringBuilder builder = new StringBuilder();
+
+ // Segment 0: 'nl'
+ // Segment 1: language code
+ // Segment 2: (country code)
+ int begin = 2;
+ if (segmentCount > 2 && isCountry(path.segment(2))) {
+ begin = 3;
+ country = path.segment(2);
+ }
+
+ for (int i = begin; i < segmentCount - 1; i++) {
+ if (i > begin)
+ builder.append('.');
+ builder.append(path.segment(i));
+ }
+ packageName = builder.toString();
+ }
+
+ String baseName = getBaseName(file.getName());
+
+ ResourceBundleFamily family = getOrCreateFamily(
+ project.getName(),
+ pluginId,
+ packageName,
+ baseName);
+ addBundle(family, getLocale(language, country), file);
+ }
+ }
+ }
+ }
+
+ // Collect property files
+ if (isFragment || javaProject == null) {
+ IFile[] propertyFiles = collectPropertyFiles(project);
+ for (IFile file : propertyFiles) {
+ IPath path = file.getProjectRelativePath();
+ int segmentCount = path.segmentCount();
+
+ if (segmentCount > 0 && path.segment(0).equals("nl")) //$NON-NLS-1$
+ continue; // 'nl' resource have been processed above
+
+ // Guess package name
+ String packageName = null;
+ if (segmentCount > 1) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < segmentCount - 1; i++) {
+ if (i > 0)
+ builder.append('.');
+ builder.append(path.segment(i));
+ }
+ packageName = builder.toString();
+ }
+
+ String baseName = getBaseName(file.getName());
+ String language = getLanguage(file.getName());
+ String country = getCountry(file.getName());
+
+ ResourceBundleFamily family = getOrCreateFamily(
+ project.getName(),
+ pluginId,
+ packageName,
+ baseName);
+ addBundle(family, getLocale(language, country), file);
+ }
+ }
+
+ }
+
+ // Look for resource bundles in Java packages (output folders, e.g. 'bin', will be ignored)
+ if (javaProject != null) {
+ IClasspathEntry[] classpathEntries = javaProject.getResolvedClasspath(true);
+ for (IClasspathEntry entry : classpathEntries) {
+ if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
+ IPath path = entry.getPath();
+ IFolder folder = workspace.getRoot().getFolder(path);
+ IFile[] propertyFiles = collectPropertyFiles(folder);
+
+ for (IFile file : propertyFiles) {
+ String name = file.getName();
+ String baseName = getBaseName(name);
+ String language = getLanguage(name);
+ String country = getCountry(name);
+ IPackageFragment pf = javaProject.findPackageFragment(file.getParent()
+ .getFullPath());
+ String packageName = pf.getElementName();
+
+ ResourceBundleFamily family = getOrCreateFamily(
+ project.getName(),
+ pluginId,
+ packageName,
+ baseName);
+
+ addBundle(family, getLocale(language, country), file);
+ }
+ } else if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) {
+ IPackageFragmentRoot[] findPackageFragmentRoots = javaProject.findPackageFragmentRoots(entry);
+ for (IPackageFragmentRoot packageFragmentRoot : findPackageFragmentRoots) {
+ IJavaElement[] children = packageFragmentRoot.getChildren();
+ for (IJavaElement child : children) {
+ IPackageFragment pf = (IPackageFragment) child;
+ Object[] nonJavaResources = pf.getNonJavaResources();
+
+ for (Object resource : nonJavaResources) {
+ if (resource instanceof IJarEntryResource) {
+ IJarEntryResource jarEntryResource = (IJarEntryResource) resource;
+ String name = jarEntryResource.getName();
+ if (name.endsWith(PROPERTIES_SUFFIX)) {
+ String baseName = getBaseName(name);
+ String language = getLanguage(name);
+ String country = getCountry(name);
+ String packageName = pf.getElementName();
+
+ ResourceBundleFamily family = getOrCreateFamily(
+ project.getName(),
+ pluginId,
+ packageName,
+ baseName);
+
+ addBundle(
+ family,
+ getLocale(language, country),
+ jarEntryResource);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Collect non-Java resources
+ Object[] nonJavaResources = javaProject.getNonJavaResources();
+ ArrayList<IFile> files = new ArrayList<IFile>();
+ for (Object resource : nonJavaResources) {
+ if (resource instanceof IContainer) {
+ IContainer container = (IContainer) resource;
+ collectPropertyFiles(container, files);
+ } else if (resource instanceof IFile) {
+ IFile file = (IFile) resource;
+ String name = file.getName();
+ if (isIgnoredFilename(name))
+ continue;
+ if (name.endsWith(PROPERTIES_SUFFIX)) {
+ files.add(file);
+ }
+ }
+ }
+ for (IFile file : files) {
+
+ // Convert path to package name format
+ IPath path = file.getProjectRelativePath();
+ String packageName = null;
+ int segmentCount = path.segmentCount();
+ if (segmentCount > 1) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < segmentCount - 1; i++) {
+ if (i > 0)
+ builder.append('.');
+ builder.append(path.segment(i));
+ }
+ packageName = builder.toString();
+ }
+
+ String baseName = getBaseName(file.getName());
+ String language = getLanguage(file.getName());
+ String country = getCountry(file.getName());
+
+ ResourceBundleFamily family = getOrCreateFamily(
+ project.getName(),
+ pluginId,
+ packageName,
+ baseName);
+ addBundle(family, getLocale(language, country), file);
+ }
+
+ }
+ } catch (Exception e) {
+ MessagesEditorPlugin.log(e);
+ }
+ }
+ }
+
+ private IFile[] collectPropertyFiles(IContainer container) throws CoreException {
+ ArrayList<IFile> files = new ArrayList<IFile>();
+ collectPropertyFiles(container, files);
+ return files.toArray(new IFile[files.size()]);
+ }
+
+ private void collectPropertyFiles(IContainer container, ArrayList<IFile> files) throws CoreException {
+ IResource[] members = container.members();
+ for (IResource resource : members) {
+ if (!resource.exists())
+ continue;
+ if (resource instanceof IContainer) {
+ IContainer childContainer = (IContainer) resource;
+ collectPropertyFiles(childContainer, files);
+ } else if (resource instanceof IFile) {
+ IFile file = (IFile) resource;
+ String name = file.getName();
+ if (file.getProjectRelativePath().segmentCount() == 0 && isIgnoredFilename(name))
+ continue;
+ if (name.endsWith(PROPERTIES_SUFFIX)) {
+ files.add(file);
+ }
+ }
+ }
+ }
+
+ private boolean isCountry(String name) {
+ if (name == null || name.length() != 2)
+ return false;
+ char c1 = name.charAt(0);
+ char c2 = name.charAt(1);
+ return 'A' <= c1 && c1 <= 'Z' && 'A' <= c2 && c2 <= 'Z';
+ }
+
+ private Locale getLocale(String language, String country) {
+ if (language == null)
+ language = ""; //$NON-NLS-1$
+ if (country == null)
+ country = ""; //$NON-NLS-1$
+ return new Locale(language, country);
+ }
+
+ private void addBundle(ResourceBundleFamily family, Locale locale, Object resource) throws CoreException {
+ ResourceBundle bundle = new ResourceBundle(family, resource, locale);
+ if ("".equals(locale.getLanguage()))
+ bundle.load();
+ family.addBundle(bundle);
+ }
+
+ private String getBaseName(String filename) {
+ if (!filename.endsWith(PROPERTIES_SUFFIX))
+ throw new IllegalArgumentException();
+ String name = filename.substring(0, filename.length() - 11);
+ int len = name.length();
+ if (len > 3 && name.charAt(len - 3) == '_') {
+ if (len > 6 && name.charAt(len - 6) == '_') {
+ return name.substring(0, len - 6);
+ } else {
+ return name.substring(0, len - 3);
+ }
+ }
+ return name;
+ }
+
+ private String getLanguage(String filename) {
+ if (!filename.endsWith(PROPERTIES_SUFFIX))
+ throw new IllegalArgumentException();
+ String name = filename.substring(0, filename.length() - 11);
+ int len = name.length();
+ if (len > 3 && name.charAt(len - 3) == '_') {
+ if (len > 6 && name.charAt(len - 6) == '_') {
+ return name.substring(len - 5, len - 3);
+ } else {
+ return name.substring(len - 2);
+ }
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ private String getCountry(String filename) {
+ if (!filename.endsWith(PROPERTIES_SUFFIX))
+ throw new IllegalArgumentException();
+ String name = filename.substring(0, filename.length() - 11);
+ int len = name.length();
+ if (len > 3 && name.charAt(len - 3) == '_') {
+ if (len > 6 && name.charAt(len - 6) == '_') {
+ return name.substring(len - 2);
+ } else {
+ return ""; //$NON-NLS-1$
+ }
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ private ResourceBundleFamily getOrCreateFamily(String projectName, String pluginId, String packageName,
+ String baseName) {
+
+ // Ignore project name
+ if (pluginId != null)
+ projectName = null;
+
+ for (ResourceBundleFamily family : bundleFamilies) {
+ if (areEqual(family.getProjectName(), projectName)
+ && areEqual(family.getPluginId(), pluginId)
+ && areEqual(family.getPackageName(), packageName)
+ && areEqual(family.getBaseName(), baseName)) {
+ return family;
+ }
+ }
+ ResourceBundleFamily family = new ResourceBundleFamily(
+ this,
+ projectName,
+ pluginId,
+ packageName,
+ baseName);
+ bundleFamilies.add(family);
+ return family;
+ }
+
+ private boolean isIgnoredFilename(String filename) {
+ return filename.equals("build.properties") || filename.equals("logging.properties"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private boolean areEqual(String str1, String str2) {
+ return str1 == null && str2 == null || str1 != null && str1.equals(str2);
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/CharArraySource.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/CharArraySource.java
new file mode 100644
index 0000000..e111b69
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/CharArraySource.java
@@ -0,0 +1,262 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * A scanner source that is backed by a character array.
+ */
+public class CharArraySource implements IScannerSource {
+
+ private char[] cbuf;
+
+ /** The end position of this source. */
+ private int end;
+
+ private int[] lineEnds = new int[2048];
+
+ /**
+ * The current position at which the next character will be read.
+ * The value <code>Integer.MAX_VALUE</code> indicates, that the end
+ * of the source has been reached (the EOF character has been returned).
+ */
+ int currentPosition = 0;
+
+ /** The number of the current line. (Line numbers are one-based.) */
+ int currentLineNumber = 1;
+
+ protected CharArraySource() {
+ }
+
+ /**
+ * Constructs a scanner source from a char array.
+ */
+ public CharArraySource(char[] cbuf) {
+ this.cbuf = cbuf;
+ this.end = cbuf.length;
+ }
+
+ /**
+ * Resets this source on the given array.
+ *
+ * @param cbuf the array to read from
+ * @param begin where to begin reading
+ * @param end where to end reading
+ */
+ protected void reset(char[] cbuf, int begin, int end) {
+ if (cbuf == null) {
+ this.cbuf = null;
+ this.end = -1;
+ currentPosition = -1;
+ currentLineNumber = -1;
+ lineEnds = null;
+ } else {
+ this.cbuf = cbuf;
+ this.end = end;
+ currentPosition = begin;
+ currentLineNumber = 1;
+ lineEnds = new int[2];
+ }
+ }
+
+ /*
+ * @see scanner.IScannerSource#charAt(int)
+ */
+ public int charAt(int index) {
+ if (index < end) {
+ return cbuf[index];
+ } else {
+ return -1;
+ }
+ }
+
+ /*
+ * @see scanner.IScannerSource#currentChar()
+ */
+ public int lookahead() {
+ if (currentPosition < end) {
+ return cbuf[currentPosition];
+ } else {
+ return -1;
+ }
+ }
+
+ /*
+ * @see scanner.IScannerSource#lookahead(int)
+ */
+ public int lookahead(int n) {
+ int pos = currentPosition + n - 1;
+ if (pos < end) {
+ return cbuf[pos];
+ } else {
+ return -1;
+ }
+ }
+
+ /*
+ * @see core.IScannerSource#readChar()
+ */
+ public int readChar() {
+ if (currentPosition < end) {
+ return cbuf[currentPosition++];
+ } else {
+ currentPosition++;
+ return -1;
+ }
+ }
+
+ /*
+ * @see core.IScannerSource#readChar(int)
+ */
+ public int readChar(int expected) {
+ int c = readChar();
+ if (c == expected) {
+ return c;
+ } else {
+ String message = "Expected char '"
+ + (char) expected
+ + "' (0x"
+ + hexDigit((expected >> 4) & 0xf)
+ + hexDigit(expected & 0xf)
+ + ") but got '" + (char) c + "' (0x" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ + hexDigit((c >> 4) & 0xf)
+ + hexDigit(c & 0xf)
+ + ")"; //$NON-NLS-1$
+ throw new LexicalErrorException(this, message);
+ }
+ }
+
+ /*
+ * @see scanner.IScannerSource#unreadChar()
+ */
+ public void unreadChar() {
+ currentPosition--;
+ }
+
+ /*
+ * @see core.IScannerSource#hasMoreChars()
+ */
+ public boolean hasMoreChars() {
+ return currentPosition < end;
+ }
+
+ /*
+ * @see scanner.IScannerSource#getPosition()
+ */
+ public int getPosition() {
+ if (currentPosition < end)
+ return currentPosition;
+ else
+ return end;
+ }
+
+ /*
+ * @see core.IScannerSource#isAtLineBegin()
+ */
+ public boolean isAtLineBegin() {
+ return currentPosition == lineEnds[currentLineNumber - 1];
+ }
+
+ /*
+ * @see scanner.IScannerSource#getCurrentLineNumber()
+ */
+ public int getCurrentLineNumber() {
+ return currentLineNumber;
+ }
+
+ /*
+ * @see scanner.IScannerSource#getCurrentColumnNumber()
+ */
+ public int getCurrentColumnNumber() {
+ return currentPosition - lineEnds[currentLineNumber - 1] + 1;
+ }
+
+ /*
+ * @see scanner.IScannerSource#getLineEnds()
+ */
+ public int[] getLineEnds() {
+ return lineEnds;
+ }
+ /*
+ * @see scanner.IScannerSource#pushLineSeparator()
+ */
+ public void pushLineSeparator() {
+ if (currentLineNumber >= lineEnds.length) {
+ int[] newLineEnds = new int[lineEnds.length * 2];
+ System.arraycopy(lineEnds, 0, newLineEnds, 0, lineEnds.length);
+ lineEnds = newLineEnds;
+ }
+ lineEnds[currentLineNumber++] = currentPosition;
+ }
+
+ /*
+ * @see scanner.IScannerSource#length()
+ */
+ public int length() {
+ return cbuf.length;
+ }
+
+ /**
+ * Returns a string that contains the characters of the source specified
+ * by the range <code>beginIndex</code> and the current position as the
+ * end index.
+ *
+ * @param beginIndex
+ * @return the String
+ */
+ public String toString(int beginIndex) {
+ return toString(beginIndex, currentPosition);
+ }
+
+ /*
+ * @see scanner.IScannerSource#toString(int, int)
+ */
+ public String toString(int beginIndex, int endIndex) {
+ return new String(cbuf, beginIndex, endIndex - beginIndex);
+ }
+
+ /**
+ * Returns the original character array that backs the scanner source.
+ * All subsequent changes to the returned array will affect the scanner
+ * source.
+ *
+ * @return the array
+ */
+ public char[] getArray() {
+ return cbuf;
+ }
+
+ /*
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "Line=" + getCurrentLineNumber() + ", Column=" + getCurrentColumnNumber(); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private static char hexDigit(int digit) {
+ return "0123456789abcdef".charAt(digit); //$NON-NLS-1$
+ }
+
+ public static CharArraySource createFrom(Reader reader) throws IOException {
+ StringBuffer buffer = new StringBuffer();
+ int BUF_SIZE = 4096;
+ char[] array = new char[BUF_SIZE];
+ for (int read = 0; (read = reader.read(array, 0, BUF_SIZE)) > 0;) {
+ buffer.append(array, 0, read);
+ }
+ char[] result = new char[buffer.length()];
+ buffer.getChars(0, buffer.length(), result, 0);
+ return new CharArraySource(result);
+ }
+
+}
\ No newline at end of file
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/IScannerSource.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/IScannerSource.java
new file mode 100644
index 0000000..6ae6dce
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/IScannerSource.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.parser;
+
+public interface IScannerSource {
+
+ /**
+ * Returns the character at the specified position.
+ *
+ * @param position the index of the character to be returned
+ * @return the character at the specified position (0-based)
+ */
+ public int charAt(int position);
+
+ /**
+ * Returns the character that will be returned by the next call
+ * to <code>readChar()</code>. Calling this method is equal to the
+ * following calls:
+ * <pre>
+ * lookahead(1)
+ * charAt(getPosition())
+ * </pre>
+ *
+ * @return the character at the current position
+ */
+ public int lookahead();
+
+ /**
+ * Returns the character at the given lookahead position. Calling this
+ * method is equal to the following call:
+ * <pre>
+ * charAt(currentPosition + n - 1)
+ * </pre>
+ *
+ * @param n the number of characters to look ahead
+ *
+ * @return the character
+ */
+ public int lookahead(int n);
+
+ /**
+ * Reads a single character.
+ *
+ * @return the character read, or -1 if the end of the source
+ * has been reached
+ */
+ public int readChar();
+
+ /**
+ * Reads a single character.
+ *
+ * @param expected the expected character; if the character read does not
+ * match this character, a <code>LexcialErrorException</code> will be thrown
+ * @return the character read, or -1 if the end of the source
+ * has been reached
+ */
+ public int readChar(int expected);
+
+ /**
+ * Unreads a single character. The current position will be decreased
+ * by 1. If -1 has been read multiple times, it will be unread multiple
+ * times.
+ */
+ public void unreadChar();
+
+ /**
+ * Retruns the current position of the source.
+ *
+ * @return the position (0-based)
+ */
+ public int getPosition();
+
+ /**
+ * Returns <code>true</code> if the current position is at the beginning of a line.
+ *
+ * @return <code>true</code> if the current position is at the beginning of a line
+ */
+ public boolean isAtLineBegin();
+
+ /**
+ * Returns the current line number.
+ *
+ * @return the current line number (1-based)
+ */
+ public int getCurrentLineNumber();
+
+ /**
+ * Returns the current column number.
+ *
+ * @return the current column number (1-based)
+ */
+ public int getCurrentColumnNumber();
+
+ /**
+ * Records the next line end position. This method has to be called
+ * just after the line separator has been read.
+ *
+ * @see IScannerSource#getLineEnds()
+ */
+ public void pushLineSeparator();
+
+ /**
+ * Returns an array of the line end positions recorded so far. Each value points
+ * to first character following the line end (and is thus an exclusive index
+ * to the line end). By definition the value <code>lineEnds[0]</code> is 0.
+ * <code>lineEnds[1]</code> contains the line end position of the first line.
+ *
+ * @return an array containing the line end positions
+ *
+ * @see IScannerSource#pushLineSeparator()
+ */
+ public int[] getLineEnds();
+
+ /**
+ * Returns <code>true</code> if more characters are available.
+ *
+ * @return <code>true</code> if more characters are available
+ */
+ public boolean hasMoreChars();
+
+ /**
+ * Returns a String that contains the characters in the specified range
+ * of the source.
+ *
+ * @param beginIndex the beginning index, inclusive
+ * @param endIndex the ending index, exclusive
+ * @return the newly created <code>String</code>
+ */
+ public String toString(int beginIndex, int endIndex);
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LexicalErrorException.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LexicalErrorException.java
new file mode 100644
index 0000000..4b5439a
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LexicalErrorException.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.parser;
+
+/**
+ * Exception thrown by a scanner when encountering lexical errors.
+ */
+public class LexicalErrorException extends RuntimeException {
+
+ private int lineNumber;
+ private int columnNumber;
+
+ /**
+ * Creates a <code>LexicalErrorException</code> without a detailed message.
+ *
+ * @param source the scanner source the error occured on
+ */
+ public LexicalErrorException(IScannerSource source) {
+ this(source.getCurrentLineNumber(), source.getCurrentColumnNumber(), null);
+ }
+
+ /**
+ * @param source the scanner source the error occured on
+ * @param message the error message
+ */
+ public LexicalErrorException(IScannerSource source, String message) {
+ this(source.getCurrentLineNumber(), source.getCurrentColumnNumber(), message);
+ }
+
+ /**
+ * @param line the number of the line where the error occured
+ * @param column the numer of the column where the error occured
+ * @param message the error message
+ */
+ public LexicalErrorException(int line, int column, String message) {
+ super("Lexical error (" + line + ", " + column + (message == null ? ")" : "): " + message)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ this.lineNumber = line;
+ this.columnNumber = column;
+ }
+
+ /**
+ * Returns the line number where the error occured.
+ *
+ * @return the line number where the error occured
+ */
+ public int getLineNumber() {
+ return lineNumber;
+ }
+
+ /**
+ * Returns the column number where the error occured.
+ *
+ * @return the column number where the error occured
+ */
+ public int getColumnNumber() {
+ return columnNumber;
+ }
+
+ public static LexicalErrorException unexpectedCharacter(IScannerSource source, int c) {
+ return new LexicalErrorException(source, "Unexpected character: '" //$NON-NLS-1$
+ + (char) c
+ + "' (0x" //$NON-NLS-1$
+ + hexDigit((c >> 4) & 0xf)
+ + hexDigit(c & 0xf)
+ + ")"); //$NON-NLS-1$
+ }
+
+ private static char hexDigit(int digit) {
+ return "0123456789abcdef".charAt(digit); //$NON-NLS-1$
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LocaleUtil.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LocaleUtil.java
new file mode 100644
index 0000000..d5ecba3
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/LocaleUtil.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.parser;
+
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+public class LocaleUtil {
+
+ private LocaleUtil() {
+ }
+
+ public static Locale parseLocale(String name) throws IllegalArgumentException {
+ String language = ""; //$NON-NLS-1$
+ String country = ""; //$NON-NLS-1$
+ String variant = ""; //$NON-NLS-1$
+
+ StringTokenizer tokenizer = new StringTokenizer(name, "_"); //$NON-NLS-1$
+ if (tokenizer.hasMoreTokens())
+ language = tokenizer.nextToken();
+ if (tokenizer.hasMoreTokens())
+ country = tokenizer.nextToken();
+ if (tokenizer.hasMoreTokens())
+ variant = tokenizer.nextToken();
+
+ if (!language.equals("") && language.length() != 2) //$NON-NLS-1$
+ throw new IllegalArgumentException();
+ if (!country.equals("") && country.length() != 2) //$NON-NLS-1$
+ throw new IllegalArgumentException();
+
+ if (!language.equals("")) { //$NON-NLS-1$
+ char l1 = language.charAt(0);
+ char l2 = language.charAt(1);
+ if (!('a' <= l1 && l1 <= 'z' && 'a' <= l2 && l2 <= 'z'))
+ throw new IllegalArgumentException();
+ }
+
+ if (!country.equals("")) { //$NON-NLS-1$
+ char c1 = country.charAt(0);
+ char c2 = country.charAt(1);
+ if (!('A' <= c1 && c1 <= 'Z' && 'A' <= c2 && c2 <= 'Z'))
+ throw new IllegalArgumentException();
+ }
+
+ return new Locale(language, country, variant);
+ }
+
+}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/RawBundle.java b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/RawBundle.java
new file mode 100644
index 0000000..7958772
--- /dev/null
+++ b/org.eclipse.babel.editor/src/org/eclipse/pde/nls/internal/ui/parser/RawBundle.java
@@ -0,0 +1,322 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.nls.internal.ui.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+
+/**
+ * A class used to manipulate resource bundle files.
+ */
+public class RawBundle {
+
+ public static abstract class RawLine {
+ String rawData;
+ public RawLine(String rawData) {
+ this.rawData = rawData;
+ }
+ public String getRawData() {
+ return rawData;
+ }
+ }
+ public static class CommentLine extends RawLine {
+ public CommentLine(String line) {
+ super(line);
+ }
+ }
+ public static class EmptyLine extends RawLine {
+ public EmptyLine(String line) {
+ super(line);
+ }
+ }
+ public static class EntryLine extends RawLine {
+ String key;
+ public EntryLine(String key, String lineData) {
+ super(lineData);
+ this.key = key;
+ }
+ }
+
+ /**
+ * The logical lines of the resource bundle.
+ */
+ private ArrayList<RawLine> lines = new ArrayList<RawLine>();
+
+ public RawBundle() {
+ }
+
+ public EntryLine getEntryLine(String key) {
+ for (RawLine line : lines) {
+ if (line instanceof EntryLine) {
+ EntryLine entryLine = (EntryLine) line;
+ if (entryLine.key.equals(key))
+ return entryLine;
+ }
+ }
+ return null;
+ }
+
+ public void put(String key, String value) {
+
+ // Find insertion position
+ int size = lines.size();
+ int pos = -1;
+ for (int i = 0; i < size; i++) {
+ RawLine line = lines.get(i);
+ if (line instanceof EntryLine) {
+ EntryLine entryLine = (EntryLine) line;
+ int compare = key.compareToIgnoreCase(entryLine.key);
+ if (compare < 0) {
+ if (pos == -1) {
+ pos = i; // possible insertion position
+ }
+ } else if (compare > 0) {
+ continue;
+ } else if (key.equals(entryLine.key)) {
+ entryLine.rawData = key + "=" + escape(value) + "\r\n";
+ return;
+ } else {
+ pos = i; // possible insertion position
+ }
+ }
+ }
+ if (pos == -1)
+ pos = lines.size();
+
+ // Append new entry
+ lines.add(pos, new EntryLine(key, key + "=" + escape(value) + "\r\n"));
+ }
+
+ private String escape(String str) {
+ StringBuilder builder = new StringBuilder();
+ int len = str.length();
+ for (int i = 0; i < len; i++) {
+ char c = str.charAt(i);
+ switch (c) {
+ case ' ' :
+ if (i == 0) {
+ builder.append("\\ ");
+ } else {
+ builder.append(c);
+ }
+ break;
+ case '\t' :
+ builder.append("\\t");
+ break;
+ case '=' :
+ case ':' :
+ case '#' :
+ case '!' :
+ case '\\' :
+ builder.append('\\').append(c);
+ break;
+ default :
+ if (31 <= c && c <= 255) {
+ builder.append(c);
+ } else {
+ builder.append("\\u");
+ builder.append(hexDigit((c >> 12) & 0x0f));
+ builder.append(hexDigit((c >> 8) & 0x0f));
+ builder.append(hexDigit((c >> 4) & 0x0f));
+ builder.append(hexDigit(c & 0x0f));
+ }
+ break;
+ }
+ }
+ return builder.toString();
+ }
+
+ private static char hexDigit(int digit) {
+ return "0123456789ABCDEF".charAt(digit); //$NON-NLS-1$
+ }
+
+ public void writeTo(OutputStream out) throws IOException {
+ OutputStreamWriter writer = new OutputStreamWriter(out, "ISO-8859-1");
+ writeTo(writer);
+ }
+
+ public void writeTo(Writer writer) throws IOException {
+ for (RawLine line : lines) {
+ writer.write(line.rawData);
+ }
+ }
+
+ public static RawBundle createFrom(InputStream in) throws IOException {
+ IScannerSource source = CharArraySource.createFrom(new InputStreamReader(in, "ISO-8859-1"));
+ return RawBundle.createFrom(source);
+ }
+
+ public static RawBundle createFrom(Reader reader) throws IOException {
+ IScannerSource source = CharArraySource.createFrom(reader);
+ return RawBundle.createFrom(source);
+ }
+
+ public static RawBundle createFrom(IScannerSource source) throws IOException {
+ RawBundle rawBundle = new RawBundle();
+ StringBuilder builder = new StringBuilder();
+
+ while (source.hasMoreChars()) {
+ int begin = source.getPosition();
+ skipAllOf(" \t\u000c", source);
+
+ // Comment line
+ if (source.lookahead() == '#' || source.lookahead() == '!') {
+ skipToOneOf("\r\n", false, source);
+ consumeLineSeparator(source);
+ int end = source.getPosition();
+ String line = source.toString(begin, end);
+ rawBundle.lines.add(new CommentLine(line));
+ continue;
+ }
+
+ // Empty line
+ if (isAtLineEnd(source)) {
+ consumeLineSeparator(source);
+ int end = source.getPosition();
+ String line = source.toString(begin, end);
+ rawBundle.lines.add(new EmptyLine(line));
+ continue;
+ }
+
+ // Entry line
+ {
+ // Key
+ builder.setLength(0);
+ loop : while (source.hasMoreChars()) {
+ char c = (char) source.readChar();
+ switch (c) {
+ case ' ' :
+ case '\t' :
+ case '\u000c' :
+ case '=' :
+ case '\r' :
+ case '\n' :
+ break loop;
+ case '\\' :
+ source.unreadChar();
+ builder.append(readEscapedChar(source));
+ break;
+ default :
+ builder.append(c);
+ break;
+ }
+ }
+ String key = builder.toString();
+
+ // Value
+ int end = 0;
+ loop : while (source.hasMoreChars()) {
+ char c = (char) source.readChar();
+ switch (c) {
+ case '\r' :
+ case '\n' :
+ consumeLineSeparator(source);
+ end = source.getPosition();
+ break loop;
+ case '\\' :
+ if (isAtLineEnd(source)) {
+ consumeLineSeparator(source);
+ } else {
+ source.unreadChar();
+ readEscapedChar(source);
+ }
+ break;
+ default :
+ break;
+ }
+ }
+ if (end == 0)
+ end = source.getPosition();
+
+ String lineData = source.toString(begin, end);
+ EntryLine entryLine = new EntryLine(key, lineData);
+ rawBundle.lines.add(entryLine);
+ }
+ }
+
+ return rawBundle;
+ }
+
+ private static char readEscapedChar(IScannerSource source) {
+ source.readChar('\\');
+ char c = (char) source.readChar();
+ switch (c) {
+ case ' ' :
+ case '=' :
+ case ':' :
+ case '#' :
+ case '!' :
+ case '\\' :
+ return c;
+ case 't' :
+ return '\t';
+ case 'n' :
+ return '\n';
+ case 'u' :
+ int d1 = Character.digit(source.readChar(), 16);
+ int d2 = Character.digit(source.readChar(), 16);
+ int d3 = Character.digit(source.readChar(), 16);
+ int d4 = Character.digit(source.readChar(), 16);
+ if (d1 == -1 || d2 == -1 || d3 == -1 || d4 == -1)
+ throw new LexicalErrorException(source, "Illegal escape sequence");
+ return (char) (d1 << 12 | d2 << 8 | d3 << 4 | d4);
+ default :
+ throw new LexicalErrorException(source, "Unknown escape sequence");
+ }
+ }
+
+ private static boolean isAtLineEnd(IScannerSource source) {
+ return source.lookahead() == '\r' || source.lookahead() == '\n';
+ }
+
+ private static void consumeLineSeparator(IScannerSource source) {
+ if (source.lookahead() == '\n') {
+ source.readChar();
+ source.pushLineSeparator();
+ } else if (source.lookahead() == '\r') {
+ source.readChar();
+ if (source.lookahead() == '\n') {
+ source.readChar();
+ }
+ source.pushLineSeparator();
+ }
+ }
+
+ private static void skipToOneOf(String delimiters, boolean readDelimiter, IScannerSource source) {
+ loop : while (source.hasMoreChars()) {
+ int c = source.readChar();
+ if (delimiters.indexOf(c) != -1) {
+ if (!readDelimiter) {
+ source.unreadChar();
+ }
+ break loop;
+ }
+ if (c == '\r') {
+ source.readChar('\n');
+ source.pushLineSeparator();
+ }
+ }
+ }
+
+ private static void skipAllOf(String string, IScannerSource source) {
+ while (source.hasMoreChars() && string.indexOf(source.lookahead()) != -1) {
+ source.readChar();
+ }
+ }
+
+
+}
diff --git a/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/PropertiesTest.java b/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/PropertiesTest.java
new file mode 100644
index 0000000..8e33143
--- /dev/null
+++ b/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/PropertiesTest.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.nls.ui.tests;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+public class PropertiesTest extends TestCase {
+
+ private Properties properties;
+
+ public void test() throws IOException {
+ String input = " # a comment line\r\n"
+ + "key1=value1\r\n"
+ + "key2=a value \\\r\n"
+ + " on two lines\r\n"
+ + "key3=a value \\\r\n"
+ + " "
+ + " with an empty line in between\r\n";
+ properties = readProperties(input);
+ assertValue("value1", "key1");
+ assertValue("a value on two lines", "key2");
+ assertValue("a value with an empty line in between", "key3");
+ }
+
+ public void testKeysWithWhitespace() throws IOException {
+ String input = ""
+ + "key1\t=key with tab\r\n"
+ + "key\\ 2 =key with escaped space\r\n"
+ + "key 3 =key with space\r\n";
+ properties = readProperties(input);
+ assertValue("key with tab", "key1");
+ assertValue("key with escaped space", "key 2");
+ assertValue("3 =key with space", "key");
+ }
+
+ public void testKeysWithMissingValue() throws IOException {
+ String input = "" + "keyWithoutValue\r\n" + "key=value\r\n";
+ properties = readProperties(input);
+ assertValue("", "keyWithoutValue");
+ assertValue("value", "key");
+ }
+
+ private void assertValue(String expected, String key) {
+ assertEquals(expected, properties.get(key));
+ }
+
+ private Properties readProperties(String input) throws IOException {
+ Properties properties = new Properties();
+ properties.load(new StringReader(input));
+ return properties;
+ }
+
+}
diff --git a/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/RawBundleTest.java b/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/RawBundleTest.java
new file mode 100644
index 0000000..579ee4d
--- /dev/null
+++ b/org.eclipse.babel.editor/tests/org/eclipse/nls/ui/tests/RawBundleTest.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Stefan Mücke and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Stefan Mücke - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.nls.ui.tests;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import junit.framework.TestCase;
+
+import org.eclipse.pde.nls.internal.ui.parser.RawBundle;
+import org.eclipse.pde.nls.internal.ui.parser.RawBundle.EntryLine;
+
+public class RawBundleTest extends TestCase {
+
+ private RawBundle rawBundle;
+
+ public void test() throws IOException {
+ String input = " # a comment line\r\n"
+ + "key1=value1\r\n"
+ + "key2=a value \\\r\n"
+ + " on two lines\r\n"
+ + "key3=a value \\\r\n"
+ + " "
+ + " with an empty line in between\r\n";
+ rawBundle = readRawBundle(input);
+ assertRawData("key1=value1\r\n", "key1");
+ assertRawData("key2=a value \\\r\n" + " on two lines\r\n", "key2");
+ assertRawData("key3=a value \\\r\n" + " " + " with an empty line in between\r\n", "key3");
+ }
+
+ public void testKeysWithWhitespace() throws IOException {
+ String input = ""
+ + "key1\t=key with tab\r\n"
+ + "key\\ 2 =key with escaped space\r\n"
+ + "key 3 =key with space\r\n";
+ rawBundle = readRawBundle(input);
+ assertRawData("key1\t=key with tab\r\n", "key1");
+ assertRawData("key\\ 2 =key with escaped space\r\n", "key 2");
+ assertRawData("key 3 =key with space\r\n", "key");
+ }
+
+ public void testKeysWithMissingValue() throws IOException {
+ String input = "keyWithoutValue\r\n" + "key=value\r\n";
+ rawBundle = readRawBundle(input);
+ assertRawData("keyWithoutValue\r\n", "keyWithoutValue");
+ assertRawData("key=value\r\n", "key");
+ }
+
+ public void testPut() throws IOException {
+ String input = "" + "key1=value1\r\n" + "key3=value3\r\n";
+ rawBundle = readRawBundle(input);
+ rawBundle.put("key2", "value2");
+ rawBundle.put("key4", "value4");
+ rawBundle.put("key0", "value0\\\t");
+ StringWriter stringWriter = new StringWriter();
+ rawBundle.writeTo(stringWriter);
+ assertEquals(""
+ + "key0=value0\\\\\\t\r\n"
+ + "key1=value1\r\n"
+ + "key2=value2\r\n"
+ + "key3=value3\r\n"
+ + "key4=value4\r\n", stringWriter.toString());
+
+ }
+
+ private void assertRawData(String expected, String key) {
+ EntryLine entryLine = rawBundle.getEntryLine(key);
+ assertEquals(expected, entryLine.getRawData());
+ }
+
+ private RawBundle readRawBundle(String input) throws IOException {
+ return RawBundle.createFrom(new StringReader(input));
+ }
+
+}