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.Iterator;
24  import java.util.List;
25  
26  import org.universAAL.middleware.container.utils.LogUtils;
27  import org.universAAL.middleware.container.utils.StringUtils;
28  import org.universAAL.middleware.owl.ManagedIndividual;
29  import org.universAAL.middleware.owl.MergedRestriction;
30  import org.universAAL.middleware.owl.OntologyManagement;
31  import org.universAAL.middleware.owl.supply.LevelRating;
32  import org.universAAL.middleware.rdf.PropertyPath;
33  import org.universAAL.middleware.rdf.Resource;
34  import org.universAAL.middleware.rdf.TypeMapper;
35  import org.universAAL.middleware.ui.impl.UIBusImpl;
36  
37  /**
38   * The structural unit of forms that serves as a container for other form
39   * controls.
40   * 
41   * @author mtazari
42   * @author Carsten Stockloew
43   * @navassoc "" children * FormControl
44   */
45  public class Group extends FormControl {
46      public static final String MY_URI = Form.uAAL_DIALOG_NAMESPACE + "Group";
47  
48      /**
49       * For each group, the list of form controls that have been created with
50       * that group as their direct container (parent) in the order of their
51       * creation.
52       */
53      public static final String PROP_CHILDREN = Form.uAAL_DIALOG_NAMESPACE
54  	    + "groupChildren";
55  
56      static final String STD_IO_CONTROLS = "ioControlsGroup";
57      static final String STD_SUBMITS = "submitsGroup";
58      static final String STD_STD_BUTTONS = "stdButtonsGroup";
59  
60      private int level = -1, numSubgroups = -1;
61      private boolean hasInput = false, hasOutput = false;
62      private LevelRating complexity;
63  
64      /**
65       * For use by de-serializers only.
66       */
67      public Group() {
68  	super();
69      }
70  
71      /**
72       * Constructs a new group. The last three parameters will be ignored if the
73       * parent is a {@link Repeat} control.
74       * 
75       * @param parent
76       *            The mandatory parent group as the direct container of this
77       *            group. See {@link FormControl#PROP_PARENT_CONTROL}.
78       * @param label
79       *            The optional {@link Label} to be associated with this group.
80       *            See {@link FormControl#PROP_CONTROL_LABEL}.
81       * @param ref
82       *            See {@link FormControl#PROP_REFERENCED_PPATH}.
83       * @param valueRestriction
84       *            See {@link Input#PROP_VALUE_RESTRICTION}. Because Groups may
85       *            contain input controls, you may specify here a
86       *            {@link org.universAAL.middleware.owl.MergedRestriction} to let
87       *            the dialog package to derive the value restrictions for any
88       *            input control whose {@link FormControl#PROP_REFERENCED_PPATH}
89       *            starts with the path given for <code>ref</code> (the previous
90       *            parameter in this constructor). If <code>ref</code> is
91       *            <code>null</code>, this parameter will be ignored.
92       * @param initialValue
93       *            A {@link org.universAAL.middleware.rdf.Resource} to be used as
94       *            the initial value for this group. If this is specified, then
95       *            the initial values of any other control whose
96       *            {@link FormControl#PROP_REFERENCED_PPATH} starts with the path
97       *            given for the above <code>ref</code> might be derivable from
98       *            this value.
99       */
100     public Group(Group parent, Label label, PropertyPath ref,
101 	    MergedRestriction valueRestriction, Resource initialValue) {
102 	super(MY_URI, parent, label, ref, valueRestriction, initialValue);
103     }
104 
105     protected Group(String typeURI, Group parent, Label label,
106 	    PropertyPath ref, MergedRestriction valueRestriction,
107 	    Object initialValue) {
108 	super(typeURI, parent, label, ref, valueRestriction, initialValue);
109     }
110 
111     Group(String label, Form theForm) {
112 	super(MY_URI, theForm, new Label(label, null), null, null, null);
113     }
114 
115     void addChild(FormControl child) {
116 	if (child == null)
117 	    return;
118 
119 	List children = (List) props.get(PROP_CHILDREN);
120 	if (children == null) {
121 	    children = new ArrayList();
122 	    props.put(PROP_CHILDREN, children);
123 	} else {
124 	    Object o = props.get(PROP_PARENT_CONTROL);
125 	    if (o instanceof Form) {
126 		// this is the root group
127 		if (((Form) o).isStandardDialog()) {
128 		    if (children.size() == 3)
129 			// dialogs may have only 3 children that are created
130 			// automatically by Form.newDialog
131 			throw new UnsupportedOperationException(
132 				"Not allowed to add children to the root group!");
133 		} else if (children.size() == 2)
134 		    // all other dialogs may have only 2 children that are
135 		    // created
136 		    // automatically by Form.newSysMenu, Form.newSubdialog or
137 		    // Form.newMessage
138 		    throw new UnsupportedOperationException(
139 			    "Not allowed to add children to the root group!");
140 	    }
141 	}
142 
143 	children.add(child);
144     }
145 
146     private void addSubtree(List l, Submit s) {
147 	List children = (List) props.get(PROP_CHILDREN);
148 	if (children == null || children.isEmpty())
149 	    return;
150 
151 	for (Iterator i = children.iterator(); i.hasNext();) {
152 	    FormControl fc = (FormControl) i.next();
153 	    if (fc instanceof Group && !(fc instanceof Repeat)) {
154 		l.add(fc);
155 		((Group) fc).addSubtree(l, s);
156 	    } else if (!(fc instanceof Submit)
157 		    || fc instanceof SubdialogTrigger)
158 		l.add(fc);
159 	}
160     }
161 
162     private PropertyPath constructSubpath(String subprop) {
163 	PropertyPath pp = getReferencedPPath();
164 	if (pp == null)
165 	    return null;
166 
167 	String[] subpath, path = pp.getThePath();
168 	if (path == null || path.length == 0)
169 	    return null;
170 
171 	subpath = new String[path.length + 1];
172 	for (int i = 0; i < path.length; i++)
173 	    subpath[i] = path[i];
174 	subpath[path.length] = subprop;
175 	return new PropertyPath(null, false, subpath);
176     }
177 
178     /**
179      * Based on the knowledge about the type of this group (see
180      * {@link #getTypeURI()}), this method generates a hierarchy of form
181      * controls that reflects the exact structure known from the type of this
182      * group. Info about the structure of the type of this group will be
183      * available only if {@link #getTypeURI()} returns a subclass of
184      * {@link org.universAAL.middleware.owl.ManagedIndividual ManagedIndividual}
185      * ; then, the structural info will be retrieved based on
186      * {@link org.universAAL.middleware.owl.OntClassInfo#getDeclaredPropertyURIs()
187      * the standard properties of that class} and
188      * {@link org.universAAL.middleware.owl.ManagedIndividual#getClassRestrictionsOnProperty(String,String)
189      * the class restrictions on them}. Properties with insufficient
190      * restrictions will be ignored. A property of type
191      * {@link org.universAAL.middleware.owl.ManagedIndividual ManagedIndividual}
192      * will create a subgroup; a property with a fixed set of values in its
193      * range will create a {@link Select} or {@link Select1} control, depending
194      * on possibly known cardinalities; all other properties will create an
195      * {@link InputField}. All of the above may be packed in a {@link Repeat}
196      * control, if the corresponding property allows more than one value.
197      */
198     public void doModelBasedExpansion() {
199 	String t = getTypeURI();
200 	if (t == null) {
201 	    LogUtils.logWarn(UIBusImpl.getModuleContext(), Group.class,
202 		    "doModelBasedExpansion",
203 		    new Object[] { "missing model ref!" }, null);
204 	    return;
205 	}
206 
207 	String[] props = OntologyManagement.getInstance().getOntClassInfo(t)
208 		.getDeclaredPropertyURIs();
209 	if (props == null || props.length == 0) {
210 	    LogUtils.logWarn(UIBusImpl.getModuleContext(), Group.class,
211 		    "doModelBasedExpansion",
212 		    new Object[] { "not a ManagedIndividual!" }, null);
213 	    return;
214 	}
215 
216 	for (int i = 0; i < props.length; i++) {
217 	    MergedRestriction r = ManagedIndividual
218 		    .getClassRestrictionsOnProperty(t, props[i]);
219 	    if (r == null) {
220 		LogUtils
221 			.logWarn(
222 				UIBusImpl.getModuleContext(),
223 				Group.class,
224 				"doModelBasedExpansion",
225 				new Object[] {
226 					"ignoring a property with unknown restrictions: ",
227 					props[i] }, null);
228 		continue;
229 	    }
230 	    Object[] values = r.getEnumeratedValues();
231 	    if (values == null || values.length == 0) {
232 		String tt = r.getPropTypeURI();
233 		if (tt == null)
234 		    LogUtils
235 			    .logWarn(
236 				    UIBusImpl.getModuleContext(),
237 				    Group.class,
238 				    "doModelBasedExpansion",
239 				    new Object[] {
240 					    "ignoring a property with insufficient restrictions: ",
241 					    props[i] }, null);
242 		else if (OntologyManagement.getInstance().isRegisteredClass(tt,
243 			true)) {
244 		    Group g = new Group(this, new Label(StringUtils
245 			    .deriveLabel(props[i]), null),
246 			    constructSubpath(props[i]), null, null);
247 		    g.doModelBasedExpansion();
248 		} else if (TypeMapper.isRegisteredDatatypeURI(tt))
249 		    new InputField(this, new Label(StringUtils
250 			    .deriveLabel(props[i]), null),
251 			    constructSubpath(props[i]), null, null);
252 		else
253 		    LogUtils.logWarn(UIBusImpl.getModuleContext(), Group.class,
254 			    "doModelBasedExpansion", new Object[] {
255 				    "ignoring a property with unknown type: ",
256 				    props[i] }, null);
257 	    } else {
258 		Select s = (r.getMinCardinality() > 1 || r.getMaxCardinality() != 1) ? new Select(
259 			this,
260 			new Label(StringUtils.deriveLabel(props[i]), null),
261 			constructSubpath(props[i]), null, null)
262 			: new Select1(this, new Label(StringUtils
263 				.deriveLabel(props[i]), null),
264 				constructSubpath(props[i]), null, null);
265 		s.generateChoices(values);
266 	    }
267 	}
268     }
269 
270     /**
271      * @see #PROP_CHILDREN
272      */
273     public FormControl[] getChildren() {
274 	List children = (List) props.get(PROP_CHILDREN);
275 	if (children == null)
276 	    return new FormControl[0];
277 	return (FormControl[]) children
278 		.toArray(new FormControl[children.size()]);
279     }
280 
281     /**
282      * This should help UI handlers to decide how to "render" a group. A group
283      * with no subgroups is assumed to have no complexity. Up to 3 non-complex
284      * subgroups cause low complexity. More than this or up to 3 subgroups with
285      * low complexity cause meddle complexity. More than this or up to 3
286      * subgroups with middle complexity cause high complexity. In all other
287      * cases the group is assumed to be fully complex.
288      * 
289      * @return The complexity of this group as a value between
290      *         {@link LevelRating#none} and {@link LevelRating#full}.
291      */
292     public LevelRating getComplexity() {
293 	if (level == -1)
294 	    getFormObject().finalizeGroupStructure();
295 	return complexity;
296     }
297 
298     Output[] getFirstLevelOutputs() {
299 	List children = (List) props.get(PROP_CHILDREN);
300 	if (children == null || children.isEmpty())
301 	    return new Output[0];
302 
303 	int subgroups = 0;
304 	Group subgroup = null;
305 	ArrayList al = new ArrayList();
306 	for (int i = 0; i < children.size(); i++)
307 	    if (children.get(i) instanceof Output)
308 		al.add(children.get(i));
309 	    else if (children.get(i) instanceof Group) {
310 		subgroups++;
311 		subgroup = (Group) children.get(i);
312 	    }
313 
314 	if (subgroups == 1) {
315 	    children = (List) subgroup.props.get(PROP_CHILDREN);
316 	    for (int i = 0; i < children.size(); i++)
317 		if (children.get(i) instanceof Output)
318 		    al.add(children.get(i));
319 	}
320 
321 	return (Output[]) al.toArray(new Output[al.size()]);
322     }
323 
324     /**
325      * Returns the level of this group in the hierarchical structure of a form.
326      * The standard groups of a form (see the documentation of the class
327      * {@link Form}) have the level 1. Their direct subgroups have the level 2,
328      * and so forth.
329      */
330     public int getHierarchyLevel() {
331 	if (level == -1)
332 	    getFormObject().finalizeGroupStructure();
333 	return level;
334     }
335 
336     /**
337      * Overrides {@link FormControl#getMaxLength()} by returning always -1,
338      * because no standard string representation of a group exists.
339      */
340     public int getMaxLength() {
341 	// not applicable
342 	return -1;
343     }
344 
345     /**
346      * Returns the number of direct subgroups in this group.
347      */
348     public int getNumberOfSubgroups() {
349 	if (level == -1)
350 	    getFormObject().finalizeGroupStructure();
351 	return numSubgroups;
352     }
353 
354     MergedRestriction getPPathRestriction(String[] pp) {
355 	Object parent = props.get(PROP_PARENT_CONTROL);
356 	if (parent instanceof Group)
357 	    return ((Group) parent).getPPathRestriction(pp);
358 	else if (parent instanceof Form)
359 	    return ((Form) parent).getPPathRestriction(pp);
360 	else
361 	    return null;
362     }
363 
364     FormControl[] getSubtree(Submit s) {
365 	ArrayList result = new ArrayList();
366 	addSubtree(result, s);
367 	return (FormControl[]) result.toArray(new FormControl[result.size()]);
368     }
369 
370     String getTypeURI(String[] pp) {
371 	Object r = props.get(PROP_VALUE_RESTRICTION);
372 	Object mypp = props.get(PROP_REFERENCED_PPATH);
373 	if (r instanceof MergedRestriction
374 		&& mypp instanceof PropertyPath
375 		&& PropertyPath.pathHasPrefix(pp, ((PropertyPath) mypp)
376 			.getThePath())) {
377 	    String[] subpath = PropertyPath.getSubpath(pp,
378 		    ((PropertyPath) mypp).getThePath().length - 1);
379 	    MergedRestriction target = ((MergedRestriction) r)
380 		    .getRestrictionOnPath(subpath);
381 	    if (target != null) {
382 		String type = target.getPropTypeURI();
383 		if (type != null)
384 		    return type;
385 	    }
386 	}
387 
388 	Object parent = props.get(PROP_PARENT_CONTROL);
389 	if (parent instanceof Group)
390 	    return ((Group) parent).getTypeURI(pp);
391 	else if (parent instanceof Form)
392 	    return ((Form) parent).getTypeURI(pp);
393 	else
394 	    return null;
395     }
396 
397     Object getValue(String[] pp) {
398 	Object parent = props.get(PROP_PARENT_CONTROL);
399 	if (parent instanceof Group)
400 	    return ((Group) parent).getValue(pp);
401 	else if (parent instanceof Form)
402 	    return ((Form) parent).getValue(pp);
403 	else
404 	    return null;
405     }
406 
407     /**
408      * Checks if there are any input controls in the subtree rooted at this
409      * group.
410      */
411     public boolean hasInput() {
412 	if (level == -1)
413 	    getFormObject().finalizeGroupStructure();
414 	return hasInput;
415     }
416 
417     /**
418      * Checks if there are any output controls in the subtree rooted at this
419      * group.
420      */
421     public boolean hasOutput() {
422 	if (level == -1)
423 	    getFormObject().finalizeGroupStructure();
424 	return hasOutput;
425     }
426 
427     boolean hasSubgroup(String label) {
428 	if (label == null)
429 	    return false;
430 	List children = (List) props.get(PROP_CHILDREN);
431 	if (children == null)
432 	    return false;
433 	for (Iterator i = children.iterator(); i.hasNext();) {
434 	    Object o = i.next();
435 	    if (o instanceof Group
436 		    && label.equals(((Group) o).getLabel().getText()))
437 		return true;
438 	}
439 	return false;
440     }
441 
442     boolean hasValue(String[] pp) {
443 	Object parent = props.get(PROP_PARENT_CONTROL);
444 	if (parent instanceof Group)
445 	    return ((Group) parent).hasValue(pp);
446 	return (parent instanceof Form && ((Form) parent).getValue(pp) != null);
447     }
448 
449     /**
450      * Checks if this group is one of the standard groups in the form (see the
451      * documentation of the class {@link Form}).
452      */
453     public boolean isRootGroup() {
454 	return getParentGroup().getProperty(PROP_PARENT_CONTROL) instanceof Form;
455     }
456 
457     void setStructuralProps(int level) {
458 	this.level = level++;
459 
460 	numSubgroups = 0;
461 	hasInput = hasOutput = false;
462 	int[] complexity = new int[] { 0, 0, 0, 0, 0 };
463 
464 	List children = (List) props.get(PROP_CHILDREN);
465 	if (children != null)
466 	    for (Iterator i = children.iterator(); i.hasNext();) {
467 		Object o = i.next();
468 		if (o instanceof Group) {
469 		    ((Group) o).setStructuralProps(level);
470 		    complexity[((Group) o).getComplexity().ord()]++;
471 		    if (((Group) o).hasInput())
472 			hasInput = true;
473 		    if (((Group) o).hasOutput())
474 			hasOutput = true;
475 		    numSubgroups++;
476 		} else if (o instanceof Input)
477 		    hasInput = true;
478 		else if (o instanceof Output)
479 		    hasOutput = true;
480 	    }
481 
482 	if (complexity[LevelRating.FULL] > 0
483 		|| complexity[LevelRating.HIGH] > 0
484 		|| complexity[LevelRating.MIDDLE] > 3)
485 	    this.complexity = LevelRating.full;
486 	else if (complexity[LevelRating.LOW] > 3
487 		|| complexity[LevelRating.MIDDLE] > 0)
488 	    this.complexity = LevelRating.high;
489 	else if (complexity[LevelRating.NONE] > 3
490 		|| complexity[LevelRating.LOW] > 0)
491 	    this.complexity = LevelRating.middle;
492 	else if (complexity[LevelRating.NONE] > 0)
493 	    this.complexity = LevelRating.low;
494 	else
495 	    this.complexity = LevelRating.none;
496     }
497 
498     /**
499      * @see org.universAAL.middleware.rdf.Resource#setProperty(String, Object)
500      */
501     public boolean setProperty(String propURI, Object value) {
502 	if (PROP_CHILDREN.equals(propURI)) {
503 	    if (props.containsKey(propURI))
504 		return false;
505 	    else if (value instanceof List) {
506 		for (Iterator i = ((List) value).iterator(); i.hasNext();)
507 		    if (!(i.next() instanceof FormControl))
508 			return false;
509 		props.put(propURI, value);
510 		return true;
511 	    } else if (value instanceof FormControl) {
512 		List l = new ArrayList(1);
513 		l.add(value);
514 		props.put(propURI, l);
515 		return true;
516 	    }
517 	    return false;
518 	} else
519 	    return super.setProperty(propURI, value);
520     }
521 
522     boolean setValue(String[] pp, Object value,
523 	    MergedRestriction valueRestrictions) {
524 	Object parent = props.get(PROP_PARENT_CONTROL);
525 	if (parent instanceof Group)
526 	    return ((Group) parent).setValue(pp, value, valueRestrictions);
527 	else if (parent instanceof Form)
528 	    return ((Form) parent).setValue(pp, value, valueRestrictions);
529 	else
530 	    return false;
531     }
532 
533     /**
534      * look for a FormControl within the group with the given URI.
535      * @param formControlURI
536      * @return the {@link FormControl} or null if not found.
537      */
538     public FormControl searchFormControl(String formControlURI){
539     	FormControl[] children = getChildren();
540     	boolean found = false;
541     	int i = 0;
542     	FormControl result = null;
543     	while (!found
544     			&& i < children.length){
545     		found = children[i].getURI().equals(formControlURI);
546     		if (found){
547     			result = children[i];
548     		}
549     		else if (children[i] instanceof Group){
550     			result = ((Group)children[i]).searchFormControl(formControlURI);
551     			found = (result != null);
552     		}
553     		i++;
554     	}
555     	return result;
556     }
557 }