View Javadoc

1   /*	
2   	Copyright 2007-2014 Fraunhofer IGD, http://www.igd.fraunhofer.de
3   	Fraunhofer-Gesellschaft - Institute for Computer Graphics Research
4   	
5   	See the NOTICE file distributed with this work for additional 
6   	information regarding copyright ownership
7   	
8   	Licensed under the Apache License, Version 2.0 (the "License");
9   	you may not use this file except in compliance with the License.
10  	You may obtain a copy of the License at
11  	
12  	  http://www.apache.org/licenses/LICENSE-2.0
13  	
14  	Unless required by applicable law or agreed to in writing, software
15  	distributed under the License is distributed on an "AS IS" BASIS,
16  	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  	See the License for the specific language governing permissions and
18  	limitations under the License.
19   */
20  package org.universAAL.middleware.ui.rdf;
21  
22  import java.util.ArrayList;
23  import java.util.Enumeration;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import org.universAAL.middleware.owl.ManagedIndividual;
28  import org.universAAL.middleware.owl.MergedRestriction;
29  import org.universAAL.middleware.rdf.PropertyPath;
30  import org.universAAL.middleware.rdf.Resource;
31  import org.universAAL.middleware.rdf.TypeMapper;
32  import org.universAAL.middleware.ui.owl.DialogType;
33  
34  /**
35   * A subclass of {@link Group} whose children are all of the same type. Hence it
36   * allows only one child as a pseudo placeholder for all the repeatable form
37   * controls of that single type. Consequently, calling
38   * {@link FormControl#getValue()} on repeat controls returns always an instance
39   * of {java.util.List} containing a list of objects with the same type. An
40   * implication hereof is that also the initial value for repeat controls must be
41   * a list.
42   * <p>
43   * As direct child of a repeat control, neither {@link Submit} nor repeat
44   * controls are allowed. If this single pseudo placeholder is then a
45   * {@link Group} control, then the children of that group play quasi the role of
46   * columns in a virtual table whose rows are the entries of the list returned by
47   * {@link FormControl#getValue()}. Otherwise, the Repeat object represents a one
48   * column list of simple values.
49   * <p>
50   * As the single child control of a Repeat object is only a pseudo placeholder
51   * control, its {@link FormControl#PROP_REFERENCED_PPATH} property must be null.
52   * If this single child is a {@link Group} control, then the reference path for
53   * all of the form controls in the subtree rooted at the pseudo child must have
54   * a value relative to that repeat control and not relative to the root of the
55   * form data, as it must be in all other cases.
56   * <p>
57   * This implementation uses a fixed selection model (confer Java standard
58   * selection models like {@link javax.swing.ListSelectionModel} and
59   * {@link javax.swing.tree.TreeSelectionModel}), where the single child control
60   * of it represents always the selected list entry. A selection index points to
61   * the corresponding entry in the list returned by {@link #getValue()}. If this
62   * index is out of range (normally -1), then the data held by the single child
63   * control is understood as candidate for being added to the list of values (see
64   * {@link #addValue()}).
65   * <p>
66   * The above mentioned selection model does not provide any eventing mechanism
67   * because the change of the selection occurs only through public methods of the
68   * class (which means that there is no automatic change of selection) and also
69   * it is assumed that instances of this class are not used in parallel so that
70   * the component having the control over it is the only source of selection
71   * change (by calling appropriate methods) that does not need to be notified.
72   * 
73   * @author mtazari
74   * @author Carsten Stockloew
75   */
76  public class Repeat extends Group {
77      public static final String MY_URI = Form.uAAL_DIALOG_NAMESPACE + "Repeat";
78  
79      /**
80       * Indicates if entries can be removed from the list of initial values
81       * associated with a repeat control.
82       */
83      public static final String PROP_IS_DELETABLE = Form.uAAL_DIALOG_NAMESPACE
84  	    + "listEntriesDeletable";
85  
86      /**
87       * Indicates if entries in the list of initial values associated with a
88       * repeat control can be edited.
89       */
90      public static final String PROP_IS_EDITABLE = Form.uAAL_DIALOG_NAMESPACE
91  	    + "listEntriesEditable";
92  
93      /**
94       * Indicates if new entries can be added to the list of initial values
95       * associated with a repeat control.
96       */
97      public static final String PROP_IS_EXPANDABLE = Form.uAAL_DIALOG_NAMESPACE
98  	    + "listAcceptsNewEntries";
99  
100     /**
101      * The form control in the repeat that plays the role of a searchable column
102      * in the sense that the UI handler should provide an additional input field
103      * where the user can enter text to be used to select a specific entry in
104      * the list of values associated with a repeat control.
105      */
106     public static final String PROP_SEARCHABLE_FIELD = Form.uAAL_DIALOG_NAMESPACE
107 	    + "searchableField";
108 
109     private List values = null;
110     private Object selection = null;
111     private int selectionIndex = -1;
112     private boolean submissionChecked = false;
113 
114 	/**
115 	 * List of virtualForms generated when {@link Repeat#virtualFormExpansion()} is called.
116 	 */
117 	private List vForms;
118 
119     /**
120      * For exclusive use by de-serializers.
121      */
122     public Repeat() {
123 	super();
124     }
125 
126     /**
127      * Constructs a new repeat control.
128      * 
129      * @param parent
130      *            The mandatory parent group as the direct container of this
131      *            repeat control. See {@link FormControl#PROP_PARENT_CONTROL}.
132      * @param label
133      *            The optional {@link Label} to be associated with this input
134      *            field. See {@link FormControl#PROP_CONTROL_LABEL}.
135      * @param ref
136      *            See {@link FormControl#PROP_REFERENCED_PPATH}; mandatory.
137      * @param valueRestriction
138      *            See {@link Input#PROP_VALUE_RESTRICTION}. Because repeat
139      *            controls may contain input controls, you may specify here a
140      *            {@link org.universAAL.middleware.owl.MergedRestriction} to let
141      *            the dialog package to derive the value restrictions for
142      *            contained input controls if the form data does not bear the
143      *            required info or if you want to define more restrictions
144      *            compared to that model-based restrictions.
145      * @param initialValue
146      *            A {@link java.util.List} to be used as the initial value for
147      *            this repeat.
148      */
149     public Repeat(Group parent, Label label, PropertyPath ref,
150 	    MergedRestriction valueRestriction, List initialValue) {
151 	super(MY_URI, parent, label, ref, valueRestriction, initialValue);
152 
153 	if (getMaxCardinality() == 1)
154 	    throw new IllegalArgumentException(
155 		    "Wrong restrictions disallowing repeat!");
156 
157 	values = initialValue;
158     }
159 
160     void addChild(FormControl child) {
161 	if (child == null || child instanceof Submit || child instanceof Repeat)
162 	    throw new IllegalArgumentException(
163 		    "Only Input, Output, and Group instances are allowed!");
164 
165 	List children = (List) props.get(PROP_CHILDREN);
166 	if (children == null) {
167 	    children = new ArrayList();
168 	    props.put(PROP_CHILDREN, children);
169 	} else if (children.size() > 0)
170 	    throw new UnsupportedOperationException(
171 		    "Not allowed to add more than one child to a Repeat object!");
172 
173 	child.changeProperty(PROP_REFERENCED_PPATH, null);
174 	children.add(child);
175     }
176 
177     /**
178      * Adds the value held by the single pseudo child control to the end of the
179      * list of values associated with this repeat control. Note: changes to the
180      * selection itself (achieved by calling
181      * {@link Input#storeUserInput(Object)} on child controls of a reapt) are
182      * only local until either this method or {@link #updateSelection()} is
183      * called.
184      * 
185      * @return true, if either there is no selection or the value differs from
186      *         the selected entry in the the list of values currently associated
187      *         with this repeat control. Otherwise it does not add the value and
188      *         returns false.
189      */
190     public boolean addValue() {
191 	checkValues();
192 
193 	if (selection != null && !values.contains(selection)
194 		&& (selectionIndex == -1 || valuesDiffer())) {
195 	    selectionIndex = values.size();
196 	    values.add(selection);
197 	    if (selection instanceof Resource)
198 		selection = ((Resource) selection).deepCopy();
199 	    submissionChecked = false;
200 	    return true;
201 	}
202 	return false;
203     }
204 
205     /**
206      * If applications have provided a list as initial data for this repeat
207      * control, they can forbid the addition of new values to that list by
208      * calling this method.
209      * 
210      * @see #PROP_IS_EXPANDABLE
211      */
212     public void banEntryAddition() {
213 	props.put(PROP_IS_EXPANDABLE, Boolean.FALSE);
214     }
215 
216     /**
217      * If applications have provided a list as initial data for this repeat
218      * control, they can forbid the deletion of values from that list by calling
219      * this method.
220      * 
221      * @see #PROP_IS_DELETABLE
222      */
223     public void banEntryDeletion() {
224 	props.put(PROP_IS_DELETABLE, Boolean.FALSE);
225     }
226 
227     /**
228      * If applications have provided a list as initial data for this repeat
229      * control, they can forbid the edition of existing values in that list by
230      * calling this method.
231      * 
232      * @see #PROP_IS_EDITABLE
233      */
234     public void banEntryEdit() {
235 	props.put(PROP_IS_EDITABLE, Boolean.FALSE);
236     }
237 
238     boolean checkSubmission() {
239 	if (!submissionChecked) {
240 	    checkValues();
241 
242 	    try {
243 		submissionChecked = getFormObject().setValue(
244 			getReferencedPPath().getThePath(), values,
245 			(MergedRestriction) props.get(PROP_VALUE_RESTRICTION));
246 	    } catch (Exception e) {
247 		submissionChecked = false;
248 	    }
249 	}
250 	return submissionChecked;
251     }
252 
253     private synchronized void checkValues() {
254 	if (values == null) {
255 	    Object o = super.getValue();
256 	    if (o instanceof List)
257 		values = (List) o;
258 	    else {
259 		values = new ArrayList();
260 		if (o != null)
261 		    values.add(o);
262 	    }
263 	}
264     }
265 
266     /**
267      * If the given form control is a column in this repeat control, this method
268      * returns the list of all values in that column in the order of their
269      * appearance in the list of values associated with this repeat control.
270      * Empty cells will add a null value to the returned list.
271      */
272     public List getAllValues(FormControl fc) {
273 	return (fc == null) ? null : getAllValues(fc.getReferencedPPath());
274     }
275 
276     List getAllValues(PropertyPath pp) {
277 	checkValues();
278 
279 	String[] thePath = (pp == null) ? null : pp.getThePath();
280 	boolean multiColumn = thePath != null && thePath.length > 0;
281 	List result = new ArrayList(values.size());
282 	for (Iterator i = values.iterator(); i.hasNext();) {
283 	    Object o = i.next();
284 	    if (o != null && multiColumn != (o instanceof Resource))
285 		return null;
286 	    if (multiColumn)
287 		result.add(Form.getValue(thePath, (Resource) o));
288 	    else
289 		result.add(o);
290 	}
291 
292 	return result;
293     }
294 
295     /**
296      * Returns the maximum number of values that can be associated with this
297      * repeat control. A negative integer is returned if there is no upper
298      * limit.
299      */
300     public int getMaxCardinality() {
301 	MergedRestriction r = getRestrictions();
302 	return (r == null) ? -1 : r.getMaxCardinality();
303     }
304 
305     /**
306      * Returns the minimum number of values that must be associated with this
307      * repeat control. A non-positive integer is returned if there is no lower
308      * limit.
309      */
310     public int getMinCardinality() {
311 	MergedRestriction r = getRestrictions();
312 	return (r == null) ? -1 : r.getMinCardinality();
313     }
314 
315     /**
316      * Returns the number of entries currently existing in the list of values
317      * associated with this repeat control.
318      */
319     public int getNumberOfValues() {
320 	checkValues();
321 
322 	return values.size();
323     }
324 
325     MergedRestriction getPPathRestriction(String[] pp) {
326 	MergedRestriction r = getRestrictions();
327 
328 	if (r != null) {
329 	    if (pp == null || pp.length == 0)
330 		return r.copyWithNewCardinality(1, 1);
331 
332 	    checkValues();
333 
334 	    if (values.isEmpty() || !(values.get(0) instanceof Resource))
335 		return Form.getPPathRestriction(pp, 0, r.getPropTypeURI());
336 	} else
337 	    checkValues();
338 
339 	return Form.getPPathRestriction(pp, (Resource) values.get(0));
340     }
341 
342     /**
343      * @see #PROP_SEARCHABLE_FIELD
344      */
345     public FormControl getSearchableField() {
346 	Object o = props.get(PROP_SEARCHABLE_FIELD);
347 	return (o instanceof FormControl) ? (FormControl) o : null;
348     }
349 
350     /**
351      * Returns the index of the current selection in the list of values
352      * associated with this repeat control. If there is no selection, returns a
353      * negative number.
354      */
355     public int getSelectionIndex() {
356 	return selectionIndex;
357     }
358 
359     String getTypeURI(String[] pp) {
360 	MergedRestriction r = getPPathRestriction(pp);
361 	return (r == null) ? null : r.getPropTypeURI();
362     }
363 
364     /**
365      * Returns the value that can be reached by the given path starting from the
366      * current selection.
367      */
368     public Object getValue(String[] pp) {
369 	return (pp == null || pp.length == 0) ? selection
370 		: (selection instanceof Resource) ? Form.getValue(pp,
371 			(Resource) selection) : null;
372     }
373 
374     boolean hasValue(String[] pp) {
375 	checkValues();
376 
377 	if (values.isEmpty())
378 	    return false;
379 
380 	if (pp == null || pp.length == 0)
381 	    return TypeMapper.getDatatypeURI(values.get(0)) != null;
382 
383 	for (Iterator i = values.iterator(); i.hasNext();) {
384 	    Object o = i.next();
385 	    if (o instanceof Resource
386 		    && Form.getValue(pp, (Resource) o) != null)
387 		return true;
388 	}
389 
390 	return false;
391     }
392 
393     /**
394      * @see #PROP_IS_EXPANDABLE
395      */
396     public boolean listAcceptsNewEntries() {
397 	return !Boolean.FALSE.equals(props.get(PROP_IS_EXPANDABLE));
398     }
399 
400     /**
401      * @see #PROP_IS_DELETABLE
402      */
403     public boolean listEntriesDeletable() {
404 	return !Boolean.FALSE.equals(props.get(PROP_IS_DELETABLE));
405     }
406 
407     /**
408      * @see #PROP_IS_EDITABLE
409      */
410     public boolean listEntriesEditable() {
411 	return !Boolean.FALSE.equals(props.get(PROP_IS_EDITABLE));
412     }
413 
414     /**
415      * If there is a selection that is not the last element in the list of
416      * values associated with this repeat control, its place will be exchanged
417      * with the next list element (the element whose index is equal to the
418      * selection index plus 1). Can be used for sorting the list of values
419      * associated with this repeat control.
420      */
421     public void moveSelectionDown() {
422 	if (values != null && selectionIndex > -1
423 		&& selectionIndex < values.size() - 1) {
424 	    int i = selectionIndex++;
425 	    Object o = values.get(selectionIndex);
426 	    values.set(selectionIndex, values.get(i));
427 	    values.set(i, o);
428 	}
429     }
430 
431     /**
432      * If there is a selection that is not the first element in the list of
433      * values associated with this repeat control (its index is greater than 0),
434      * its place will be exchanged with the previous list element (the element
435      * whose index is equal to the selection index minus 1). Can be used for
436      * sorting the list of values associated with this repeat control.
437      */
438     public void moveSelectionUp() {
439 	if (selectionIndex > 0 && selectionIndex < values.size()) {
440 	    int i = selectionIndex--;
441 	    Object o = values.get(selectionIndex);
442 	    values.set(selectionIndex, values.get(i));
443 	    values.set(i, o);
444 	}
445     }
446 
447     private Object newValue() {
448 	String type = getTypeURI();
449 	if (type == null) {
450 	    List children = (List) props.get(PROP_CHILDREN);
451 	    if (children == null || children.size() != 1)
452 		throw new IllegalStateException("Repeat object in wrong state!");
453 	    if (children.get(0) instanceof Group)
454 		return new Resource();
455 	} else if (TypeMapper.isRegisteredDatatypeURI(type)) {
456 	    Resource pr = ManagedIndividual.getInstance(type, null);
457 	    if (pr == null) {
458 		pr = new Resource();
459 		pr.addType(type, false);
460 	    }
461 	    return pr;
462 	}
463 	return null;
464     }
465 
466     /**
467      * Removes the current selection from the list of values associated with
468      * this repeat control. After this operation, there will be no selection and
469      * it must be set explicitly by calling {@link #setSelection(int)}.
470      * 
471      * @return true, if there was a selection; false, otherwise.
472      */
473     public boolean removeSelection() {
474 	if (selectionIndex > -1 && selectionIndex < values.size()) {
475 	    values.remove(selectionIndex);
476 	    submissionChecked = false;
477 	    selectionIndex = -1;
478 	    selection = null;
479 	    return true;
480 	}
481 	return false;
482     }
483 
484     /**
485      * @see #PROP_SEARCHABLE_FIELD
486      */
487     public void setSearchableField(FormControl fc) {
488 	if (fc != null)
489 	    props.put(PROP_SEARCHABLE_FIELD, fc);
490     }
491 
492     /**
493      * Changes the current selection to point to the element that has the given
494      * index (the parameter i) in the list of values associated with this
495      * repeat. If the given index is out of range, the effect will be equivalent
496      * to de-selecting any current selection.
497      */
498     public void setSelection(int i) {
499 	checkValues();
500 
501 	if (i < 0 || i >= values.size()) {
502 	    selection = newValue();
503 	    selectionIndex = -1;
504 	} else {
505 	    selection = values.get(i);
506 	    if (selection instanceof Resource)
507 		selection = ((Resource) selection).deepCopy();
508 	    selectionIndex = i;
509 	}
510     }
511 
512     boolean setValue(String[] pp, Object value,
513 	    MergedRestriction valueRestrictions) {
514 	if (selection == null)
515 	    selection = newValue();
516 
517 	if (selection instanceof Resource)
518 	    return Form.setValue((Resource) selection, pp, value,
519 		    valueRestrictions);
520 	else if (pp == null || pp.length == 0) {
521 	    if (value == null) {
522 		selection = null;
523 		selectionIndex = -1;
524 	    } else {
525 		Object aux = props.get(PROP_REFERENCED_PPATH);
526 		String prop = (aux instanceof PropertyPath) ? ((PropertyPath) aux)
527 			.getLastPathElement()
528 			: null;
529 		if (prop == null || valueRestrictions != null)
530 		    // a single input child does not need any value restrictions
531 		    // such value restriction must be added to the Repeat object
532 		    // itself
533 		    // so, this case is a sign of inconsistent state
534 		    // TODO: add a log entry!
535 		    return false;
536 		valueRestrictions = getRestrictions();
537 		Resource dummy = new Resource();
538 		dummy.setProperty(prop, value);
539 		if (valueRestrictions != null
540 			&& !valueRestrictions.hasMemberIgnoreCardinality(dummy))
541 		    return false;
542 		selection = value;
543 		return true;
544 	    }
545 	}
546 	return false;
547     }
548 
549     /**
550      * If there is a valid selection, the local changes to it will be reflected
551      * in the list of values associated with this repeat control. In this case,
552      * it returns true, otherwise null.
553      */
554     public boolean updateSelection() {
555 	if (selection != null && selectionIndex > -1
556 		&& selectionIndex < values.size()) {
557 	    values.set(selectionIndex, selection);
558 	    if (selection instanceof Resource)
559 		selection = ((Resource) selection).deepCopy();
560 	    submissionChecked = false;
561 	    return true;
562 	}
563 
564 	return false;
565     }
566 
567     private boolean valuesDiffer() {
568 	if (selection instanceof Resource) {
569 	    Object o = values.get(selectionIndex);
570 	    for (Enumeration e = ((Resource) selection).getPropertyURIs(); e
571 		    .hasMoreElements();) {
572 		String key = e.nextElement().toString();
573 		if (!((Resource) selection).getProperty(key).equals(
574 			((Resource) o).getProperty(key)))
575 		    return true;
576 	    }
577 	    return false;
578 	} else
579 	    return true;
580     }
581     
582 	/**
583 	 * Generates a {@link List} of {@link Form}s which each contains in its IOControls group
584 	 * the corresponding row of {@link FormControl}s. Each of these {@link FormControl}s will be a copy
585 	 * of the {@link Repeat}'s {@link FormControl}s  but their {@link FormControl#getValue()} and {@link Input#storeUserInput(Object)}(if applies)
586 	 * will be redirected to the correct place.
587 	 * <br>
588 	 * <p>
589 	 * If the {@link Repeat}'s child is a single {@link FormControl}, then each generated {@link Form}'s IOControls group will 
590 	 * contain the copy of the referenced {@link FormControl}. 
591 	 * If the {@link Repeat}'s child is a {@link Group}, then the {@link Group}'s children will be copied into the 
592 	 * new {@link Form}s IOGroup.
593 	 * </p><p>
594 	 * This works because the dataRoot of each new {@link Form} is the one corresponding for the row, so each {@link FormControl}
595 	 * can be modeled as usual. this works whether the property path of the {@link Repeat} points to a {@link List} of {@link Resource}s or
596 	 * a {@link List} of {@link Object}s (in which case the propertypaths of the child of the {@link Repeat} should be empty.
597 	 * <p>
598 	 * @return a {@link List} of {@link Form}s.
599 	 * @throws IllegalArgumentException if the prerequisites are not met.
600 	 */
601 	public List virtualFormExpansion() {
602 		if (vForms == null){
603 			vForms = generateVirtualForms();
604 		}
605 		return vForms;
606 	}
607 	
608 	private List generateVirtualForms(){
609 		FormControl[] elems = getChildren();
610 		if (elems == null || elems.length != 1 || elems[0] == null) {
611 			throw new IllegalArgumentException("Malformed Repeat only allowed one valid child!");
612 		}
613 		if (elems[0] instanceof Group) {
614 			elems = ((Group) elems[0]).getChildren();
615 			if (elems == null || elems.length == 0)
616 				throw new IllegalArgumentException("Malformed child group is empty!");
617 		}else if (! (elems[0] instanceof FormControl)){
618 			throw new IllegalArgumentException("child is not a FormControl!");
619 		}
620 		
621 		ArrayList formList = new ArrayList();
622 		PropertyPath ref = (PropertyPath) getProperty(PROP_REFERENCED_PPATH);
623 		Object repeatData = getFormObject().getValue(ref.getThePath());
624 		List repeatList = null;
625 		if (repeatData instanceof Resource) {
626 //			repeatList = ((Resource) repeatData).asList();
627 		    repeatList = new ArrayList();
628 		    repeatList.add(repeatData);
629 		}
630 		if (repeatData instanceof List) {
631 			repeatList = (List) repeatData;
632 		}
633 		if (repeatData == null) {
634 			// it is not initialised
635 			repeatList = new ArrayList();
636 		}
637 		
638 		int index = 0;
639 		for (Iterator i = repeatList.iterator(); i.hasNext();) {
640 			Object res = i.next();
641 			Form subForm = VirtualForm.createNewVirtualForm(res);
642 			Group gio = (Group) subForm.getIOControls();
643 			for (int j = 0; j < elems.length; j++) {
644 				if (elems[j] != null) {
645 					FormControl nFC = (FormControl) elems[j].copy(false);
646 					gio.addChild(nFC);
647 					if (elems[j] instanceof SubdialogTrigger) {
648 						nFC.changeProperty(
649 								SubdialogTrigger.PROP_SUBMISSION_ID,
650 								nFC.getProperty(SubdialogTrigger.PROP_REPEATABLE_ID_PREFIX)
651 										+ Integer.toString(index));
652 					} 
653 					else {
654 						nFC.changeProperty(PROP_PARENT_CONTROL, gio); 
655 					}
656 				}
657 			}
658 			formList.add(subForm);
659 			index++;
660 		}
661 
662 		return formList;
663 	}
664 
665 	/** {@ inheritDoc}	 */
666 	public FormControl searchFormControl(String formControlURI) {
667 		FormControl res =  super.searchFormControl(formControlURI);
668 		Iterator i = vForms.iterator();
669 		while (i.hasNext()
670 				&& res == null) {
671 			Form f = (Form) i.next();
672 			res = f.searchFormControl(formControlURI);
673 		}
674 		return res;
675 	}
676 	
677 	private static class VirtualForm extends Form {
678 		
679 		
680 		/**
681 		 * 
682 		 */
683 		public VirtualForm(Object dataRoot) {
684 			super(uAAL_DIALOG_NAMESPACE, 5);
685 			addType(MY_URI, true);
686 			props.put(PROP_DIALOG_CREATION_TIME, TypeMapper.getCurrentDateTime());
687 			props.put(PROP_ROOT_GROUP, new Group("Virtual Form", this));
688 			props.put(PROP_DIALOG_DATA_ROOT, (dataRoot == null) ? new Resource()
689 				: dataRoot);
690 		}
691 		
692 		static public Form createNewVirtualForm(Object root){
693 			Form f = new VirtualForm(root);
694 			f.changeProperty(PROP_DIALOG_TYPE, DialogType.stdDialog);
695 			Group rootg = (Group) f.getProperty(Form.PROP_ROOT_GROUP);
696 			new Group(rootg, new Label(Group.STD_IO_CONTROLS, null), null, null,
697 				null);
698 			return f;
699 		}
700 
701 		/** {@ inheritDoc}	 */
702 		Object getValue(String[] pp) {
703 			/*if (pp == null) 
704 				return null;*/
705 			// this is because controls may not be linked to anything
706 			if (pp == null || pp.length == 0)
707 			    return props.get(PROP_DIALOG_DATA_ROOT);
708 			else
709 				return super.getValue(pp);
710 		}
711 		
712 	}
713 }