View Javadoc

1   /*
2    * Created on Mar 19, 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.wizards.document;
13  import java.util.ArrayList;
14  import java.util.List;
15  import java.util.NoSuchElementException;
16  import javax.xml.namespace.QName;
17  import org.w3c.dom.Element;
18  import org.w3c.dom.Text;
19  import sk.baka.ikslibs.DOMUtils;
20  import sk.baka.ikslibs.modify.DOMMutils;
21  import sk.baka.ikslibs.ptr.DOMPoint;
22  import sk.uniba.euromath.document.NamespaceManager;
23  import sk.uniba.euromath.document.XMLAccess;
24  import sk.uniba.euromath.document.schema.AttributeListRule;
25  import sk.uniba.euromath.document.schema.ElementSequenceRule;
26  import sk.uniba.euromath.document.schema.InsertList;
27  import sk.uniba.euromath.document.schema.NewElementRule;
28  import sk.uniba.euromath.document.schema.plug.IValueRule;
29  import sk.uniba.euromath.editor.lang.Messages;
30  import sk.uniba.euromath.editor.wizards.IMultiWizardProvider;
31  import sk.uniba.euromath.editor.wizards.IWizard;
32  import sk.uniba.euromath.editor.wizards.MultiWizard;
33  /***
34   * <p>
35   * Fills given element with new content. The element is expected to be freshly
36   * created and thus must not contain any children nodes nor attributes.
37   * </p>
38   * @author Martin Vysny
39   */
40  public class FillNewElementWizardProvider implements IMultiWizardProvider {
41  	/***
42  	 * Creates instance of the wizard.
43  	 * @param element the element to be filled. The element is expected to be
44  	 * freshly created and thus must not contain any children nodes nor
45  	 * attributes.
46  	 * @param rule the rule for contents of the element.
47  	 * @param xmlAccess the document instance.
48  	 * @param nsMan actual namespace manager. It won't get modified. If
49  	 * <code>null</code> then <code>xmlAccess</code> current namespace
50  	 * manager is taken.
51  	 * @param name the name of the activity provided by this provider. If
52  	 * <code>null</code> then default name is used.
53  	 */
54  	public FillNewElementWizardProvider(Element element, NewElementRule rule,
55  			XMLAccess xmlAccess, NamespaceManager nsMan, String name) {
56  		super();
57  		this.xmlAccess = xmlAccess;
58  		this.name = name == null ? Messages.getString("ENTER_ELEMENT_CONTENT") : name; //$NON-NLS-1$
59  		startupManager = nsMan == null ? xmlAccess.getNsManager() : nsMan;
60  		// create first factory.
61  		firstFactory = getNextStep(rule, element);
62  		if (firstFactory != null) {
63  			// register it.
64  			stepFactories.add(firstFactory);
65  			visitedSteps.add(firstFactory.newStep(startupManager));
66  		}
67  	}
68  	/***
69  	 * The provider name.
70  	 */
71  	protected final String name;
72  	/***
73  	 * The document instance.
74  	 */
75  	protected final XMLAccess xmlAccess;
76  	/***
77  	 * Represents one step in wizard.
78  	 * @author Martin Vysny
79  	 */
80  	protected interface WizardStep {
81  		/***
82  		 * Returns wizard that allows user to enter data required for this step
83  		 * to be finished succesfully.
84  		 * @return wizard instance. Each call to this method should return same
85  		 * instance of wizard.
86  		 */
87  		public IWizard getWizard();
88  		/***
89  		 * Returns element where this step creates contents.
90  		 * @return the element whose attributes or children are being created by
91  		 * this step.
92  		 */
93  		public Element getContextElement();
94  		/***
95  		 * Checks whether <code>moveToNextStep()</code> will return non-empty
96  		 * list. It does not finish this step. May be called multiple times.
97  		 * <code>getWizard().canFinish()</code> is granted to be
98  		 * <code>true</code>, and <code>getWizard()</code> has already been
99  		 * invoked.
100 		 * @return <code>true</code> if there are still more steps.
101 		 */
102 		public boolean hasNextSteps();
103 		/***
104 		 * Finishes this step and moves to the next step.
105 		 * <code>getWizard().canFinish()</code> is granted to be
106 		 * <code>true</code>, and <code>getWizard()</code> has already been
107 		 * invoked.
108 		 * @return next step, or empty array if no more steps are required.
109 		 * Never <code>null</code>.
110 		 */
111 		public List<AbstractStepFactory> moveToNextStep();
112 		/***
113 		 * May be called after call to <code>moveToNextStep()</code> if user
114 		 * wishes to rollback next step and return to this one.
115 		 */
116 		public void rollbackFromNextStep();
117 	}
118 	/***
119 	 * Produces new steps. Allows wizards to be created lazily.
120 	 * @author Martin Vysny
121 	 */
122 	protected abstract class AbstractStepFactory {
123 		/***
124 		 * Rule to use for produced step.
125 		 */
126 		public final NewElementRule rule;
127 		/***
128 		 * Step shall fill contents of this element.
129 		 */
130 		public final Element e;
131 		/***
132 		 * Constructor.
133 		 * @param rule Rule to use for produced step.
134 		 * @param e Step shall fill contents of this element.
135 		 */
136 		protected AbstractStepFactory(NewElementRule rule, Element e) {
137 			super();
138 			this.rule = rule;
139 			this.e = e;
140 		}
141 		/***
142 		 * Produces instance of the step.
143 		 * @param manager the namespace manager to use. It must not be modified
144 		 * by this step, nor by the following steps.
145 		 * @return instance of the step. Must not return <code>null</code>.
146 		 */
147 		protected abstract WizardStep newStep(NamespaceManager manager);
148 	}
149 	/***
150 	 * Step that allows user to create attributes in newly created element.
151 	 * @author Martin Vysny
152 	 */
153 	protected class CreateAttributesWS implements WizardStep {
154 		/***
155 		 * rule for this new element.
156 		 */
157 		protected final NewElementRule rule;
158 		/***
159 		 * Rules for possible new attributes, as required by the schema.
160 		 */
161 		protected final List<AttributeListRule> attrRules;
162 		/***
163 		 * the new element. Must not contain any elements nor attributes.
164 		 */
165 		protected final Element e;
166 		/***
167 		 * current namespace manager. won't get modified.
168 		 */
169 		protected final NamespaceManager nsManager;
170 		/***
171 		 * Constructor.
172 		 * @param rule rule for this new element.
173 		 * @param e the new element. Must not contain any elements nor
174 		 * attributes.
175 		 * @param nsManager current namespace manager. won't get modified.
176 		 */
177 		protected CreateAttributesWS(NewElementRule rule, Element e,
178 				NamespaceManager nsManager) {
179 			super();
180 			attrRules = rule.getLists();
181 			this.e = e;
182 			this.nsManager = nsManager;
183 			this.rule = rule;
184 			if (attrRules.isEmpty())
185 				throw new IllegalArgumentException();
186 		}
187 		/***
188 		 * The wizard instance.
189 		 */
190 		protected CreateAttributeListWizard wizard = null;
191 		/*
192 		 * (non-Javadoc)
193 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#getWizard()
194 		 */
195 		public CreateAttributeListWizard getWizard() {
196 			if (wizard != null)
197 				return wizard;
198 			wizard = new CreateAttributeListWizard(attrRules, DOMUtils
199 					.printQName(e),
200 					FillNewElementWizardProvider.this.xmlAccess, nsManager);
201 			return wizard;
202 		}
203 		/*
204 		 * (non-Javadoc)
205 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#getContextElement()
206 		 */
207 		public Element getContextElement() {
208 			return e;
209 		}
210 		/*
211 		 * (non-Javadoc)
212 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#hasNextSteps()
213 		 */
214 		public boolean hasNextSteps() {
215 			List< ? > result = moveToNextStep();
216 			rollbackFromNextStep();
217 			return result.size() != 0;
218 		}
219 		/*
220 		 * (non-Javadoc)
221 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#moveToNextStep()
222 		 */
223 		public List<AbstractStepFactory> moveToNextStep() {
224 			// get all attributes names and values
225 			if (wizard == null)
226 				throw new IllegalStateException("Page has not yet been created"); //$NON-NLS-1$
227 			// create the attributes
228 			wizard.createAttributes(e, null);
229 			// create and return next wizard step
230 			List<AbstractStepFactory> nextStep = new ArrayList<AbstractStepFactory>(
231 					1);
232 			AbstractStepFactory nextFactory = getNextStep(rule, e);
233 			if (nextFactory != null)
234 				nextStep.add(nextFactory);
235 			return nextStep;
236 		}
237 		/*
238 		 * (non-Javadoc)
239 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#rollbackFromNextStep()
240 		 */
241 		public void rollbackFromNextStep() {
242 			DOMMutils.clearAttributes(e);
243 		}
244 	}
245 	/***
246 	 * Creates new elements or text nodes in new element. This element should
247 	 * already contain attributes, to select correct rule amongst similar rules.
248 	 * @author Martin Vysny
249 	 */
250 	protected class CreateElementContentsWS implements WizardStep {
251 		/***
252 		 * rule for this new element.
253 		 */
254 		protected final NewElementRule rule;
255 		/***
256 		 * the new element. Must not contain any elements nor attributes.
257 		 */
258 		protected final Element e;
259 		/***
260 		 * current namespace manager. won't get modified.
261 		 */
262 		protected final NamespaceManager nsManager;
263 		/***
264 		 * Constructor.
265 		 * @param rule rule for this new element.
266 		 * @param e the new element. Must not contain any elements nor
267 		 * attributes.
268 		 * @param nsManager current namespace manager. won't get modified.
269 		 */
270 		protected CreateElementContentsWS(NewElementRule rule, Element e,
271 				NamespaceManager nsManager) {
272 			super();
273 			this.e = e;
274 			this.nsManager = nsManager;
275 			this.rule = rule;
276 		}
277 		/***
278 		 * The wizard instance.
279 		 */
280 		protected InsertListChooserWizard wizard = null;
281 		/***
282 		 * User may select between these insertlists.
283 		 */
284 		protected List<InsertList> insertLists = null;
285 		/*
286 		 * (non-Javadoc)
287 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#getWizard()
288 		 */
289 		public IWizard getWizard() {
290 			if (wizard != null)
291 				return wizard;
292 			// get rules for new content.
293 			ElementSequenceRule seqRule = rule.getNewContent(e);
294 			insertLists = (seqRule == null) ? new ArrayList<InsertList>()
295 					: seqRule.getSequences(DOMPoint.FIRST);
296 			IValueRule textRule = rule.getNewTextRule(e);
297 			// if there is no required content then just return
298 			assert (insertLists.size() != 0) || (textRule != null);
299 			wizard = new InsertListChooserWizard(
300 					FillNewElementWizardProvider.this.xmlAccess, insertLists,
301 					nsManager, DOMUtils.printQName(e), textRule, Messages
302 							.getString("NEW_ELEMENT_CONTENT")); //$NON-NLS-1$
303 			return wizard;
304 		}
305 		/*
306 		 * (non-Javadoc)
307 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#getContextElement()
308 		 */
309 		public Element getContextElement() {
310 			return e;
311 		}
312 		/*
313 		 * (non-Javadoc)
314 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#hasNextSteps()
315 		 */
316 		public boolean hasNextSteps() {
317 			List< ? > result = moveToNextStep();
318 			rollbackFromNextStep();
319 			return result.size() != 0;
320 		}
321 		/*
322 		 * (non-Javadoc)
323 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#moveToNextStep()
324 		 */
325 		public List<AbstractStepFactory> moveToNextStep() {
326 			int sel = wizard.ilcPage.getWidget().getSelected();
327 			if (sel == -2) {
328 				// create a text, no elements actually needs to be created.
329 				Text text = xmlAccess.getDocument().createTextNode(
330 						wizard.ilcPage.getWidget().getText());
331 				e.appendChild(text);
332 				return new ArrayList<AbstractStepFactory>();
333 			}
334 			// some insertlist must be selected.
335 			assert sel >= 0;
336 			List<QName> elementNames = wizard.getNames();
337 			InsertList insertList = insertLists.get(sel);
338 			// create new elements and for each element compute new correct
339 			// 'step' object.
340 			List<AbstractStepFactory> result = new ArrayList<AbstractStepFactory>(
341 					insertList.size());
342 			for (int i = 0; i < insertList.size(); i++) {
343 				// create new element and get a rule for it.
344 				QName qname = elementNames.get(i);
345 				Element newElement = xmlAccess.getDocument().createElementNS(
346 						qname.getNamespaceURI(), DOMUtils.printQName(qname));
347 				e.appendChild(newElement);
348 				NewElementRule newRule = insertList.get(i).nameList.getRule(
349 						qname, true);
350 				// generate factory for next step.
351 				final AbstractStepFactory factory = getNextStep(newRule,
352 						newElement);
353 				if (factory != null)
354 					result.add(factory);
355 			}
356 			return result;
357 		}
358 		/*
359 		 * (non-Javadoc)
360 		 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.WizardStep#rollbackFromNextStep()
361 		 */
362 		public void rollbackFromNextStep() {
363 			// delete all created child nodes.
364 			DOMMutils.clearChildren(e, true);
365 		}
366 	}
367 	/***
368 	 * Determines which step is to be taken next.
369 	 * @param rule rule for element e.
370 	 * @param e the element. It may already have attributes created.
371 	 * @return new wizard step factory, or <code>null</code> if there are no
372 	 * required elements and/or attributes in given rule.
373 	 */
374 	protected AbstractStepFactory getNextStep(NewElementRule rule, Element e) {
375 		// check if attributes are needed to be created
376 		if (!e.hasAttributes()) {
377 			List<AttributeListRule> attrRules = rule.getLists();
378 			if ((attrRules != null) && (attrRules.size() > 0)) {
379 				// we need to create some attributes.
380 				return new AbstractStepFactory(rule, e) {
381 					/*
382 					 * (non-Javadoc)
383 					 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.AbstractStepFactory#newStep(sk.uniba.euromath.document.NamespaceManager)
384 					 */
385 					@Override
386 					protected WizardStep newStep(NamespaceManager manager) {
387 						return new CreateAttributesWS(rule, e, manager);
388 					}
389 				};
390 			}
391 		}
392 		// check if we need to create some element content.
393 		ElementSequenceRule seqRule = rule.getNewContent(e);
394 		List<InsertList> insertLists = (seqRule == null) ? new ArrayList<InsertList>()
395 				: seqRule.getSequences(DOMPoint.FIRST);
396 		IValueRule textRule = rule.getNewTextRule(e);
397 		// if there is no required content then just return
398 		if ((insertLists.size() == 0) && (textRule == null))
399 			return null;
400 		return new AbstractStepFactory(rule, e) {
401 			/*
402 			 * (non-Javadoc)
403 			 * @see sk.uniba.euromath.editor.wizards.document.FillNewElementWizardProvider.AbstractStepFactory#newStep(sk.uniba.euromath.document.NamespaceManager)
404 			 */
405 			@Override
406 			protected WizardStep newStep(NamespaceManager manager) {
407 				return new CreateElementContentsWS(rule, e, manager);
408 			}
409 		};
410 	}
411 	/***
412 	 * All wizard step factories. This list may contain more items than already
413 	 * returned wizards - predictions, which wizard will follow are stored here
414 	 * aswell.
415 	 */
416 	protected final List<AbstractStepFactory> stepFactories = new ArrayList<AbstractStepFactory>();
417 	/***
418 	 * List of steps that have been already visited. Last item of this array is
419 	 * a step that was returned by recent call to <code>next()</code> and is
420 	 * currently interacting with user. This list is consistent with the
421 	 * <code>stepFactories</code>: i-th factory has produced i-th step.
422 	 */
423 	protected final List<WizardStep> visitedSteps = new ArrayList<WizardStep>();
424 	/***
425 	 * Index of item which was returned by last <code>next()</code> call.
426 	 */
427 	private int activeItem = 0;
428 	/***
429 	 * The first factory. May be <code>null</code> - in this case no element
430 	 * content needs to be created and this provider provides no wizards. If not
431 	 * <code>null</code> and <code>stepFactories</code> has at least one
432 	 * item, then <code>stepFactories.get(0)==firstFactory</code>.
433 	 */
434 	protected final AbstractStepFactory firstFactory;
435 	/*
436 	 * (non-Javadoc)
437 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#performCancel()
438 	 */
439 	public void performCancel() {
440 		// no special processing needed
441 	}
442 	/*
443 	 * (non-Javadoc)
444 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#performFinish()
445 	 */
446 	public void performFinish() {
447 		// finalize last step
448 		List< ? > result = visitedSteps.get(visitedSteps.size() - 1)
449 				.moveToNextStep();
450 		assert result.size() == 0;
451 	}
452 	/*
453 	 * (non-Javadoc)
454 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#current()
455 	 */
456 	public IWizard current() {
457 		if (firstFactory == null)
458 			return null;
459 		return visitedSteps.get(activeItem).getWizard();
460 	}
461 	/*
462 	 * (non-Javadoc)
463 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#hasNext()
464 	 */
465 	public boolean hasNext() {
466 		// check if we are able to produce wizard in next step.
467 		if (activeItem < stepFactories.size() - 1)
468 			return true;
469 		// check if last wizard step is able to produce more steps
470 		WizardStep last = visitedSteps.get(activeItem);
471 		return last.hasNextSteps();
472 	}
473 	/*
474 	 * (non-Javadoc)
475 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#hasPrevious()
476 	 */
477 	public boolean hasPrevious() {
478 		return activeItem > 0;
479 	}
480 	/***
481 	 * Namespace manager provided at construction time.
482 	 */
483 	protected final NamespaceManager startupManager;
484 	/*
485 	 * (non-Javadoc)
486 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#next()
487 	 */
488 	public IWizard next() {
489 		assert activeItem == visitedSteps.size() - 1;
490 		assert visitedSteps.size() <= stepFactories.size();
491 		// get factory that'll produce wizard step able to produce active wizard
492 		AbstractStepFactory factory;
493 		WizardStep activeStep = visitedSteps.get(activeItem);
494 		// register factories spawned from currently active wizard, if
495 		// needed.
496 		List<AbstractStepFactory> factories = activeStep.moveToNextStep();
497 		int i = activeItem + 1;
498 		for (AbstractStepFactory newFactory : factories) {
499 			stepFactories.add(i, newFactory);
500 			i++;
501 		}
502 		// get new active factory.
503 		if (activeItem + 1 >= stepFactories.size())
504 			throw new NoSuchElementException();
505 		factory = stepFactories.get(activeItem + 1);
506 		// create the step and register it
507 		WizardStep step = factory.newStep(newNsManager());
508 		visitedSteps.add(step);
509 		// check if there is some required content. If yes then create and
510 		// register appropriate factories.
511 		activeItem++;
512 		return step.getWizard();
513 	}
514 	/***
515 	 * Determines and returns active namespace manager.
516 	 * @return namespace manager of current step's wizard.
517 	 */
518 	public NamespaceManager newNsManager() {
519 		// if (activeItem == 0)
520 		// return startupManager;
521 		// retrieve namespace manager from previous step.
522 		final WizardStep activeStep = visitedSteps.get(activeItem);
523 		final IWizard activeWizard = activeStep.getWizard();
524 		final NamespaceManager result;
525 		if (activeWizard instanceof CreateAttributeListWizard) {
526 			result = ((CreateAttributeListWizard) activeWizard).newNsManager();
527 		} else {
528 			result = ((InsertListChooserWizard) activeWizard).newNsManager();
529 		}
530 		return result;
531 	}
532 	/*
533 	 * (non-Javadoc)
534 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#previous()
535 	 */
536 	public IWizard previous() {
537 		if (!hasPrevious())
538 			throw new NoSuchElementException();
539 		AbstractStepFactory active = stepFactories.get(activeItem);
540 		// check if previous factory has created contents of parent element. If
541 		// yes then we shall delete all consecutive factories generating
542 		// contents of same element.
543 		AbstractStepFactory previous = stepFactories.get(activeItem - 1);
544 		// test if we have to delete factories producing contents of same
545 		// element. We must delete all factories that may become invalid when
546 		// 'previous' factory becomes active. This may happen when:
547 		// a) previous factory generates attributes for e. depending on these
548 		// attributes, different rules for contents of e may be selected. We
549 		// must delete all factories for e.
550 		WizardStep prevStep = visitedSteps.get(activeItem - 1);
551 		boolean delete = (prevStep.getWizard() instanceof CreateAttributeListWizard);
552 		// b) previous factory generates content for parent of e. We must delete
553 		// all factories for e.
554 		if (!delete) {
555 			delete = (active.e.getParentNode() != null)
556 					&& active.e.getParentNode().isSameNode(previous.e);
557 		}
558 		if (delete) {
559 			// remove 'active' factory and all that are creating contents of
560 			// same element.
561 			while (activeItem < stepFactories.size()) {
562 				AbstractStepFactory fac = stepFactories.get(activeItem);
563 				if (active.e.isSameNode(fac.e))
564 					stepFactories.remove(activeItem);
565 				else
566 					break;
567 			}
568 		}
569 		// go back.
570 		IWizard result = visitedSteps.get(activeItem).getWizard();
571 		visitedSteps.remove(activeItem);
572 		assert visitedSteps.size() == activeItem;
573 		activeItem--;
574 		result = visitedSteps.get(activeItem).getWizard();
575 		return result;
576 	}
577 	/*
578 	 * (non-Javadoc)
579 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#dispose()
580 	 */
581 	public void dispose() {
582 		visitedSteps.clear();
583 		stepFactories.clear();
584 	}
585 	/*
586 	 * (non-Javadoc)
587 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#setWizard(sk.uniba.euromath.editor.wizards.MultiWizard)
588 	 */
589 	public void setWizard(MultiWizard wizard) {
590 		// do nothing, we don't use the wizard instance.
591 	}
592 	/*
593 	 * (non-Javadoc)
594 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#getName()
595 	 */
596 	public String getName() {
597 		return name;
598 	}
599 }