View Javadoc

1   /*
2    * Created on Apr 8, 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.Collections;
15  import java.util.Iterator;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.NoSuchElementException;
19  
20  import javax.xml.namespace.QName;
21  
22  import org.eclipse.jface.dialogs.MessageDialog;
23  import org.eclipse.jface.window.Window;
24  import org.eclipse.swt.widgets.Shell;
25  import org.w3c.dom.Element;
26  
27  import sk.baka.ikslibs.DOMUtils;
28  import sk.baka.ikslibs.ptr.DOMPoint;
29  import sk.baka.ikslibs.ptr.DomPointer;
30  import sk.uniba.euromath.document.NamespaceManager;
31  import sk.uniba.euromath.document.XMLAccess;
32  import sk.uniba.euromath.document.schema.ElementLoc;
33  import sk.uniba.euromath.document.schema.INameList;
34  import sk.uniba.euromath.document.schema.InsertList;
35  import sk.uniba.euromath.document.schema.NameListCustom;
36  import sk.uniba.euromath.document.schema.NewElementRule;
37  import sk.uniba.euromath.document.schema.plug.INewElementRuleP;
38  import sk.uniba.euromath.editor.lang.Messages;
39  import sk.uniba.euromath.editor.wizards.IMultiWizardProvider;
40  import sk.uniba.euromath.editor.wizards.IWizard;
41  import sk.uniba.euromath.editor.wizards.MultiWizard;
42  import sk.uniba.euromath.editor.wizards.WizardDialog;
43  /***
44   * A wizard that queries contents of new element(s) that should be inserted at
45   * given location. When wizard finishes, these elements are returned as an array
46   * of insertpoints and elements. Wizard does not insert those elements itself,
47   * you may use
48   * {@link sk.uniba.euromath.editor.wizards.document.ElementLoc#insert()} method
49   * to do that.
50   * @author Martin Vysny
51   */
52  public class InsertElementWizardProvider implements IMultiWizardProvider {
53  	/***
54  	 * Helper method that executes the insert element wizard.
55  	 * @param parent parent of the wizard dialog. Can be <code>null</code> but
56  	 * this is not recommended.
57  	 * @param place where to insert new elements.
58  	 * @param xmlAccess the document instance.
59  	 * @param nsManager the namespace manager instance.
60  	 * @param preselected this element was requested by user to appear as first
61  	 * element. May be <code>null</code>.
62  	 * @return new elements and their locations, or <code>null</code> if user
63  	 * cancelled the wizard or no elements are insertable.
64  	 */
65  	public static sk.uniba.euromath.editor.wizards.document.ElementLoc execute(
66  			Shell parent, XMLAccess xmlAccess, DomPointer place,
67  			NamespaceManager nsManager, QName preselected) {
68  		if (!place.parent.isSameNode(place.parentElement)) {
69  			throw new IllegalArgumentException(
70  					"Cannot insert elements into an entity.");//$NON-NLS-1$
71  		}
72  		// compute base insertlist
73  		final List<InsertList> insertLists = xmlAccess.getSchema()
74  				.getElementRule(place.parentElement).getInsertableElements(
75  						place.ip);
76  		if (insertLists.size() == 0) {
77  			MessageDialog.openInformation(parent, Messages
78  					.getString("INFORMATION"), //$NON-NLS-1$
79  					Messages.getString("NO_INSERTABLE_ELEMENTS")); //$NON-NLS-1$
80  			return null;
81  		}
82  		// search for an insertlist that may be preselected
83  		filterInsertLists(preselected, insertLists, place.ip);
84  		// create the provider instance and open the wizard
85  		final InsertElementWizardProvider provider = new InsertElementWizardProvider(
86  				xmlAccess, insertLists, place.parentElement, nsManager);
87  		final MultiWizard w = new MultiWizard(provider);
88  		final WizardDialog dlg = new WizardDialog(parent, w, Messages
89  				.getString("INSERT_NEW_ELEMENTS"));//$NON-NLS-1$
90  		if (dlg.open() != Window.OK)
91  			return null;
92  		// build result object
93  		final List<Element> roots = provider.getElements();
94  		final List<DOMPoint> ips = new ArrayList<DOMPoint>(roots.size());
95  		final InsertList selected = provider.getInsertList();
96  		for (ElementLoc loc : selected) {
97  			ips.add(loc.ip);
98  		}
99  		final sk.uniba.euromath.editor.wizards.document.ElementLoc result = new sk.uniba.euromath.editor.wizards.document.ElementLoc(
100 				xmlAccess, place.parentElement, roots, ips);
101 		return result;
102 	}
103 	/***
104 	 * Filters out all insertlists whose elementloc does not accept the
105 	 * 'preselected' qname. If no such insertlist exists then the list is kept
106 	 * intact.
107 	 * @param preselected the preselected qname.
108 	 * @param insertLists list of insertlists to filter.
109 	 * @param ip insertpoint where the element will be inserted.
110 	 * @return <code>true</code> if some insertlists were filtered out,
111 	 * <code>false</code> otherwise.
112 	 */
113 	private static boolean filterInsertLists(final QName preselected,
114 			final List<InsertList> insertLists, final DOMPoint ip) {
115 		if (preselected != null) {
116 			// try to find an insertlist containing the 'preselected' rule.
117 			boolean isPreselected = false;
118 			for (final InsertList list : insertLists) {
119 				if (list.canCreate(preselected, ip)) {
120 					isPreselected = true;
121 					break;
122 				}
123 			}
124 			if (!isPreselected) {
125 				// no insertlist can be preselected. leave the list intact.
126 				return false;
127 			}
128 			// filter out the preselected insertlists.
129 			for (final Iterator<InsertList> i = insertLists.iterator(); i
130 					.hasNext();) {
131 				final InsertList il = i.next();
132 				final ElementLoc loc = il.getFirstAt(ip, preselected);
133 				if (loc == null) {
134 					i.remove();
135 					continue;
136 				}
137 				// create namelist with only one rule.
138 				final NewElementRule rule = loc.nameList.getRule(preselected,
139 						true);
140 				assert rule != null;
141 				final Map<QName, NewElementRule> rules = Collections
142 						.singletonMap(preselected, rule);
143 				loc.nameList = new NameListCustom<NewElementRule, INewElementRuleP>(
144 						loc.nameList.getNamespaceUri(), rules);
145 			}
146 			return true;
147 		}
148 		return false;
149 	}
150 	/***
151 	 * Constructor.
152 	 * @param xmlAccess instance of the document.
153 	 * @param insertLists list of possible insertlists.
154 	 * @param parent the parent element - here all elements will be created.
155 	 * @param nsManager the namespace manager instance.
156 	 */
157 	public InsertElementWizardProvider(XMLAccess xmlAccess,
158 			List<InsertList> insertLists, Element parent,
159 			NamespaceManager nsManager) {
160 		super();
161 		if (insertLists.size() == 0)
162 			throw new IllegalArgumentException("No insertlists"); //$NON-NLS-1$
163 		this.xmlAccess = xmlAccess;
164 		this.startNsManager = nsManager;
165 		// create first wizard - the insert list chooser.
166 		InsertListChooserWizard first = new InsertListChooserWizard(xmlAccess,
167 				insertLists, xmlAccess.getNsManager(), DOMUtils
168 						.printQName(parent), null, Messages
169 						.getString("INSERT_NEW_ELEMENTS")); //$NON-NLS-1$
170 		wizards.add(first);
171 	}
172 	/***
173 	 * The document instance.
174 	 */
175 	protected final XMLAccess xmlAccess;
176 	/***
177 	 * Wizards that has been activated. Warning: may contain <code>null</code>s
178 	 * in order to be consistent with <code>rules</code> and
179 	 * <code>roots</code> lists.
180 	 */
181 	protected final List<IWizard> wizards = new ArrayList<IWizard>();
182 	/***
183 	 * This manager was given to the constructor.
184 	 */
185 	protected final NamespaceManager startNsManager;
186 	/***
187 	 * We are being executed in this multiwizard.
188 	 */
189 	protected MultiWizard parent;
190 	/*
191 	 * (non-Javadoc)
192 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#performFinish()
193 	 */
194 	public void performFinish() {
195 		// all data is already available, nothing needs to be done
196 	}
197 	/*
198 	 * (non-Javadoc)
199 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#performCancel()
200 	 */
201 	public void performCancel() {
202 		// nothing needs to be done.
203 	}
204 	/*
205 	 * (non-Javadoc)
206 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#dispose()
207 	 */
208 	public void dispose() {
209 		wizards.clear();
210 	}
211 	/*
212 	 * (non-Javadoc)
213 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#setWizard(sk.uniba.euromath.editor.wizards.MultiWizard)
214 	 */
215 	public void setWizard(MultiWizard wizard) {
216 		parent = wizard;
217 	}
218 	/*
219 	 * (non-Javadoc)
220 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#current()
221 	 */
222 	public IWizard current() {
223 		IWizard result = wizards.get(wizards.size() - 1);
224 		assert result != null;
225 		return result;
226 	}
227 	/***
228 	 * Contains rules for elements. Last rule is the rule being currently filled
229 	 * in. i-th rule fills contents of <code>roots.get(i)</code> using
230 	 * <code>wizards.get(i+1)</code> wizard. Note that this wizard may be
231 	 * <code>null</code> if element does not require any contents.
232 	 */
233 	protected final List<NewElementRule> rules = new ArrayList<NewElementRule>();
234 	/***
235 	 * 'Root' elements - elements that'll be inserted into given parent element.
236 	 */
237 	protected List<Element> roots = null;
238 	/***
239 	 * 'Root' elements - elements that'll be inserted into given parent element.
240 	 * If wizard shows its first page then <code>null</code> is returned.
241 	 * @return list of root elements or <code>null</code> if wizard shows its
242 	 * first page.
243 	 */
244 	public List<Element> getElements() {
245 		if (roots == null)
246 			return null;
247 		return Collections.unmodifiableList(roots);
248 	}
249 	/***
250 	 * Selected insert list.
251 	 * @return selected insert list. <code>null</code> if this provider is
252 	 * showing first wizard.
253 	 */
254 	public InsertList getInsertList() {
255 		return selected;
256 	}
257 	/***
258 	 * Previous namespace manager. If this provider is showing first wizard,
259 	 * then construct-time namespace manager is returned.
260 	 * @return current namespace manager. Queried from the last shown wizard
261 	 * (i.e. wizard before the currently active wizard).
262 	 */
263 	public NamespaceManager getPrevNsManager() {
264 		if (wizards.size() == 1)
265 			return startNsManager;
266 		for (int i = wizards.size() - 2; i > 0; i--) {
267 			MultiWizard w = (MultiWizard) wizards.get(i);
268 			if (w == null)
269 				continue;
270 			FillNewElementWizardProvider p = (FillNewElementWizardProvider) w
271 					.getProvider();
272 			return p.newNsManager();
273 		}
274 		InsertListChooserWizard first = (InsertListChooserWizard) wizards
275 				.get(0);
276 		return first.newNsManager();
277 	}
278 	/***
279 	 * Current namespace manager.
280 	 * @return current namespace manager. Queried from the currently active
281 	 * wizard.
282 	 */
283 	public NamespaceManager newNsManager() {
284 		if (wizards.size() == 1) {
285 			InsertListChooserWizard first = (InsertListChooserWizard) wizards
286 					.get(0);
287 			return first.newNsManager();
288 		}
289 		MultiWizard w = (MultiWizard) wizards.get(wizards.size() - 1);
290 		FillNewElementWizardProvider p = (FillNewElementWizardProvider) w
291 				.getProvider();
292 		return p.newNsManager();
293 	}
294 	/***
295 	 * Selected insert list. <code>null</code> if this provider is showing
296 	 * first wizard.
297 	 */
298 	protected InsertList selected;
299 	/*
300 	 * (non-Javadoc)
301 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#next()
302 	 */
303 	public IWizard next() {
304 		assert wizards.size() > 0;
305 		if (wizards.size() == 1) {
306 			InsertListChooserWizard first = (InsertListChooserWizard) wizards
307 					.get(0);
308 			selected = first.ilcPage.getWidget().getInsertList();
309 			// make a list of 'root' elements
310 			List<QName> rootsNames = first.getNames();
311 			roots = new ArrayList<Element>(rootsNames.size());
312 			for (QName rootName : rootsNames) {
313 				Element e = xmlAccess.getDocument().createElementNS(
314 						rootName.getNamespaceURI(),
315                         DOMUtils.printQName(rootName));
316 				roots.add(e);
317 			}
318 		}
319 		// get next wizard
320 		IWizard next = findNextWizard();
321 		if (next == null)
322 			throw new NoSuchElementException();
323 		return next;
324 	}
325 	/***
326 	 * Finds next wizard for querying contents of an element. Returns
327 	 * <code>null</code> if no wizard is needed. All used rules are placed
328 	 * into the <code>rules</code> array to mark progress done. The
329 	 * <code>wizards</code> array is filled with <code>null</code>s if
330 	 * required, to remain consistent with the <code>rules</code> array.
331 	 * @return a wizard instance, or <code>null</code> if no wizard is needed.
332 	 */
333 	protected IWizard findNextWizard() {
334 		assert roots != null;
335 		if (rules.size() == roots.size())
336 			return null;
337 		assert wizards.size() == rules.size() + 1;
338 		// for each element its contents must be filled. Find first element that
339 		// actually requires some content.
340 		for (int i = rules.size(); i < roots.size(); i++) {
341 			Element root = roots.get(i);
342 			INameList<NewElementRule> nameList = selected.get(i).nameList;
343 			NewElementRule rule = nameList.getRule(DOMUtils.getQName(root),
344 					true);
345 			// try to create provider for creating element's contents
346 			FillNewElementWizardProvider p = new FillNewElementWizardProvider(
347 					root, rule, xmlAccess, newNsManager(), null);
348 			MultiWizard wizard;
349 			if (p.current() == null) {
350 				// no needed contents. set wizard to null
351 				wizard = null;
352 			} else {
353 				wizard = new MultiWizard(p);
354 			}
355 			wizards.add(wizard);
356 			rules.add(rule);
357 			if (wizard != null)
358 				return wizard;
359 		}
360 		// no wizard found, return null and cleanup last added null wizards
361 		for (int i = wizards.size() - 1; i > 0; i--) {
362 			if (wizards.get(i) != null)
363 				break;
364 			wizards.remove(i);
365 			rules.remove(i - 1);
366 		}
367 		return null;
368 	}
369 	/*
370 	 * (non-Javadoc)
371 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#previous()
372 	 */
373 	public IWizard previous() {
374 		if (!hasPrevious())
375 			throw new NoSuchElementException();
376 		// find last non-null wizard, except the very last one.
377 		int lastWizard = wizards.size() - 1;
378 		wizards.set(lastWizard, null);
379 		for (int i = lastWizard; i > 0; i--) {
380 			if (wizards.get(i) != null)
381 				break;
382 			wizards.remove(i);
383 			rules.remove(i - 1);
384 		}
385 		// we got the wizard.
386 		assert wizards.size() != 0;
387 		if (wizards.size() == 1) {
388 			selected = null;
389 			roots = null;
390 		}
391 		return wizards.get(wizards.size() - 1);
392 	}
393 	/*
394 	 * (non-Javadoc)
395 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#hasNext()
396 	 */
397 	public boolean hasNext() {
398 		// a simple and uneffective code
399 		try {
400 			next();
401 			previous();
402 			return true;
403 		} catch (NoSuchElementException ex) {
404 			return false;
405 		}
406 	}
407 	/*
408 	 * (non-Javadoc)
409 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#hasPrevious()
410 	 */
411 	public boolean hasPrevious() {
412 		return wizards.size() > 1;
413 	}
414 	/*
415 	 * (non-Javadoc)
416 	 * @see sk.uniba.euromath.editor.wizards.IMultiWizardProvider#getName()
417 	 */
418 	public String getName() {
419 		return null;
420 	}
421 }