View Javadoc

1   /*
2    * Created on May 6, 2005. Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the "License"); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.editor.widgets;
13  import java.io.ByteArrayOutputStream;
14  import java.io.IOException;
15  import java.io.PrintStream;
16  import java.util.EnumSet;
17  import java.util.HashMap;
18  import java.util.HashSet;
19  import java.util.IdentityHashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import org.apache.commons.lang.StringUtils;
24  import org.eclipse.jface.dialogs.MessageDialog;
25  import org.eclipse.swt.SWT;
26  import org.eclipse.swt.events.DisposeEvent;
27  import org.eclipse.swt.events.DisposeListener;
28  import org.eclipse.swt.events.ModifyEvent;
29  import org.eclipse.swt.events.ModifyListener;
30  import org.eclipse.swt.events.SelectionAdapter;
31  import org.eclipse.swt.events.SelectionEvent;
32  import org.eclipse.swt.layout.GridData;
33  import org.eclipse.swt.layout.GridLayout;
34  import org.eclipse.swt.widgets.Button;
35  import org.eclipse.swt.widgets.Combo;
36  import org.eclipse.swt.widgets.Composite;
37  import org.eclipse.swt.widgets.Label;
38  import sk.baka.ikslibs.ResultEnum;
39  import sk.baka.xml.gene.CoordinatorInfo;
40  import sk.baka.xml.gene.ExportException;
41  import sk.baka.xml.gene.exportgraph.ExportGraph;
42  import sk.baka.xml.gene.exportgraph.ExportGraphBuilder;
43  import sk.baka.xml.gene.exportgraph.GraphNode;
44  import sk.baka.xml.gene.exportgraph.IGraphEdgeSelector;
45  import sk.baka.xml.gene.exportgraph.TransformGraph;
46  import sk.baka.xml.gene.exportgraph.TransformNode;
47  import sk.uniba.euromath.config.EuromathConfig;
48  import sk.uniba.euromath.config.bind.NamespaceType;
49  import sk.uniba.euromath.editor.lang.Messages;
50  /***
51   * <p>
52   * Allows user to select choices in an export graph. Able to convert list of
53   * graph nodes to a list of {@link TransformNode} instances.
54   * </p>
55   * @author Martin Vysny
56   */
57  public class GraphSelectorWidget extends AbstractUserInputWidget {
58  	/***
59  	 * Here all controls will be placed.
60  	 */
61  	protected final Composite composite;
62  	/***
63  	 * Constructor.
64  	 * @param parent the parent widget
65  	 * @param editableChoices all choice nodes. May be empty or
66  	 * <code>null</code>.
67  	 * @param graph offer this graph to user. Must not be empty.
68  	 * @throws IllegalArgumentException if graphList or editableChoices is
69  	 * empty, or editableChoices refers to non-choosable nodes.
70  	 */
71  	public GraphSelectorWidget(final Composite parent,
72  			final Map<GraphNode, Set<String>> editableChoices,
73  			final ExportGraph graph) {
74  		super();
75  		if ((graph == null) || graph.isEmpty())
76  			throw new IllegalArgumentException("Graph is empty or null"); //$NON-NLS-1$
77  		// create controls
78  		composite = new Composite(parent, SWT.NONE);
79  		final GridLayout l = new GridLayout(2, false);
80  		composite.setLayout(l);
81  		// create controls
82  		if ((editableChoices != null) && !editableChoices.isEmpty()) {
83  			createChoiceNodeControls(editableChoices);
84  		}
85  		createNamespaceOptionsControls(graph);
86  		this.graph = graph;
87  		// create button that displays the graph
88  		final Button showGraphButton = new Button(composite, SWT.PUSH);
89  		showGraphButton.setText(Messages.getString("SHOW_EXPORT_GRAPH_REP")); //$NON-NLS-1$
90  		showGraphButton.addSelectionListener(new SelectionAdapter() {
91  			/*
92  			 * (non-Javadoc)
93  			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
94  			 */
95  			@Override
96  			public void widgetSelected(SelectionEvent e) {
97  				// compute the string representation of graph
98  				final ByteArrayOutputStream bos = new ByteArrayOutputStream();
99  				final PrintStream out = new PrintStream(bos);
100 				ExportGraphBuilder.printGraph(GraphSelectorWidget.this.graph,
101 						out);
102 				out.close();
103 				final String graphRep = new String(bos.toByteArray());
104 				MessageDialog.openInformation(parent.getShell(),
105 						Messages.getString("EXPORT_GRAPH_REPRESENTATION"), graphRep); //$NON-NLS-1$
106 			}
107 		});
108 	}
109 	/***
110 	 * Creates controls allowing you to choose one of the choice nodes options.
111 	 * @param editableChoices all choice nodes. Must not be <code>null</code>.
112 	 */
113 	private void createChoiceNodeControls(
114 			final Map<GraphNode, Set<String>> editableChoices) {
115 		for (final GraphNode graph : editableChoices.keySet()) {
116 			for (final String target : editableChoices.get(graph)) {
117 				final List<GraphNode> children = graph.getTargets(target);
118 				if (children.size() < 2)
119 					throw new IllegalArgumentException(graph + ":" + target //$NON-NLS-1$
120 							+ " is not choice node"); //$NON-NLS-1$
121 				// create controls for each namespace that has multiple
122 				// exporters connected.
123 				new Label(composite, SWT.NONE).setText(Messages
124 						.getString("SOURCE_NAMESPACE")); //$NON-NLS-1$
125 				final Label ns = new Label(composite, SWT.NONE);
126 				ns.setText(StringUtils.defaultString(target));
127 				final NamespaceType nsType = EuromathConfig
128 						.getNamespace(target);
129 				if ((nsType != null) && (nsType.getDesc() != null)) {
130 					ns.setToolTipText(nsType.getDesc());
131 				}
132 				new Label(composite, SWT.NONE).setText(Messages
133 						.getString("EXPORTERS")); //$NON-NLS-1$
134 				final ComboAndValue combo = new ComboAndValue(children);
135 				// register the combobox
136 				Map<String, ComboAndValue> map = graphChoices.get(graph);
137 				if (map == null) {
138 					map = new HashMap<String, ComboAndValue>();
139 					graphChoices.put(graph, map);
140 				}
141 				map.put(StringUtils.defaultString(target), combo);
142 			}
143 		}
144 	}
145 	/***
146 	 * Creates controls for source document namespace processing tuning.
147 	 * @param graph offer this graph to user. Must not be empty.
148 	 */
149 	private void createNamespaceOptionsControls(final ExportGraph graph) {
150 		for (final GraphNode node : graph.getAllNodes()) {
151 			if (node.info != null)
152 				throw new IllegalArgumentException("Node " + node //$NON-NLS-1$
153 						+ " does not represent source document node"); //$NON-NLS-1$
154 			new Label(composite, SWT.NONE).setText(Messages
155 					.getString("DOCUMENT_NAMESPACE")); //$NON-NLS-1$
156 			final Label ns = new Label(composite, SWT.NONE);
157 			ns.setText(StringUtils.defaultString(node.targetNamespace));
158 			final NamespaceType nsType = EuromathConfig
159 					.getNamespace(node.targetNamespace);
160 			if ((nsType != null) && (nsType.getDesc() != null)) {
161 				ns.setToolTipText(nsType.getDesc());
162 			}
163 			final CheckboxAndValue cb = new CheckboxAndValue(composite, !graph
164 					.isRegular(node), node.targetNamespace);
165 			wildcardCheckboxes.put(StringUtils
166 					.defaultString(node.targetNamespace), cb);
167 		}
168 	}
169 	/***
170 	 * Computes and returns all choice nodes that the user can edit using this
171 	 * widget.
172 	 * @param graph the export graph. Should be able to transform whole
173 	 * document.
174 	 * @return never <code>null</code>. A map of choice nodes. If empty then
175 	 * there are no choice points.
176 	 */
177 	public static Map<GraphNode, Set<String>> getEditableChoices(
178 			final ExportGraph graph) {
179 		Map<GraphNode, Set<String>> result = new HashMap<GraphNode, Set<String>>();
180 		for (final GraphNode node : graph.getRegularNodes()) {
181 			scanGraph(node, result);
182 		}
183 		return result;
184 	}
185 	/***
186 	 * Scans the graph for a choice node. If such node is found then it is
187 	 * registered into the {@link #graphChoices} map and the scan continues.
188 	 * Descendants of the choice node are not searched for the choice nodes.
189 	 * @param graph graph to analyze.
190 	 * @param editableChoices put all choice nodes here.
191 	 */
192 	protected static void scanGraph(final GraphNode graph,
193 			final Map<GraphNode, Set<String>> editableChoices) {
194 		for (final String target : graph.getResultNamespaces()) {
195 			final List<GraphNode> children = graph.getTargets(target);
196 			if ((children == null) || (children.isEmpty())) {
197 				// this node emits this output to the controller. ignore.
198 			} else if (children.size() > 1) {
199 				// we have found a choice node. register it into editableChoices
200 				Set<String> targets = editableChoices.get(graph);
201 				if (targets == null) {
202 					targets = new HashSet<String>();
203 					editableChoices.put(graph, targets);
204 				}
205 				targets.add(StringUtils.defaultString(target));
206 				// do not walk through children
207 			} else if (children.size() == 1) {
208 				// not a choice node. try its children.
209 				final GraphNode child = children.get(0);
210 				scanGraph(child, editableChoices);
211 			}
212 		}
213 	}
214 	/***
215 	 * Graph provided to the constructor.
216 	 */
217 	protected final ExportGraph graph;
218 	/***
219 	 * Contains choices in graphs. Maps graph node instance and a namespace
220 	 * string to a combobox instance. Combo allows to select one of the graph
221 	 * node children.
222 	 */
223 	protected final IdentityHashMap<GraphNode, Map<String, ComboAndValue>> graphChoices = new IdentityHashMap<GraphNode, Map<String, ComboAndValue>>();
224 	/***
225 	 * Contains all wildcard checkboxes. If this checkbox is checked then the
226 	 * namespace is transmitted to receiver as a tree representation, it is not
227 	 * exported normally.
228 	 */
229 	protected final Map<String, CheckboxAndValue> wildcardCheckboxes = new HashMap<String, CheckboxAndValue>();
230 	/***
231 	 * Maintains combobox selection index even when combobox is disposed.
232 	 * @author Martin Vysny
233 	 */
234 	private final class ComboAndValue {
235 		/***
236 		 * Combobox instance.
237 		 */
238 		public final Combo combo;
239 		/***
240 		 * Selection.
241 		 */
242 		private int selection = 0;
243 		/***
244 		 * Constructor.
245 		 * @param children we may choose from these child nodes.
246 		 */
247 		public ComboAndValue(List<GraphNode> children) {
248 			super();
249 			combo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
250 			final GridData data = new GridData(SWT.CENTER, SWT.LEFT, false,
251 					true);
252 			combo.setLayoutData(data);
253 			// fill the combobox with all exporters
254 			final String[] comboItems = new String[children.size()];
255 			for (int i = 0; i < children.size(); i++) {
256 				comboItems[i] = children.get(i).info.displayableName;
257 			}
258 			combo.setItems(comboItems);
259 			combo.select(0);
260 			combo.addDisposeListener(new DisposeListener() {
261 				/*
262 				 * (non-Javadoc)
263 				 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
264 				 */
265 				public void widgetDisposed(DisposeEvent e) {
266 					selection = combo.getSelectionIndex();
267 				}
268 			});
269 			combo.addModifyListener(new ModifyListener() {
270 				/*
271 				 * (non-Javadoc)
272 				 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
273 				 */
274 				public void modifyText(ModifyEvent e) {
275 					fireDataModified();
276 				}
277 			});
278 		}
279 		/***
280 		 * Returns combobox selection index.
281 		 * @return the selection index.
282 		 */
283 		public int getSelectionIndex() {
284 			if (!combo.isDisposed())
285 				return combo.getSelectionIndex();
286 			return selection;
287 		}
288 	}
289 	/***
290 	 * Maintains wildcard selection checkbox and its value even when the
291 	 * checkbox is disposed.
292 	 * @author Martin Vysny
293 	 */
294 	private final class CheckboxAndValue {
295 		/***
296 		 * The checkbox.
297 		 */
298 		public final Button checkbox;
299 		/***
300 		 * Checkbox value.
301 		 */
302 		private boolean checked;
303 		/***
304 		 * Constructor.
305 		 * @param composite where the checkbox is placed.
306 		 * @param alwaysChecked if true then checkbox is disabled and always
307 		 * checked.
308 		 * @param namespace the namespace. Set as data property of the checkbox.
309 		 */
310 		public CheckboxAndValue(final Composite composite,
311 				final boolean alwaysChecked, final String namespace) {
312 			super();
313 			checkbox = new Button(composite, SWT.CHECK);
314 			checkbox.setText(Messages.getString("USE_TREE_REPRESENTATION")); //$NON-NLS-1$
315 			checkbox.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER,
316 					true, false, 2, 1));
317 			checkbox.setData(namespace);
318 			if (alwaysChecked) {
319 				checkbox.setSelection(true);
320 				checkbox.setEnabled(false);
321 			}
322 			checkbox.addSelectionListener(new SelectionAdapter() {
323 				/*
324 				 * (non-Javadoc)
325 				 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
326 				 */
327 				@Override
328 				public void widgetSelected(SelectionEvent e) {
329 					enableChoiceSelectors();
330 					fireDataModified();
331 				}
332 			});
333 			checkbox.addDisposeListener(new DisposeListener() {
334 				/*
335 				 * (non-Javadoc)
336 				 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
337 				 */
338 				public void widgetDisposed(DisposeEvent e) {
339 					checked = checkbox.getSelection();
340 				}
341 			});
342 		}
343 		/***
344 		 * Tests if checkbox is checked.
345 		 * @return <code>true</code> if checkbox is checked.
346 		 */
347 		public boolean isChecked() {
348 			if (!checkbox.isDisposed())
349 				return checkbox.getSelection();
350 			return checked;
351 		}
352 		/***
353 		 * Enables or disables comboboxes selecting choice nodes, that would
354 		 * actually get replaced by a wildcard exporter when this checkbox is
355 		 * checked.
356 		 */
357 		private void enableChoiceSelectors() {
358 			final String namespace = StringUtils
359 					.defaultString((String) checkbox.getData());
360 			final boolean checked = isChecked();
361 			// enable/disable all choice nodes that are descendants of node
362 			// reading from 'namespace'
363 			final GraphNode node = graph.getAllNodesAsMap().get(namespace);
364 			assert node != null;
365 			for (final GraphNode choiceNode : graphChoices.keySet()) {
366 				if (choiceNode.equals(node) || node.isAscendantOf(choiceNode)) {
367 					// enable/disable it.
368 					for (final ComboAndValue combo : graphChoices.get(
369 							choiceNode).values()) {
370 						combo.combo.setEnabled(!checked);
371 					}
372 				}
373 			}
374 		}
375 	}
376 	/***
377 	 * <p>
378 	 * Processes actual selection and returns exact transformation tree. Each
379 	 * transformation info is produced from appropriate graph node: i-th info is
380 	 * produced from i-th graphlist item.
381 	 * </p>
382 	 * <p>
383 	 * Not all choice nodes are made selectable; if a choice node is not
384 	 * selectable then simply the first exporter is selected.
385 	 * </p>
386 	 * @param info the coordinator info object.
387 	 * @return list of transformation infos.
388 	 * @throws ExportException if exporter fails to initialize.
389 	 * @throws IOException if i/o error occurs.
390 	 */
391 	public TransformGraph getTransformationInfo(final CoordinatorInfo info)
392 			throws ExportException, IOException {
393 		final IGraphEdgeSelector userSelector = new ComboGraphEdgeSelector();
394 		final ExportGraphBuilder builder = new ExportGraphBuilder();
395 		final List<GraphNode> allNodes = graph.getAllNodes();
396 		final TransformGraph result = new TransformGraph();
397 		// convert all graph nodes to transformation info instance.
398 		for (GraphNode node : allNodes) {
399 			// if user wishes to export the namespace using wildcard exporter,
400 			// then replace this node with a wildcard node. This is needed only
401 			// when this node is a regular node.
402 			final String ns = StringUtils.defaultString(node.targetNamespace);
403 			if (graph.isRegular(node) && wildcardCheckboxes.get(ns).isChecked()) {
404 				node = builder.newWildcardGraphNode(node.targetNamespace,
405 						EnumSet.of(ResultEnum.DOM), info);
406 			}
407 			final TransformNode ti = builder.toTransformationInfo(node, info,
408 					userSelector);
409 			if (ti == null)
410 				result.addDirectPipe(ns, true);
411 			else
412 				result.add(ti, true);
413 		}
414 		return result;
415 	}
416 	/***
417 	 * Selects the outgoing edge depending on the value of the combobox, if
418 	 * registered.
419 	 * @author Martin Vysny
420 	 */
421 	private final class ComboGraphEdgeSelector implements IGraphEdgeSelector {
422 		/*
423 		 * (non-Javadoc)
424 		 * @see sk.uniba.euromath.api.export.graph.IGraphEdgeSelector#getChildNode(sk.uniba.euromath.api.export.graph.GraphNode,
425 		 * java.lang.String)
426 		 */
427 		public int getChildNode(GraphNode node, String namespace) {
428 			final Map<String, ComboAndValue> map = graphChoices.get(node);
429 			if (map == null)
430 				return 0;
431 			final ComboAndValue combo = map.get(namespace);
432 			if (combo == null)
433 				return 0;
434 			return combo.getSelectionIndex();
435 		}
436 	}
437 	/*
438 	 * (non-Javadoc)
439 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getComposite()
440 	 */
441 	public Composite getComposite() {
442 		return composite;
443 	}
444 	/*
445 	 * (non-Javadoc)
446 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getLastError()
447 	 */
448 	public ValidityMessages getMessages() {
449 		// component is always valid
450 		return null;
451 	}
452 	/***
453 	 * The state of the widget.
454 	 * @author Martin Vysny
455 	 */
456 	public class State {
457 		/***
458 		 * Selects correct nodes when a choice of nodes is available.
459 		 */
460 		public final IGraphEdgeSelector nodeSelector;
461 		/***
462 		 * List of namespaces explicitly selected as wildcards.
463 		 */
464 		public final Set<String> wildcards = new HashSet<String>();
465 		private State() {
466 			super();
467 			nodeSelector = new ComboGraphEdgeSelector();
468 			final List<GraphNode> allNodes = graph.getAllNodes();
469 			for (final GraphNode node : allNodes) {
470 				final String ns = StringUtils
471 						.defaultString(node.targetNamespace);
472 				if (wildcardCheckboxes.get(ns).isChecked())
473 					wildcards.add(ns);
474 			}
475 		}
476 	}
477 	/*
478 	 * (non-Javadoc)
479 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getState()
480 	 */
481 	public Object getState() {
482 		return new State();
483 	}
484 	/*
485 	 * (non-Javadoc)
486 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getStateClass()
487 	 */
488 	public Class< ? > getStateClass() {
489 		return State.class;
490 	}
491 	/*
492 	 * (non-Javadoc)
493 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#setState(java.lang.Object)
494 	 */
495 	public void setState(Object state) {
496 		throw new UnsupportedOperationException();
497 	}
498 }