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