Coverage Report - org.kuali.student.common.ui.client.mvc.DataModel
 
Classes in this File Line Coverage Branch Coverage Complexity
DataModel
48%
64/133
54%
23/42
1.649
DataModel$1
90%
9/10
66%
4/6
1.649
DataModel$QueryCallback
N/A
N/A
1.649
 
 1  
 /**
 2  
  * Copyright 2010 The Kuali Foundation Licensed under the
 3  
  * Educational Community License, Version 2.0 (the "License"); you may
 4  
  * not use this file except in compliance with the License. You may
 5  
  * obtain a copy of the License at
 6  
  *
 7  
  * http://www.osedu.org/licenses/ECL-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing,
 10  
  * software distributed under the License is distributed on an "AS IS"
 11  
  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 12  
  * or implied. See the License for the specific language governing
 13  
  * permissions and limitations under the License.
 14  
  */
 15  
 
 16  
 /**
 17  
  *
 18  
  */
 19  
 package org.kuali.student.common.ui.client.mvc;
 20  
 
 21  
 import java.util.Date;
 22  
 import java.util.HashMap;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 import java.util.Set;
 26  
 
 27  
 import org.kuali.student.common.assembly.data.Data;
 28  
 import org.kuali.student.common.assembly.data.Metadata;
 29  
 import org.kuali.student.common.assembly.data.ModelDefinition;
 30  
 import org.kuali.student.common.assembly.data.QueryPath;
 31  
 import org.kuali.student.common.assembly.data.Data.DataType;
 32  
 import org.kuali.student.common.assembly.data.Data.DataValue;
 33  
 import org.kuali.student.common.assembly.data.Data.Key;
 34  
 import org.kuali.student.common.assembly.data.Data.Property;
 35  
 import org.kuali.student.common.assembly.data.Data.Value;
 36  
 import org.kuali.student.common.assembly.data.HasChangeCallbacks.ChangeCallback;
 37  
 import org.kuali.student.common.assembly.data.HasChangeCallbacks.ChangeCallbackRegistration;
 38  
 import org.kuali.student.common.assembly.data.HasChangeCallbacks.ChangeType;
 39  
 import org.kuali.student.common.ui.client.configurable.mvc.FieldDescriptor;
 40  
 import org.kuali.student.common.ui.client.mvc.ModelChangeEvent.Action;
 41  
 import org.kuali.student.common.ui.client.validator.ClientDateParser;
 42  
 import org.kuali.student.common.ui.client.validator.DataModelValidator;
 43  
 import org.kuali.student.common.validation.dto.ValidationResultInfo;
 44  
 import org.kuali.student.common.validator.DateParser;
 45  
 
 46  
 import com.google.gwt.core.client.GWT;
 47  
 import com.google.gwt.event.shared.HandlerManager;
 48  
 import com.google.gwt.event.shared.HandlerRegistration;
 49  
 
 50  
 /**
 51  
  * The data model for Kuali Student.  Data is stored in a map of maps and is accessed through a QueryPath.
 52  
  * 
 53  
  * @author Kuali Student Team
 54  
  * @see QueryPath
 55  
  */
 56  
 /**
 57  
  * @author Kuali Student Team
 58  
  *
 59  
  */
 60  
 @SuppressWarnings("unchecked")
 61  10
 public class DataModel implements Model {
 62  
     public interface QueryCallback<T> {
 63  
         void onResult(QueryPath path, T result);
 64  
     }
 65  
 
 66  
     private static final long serialVersionUID = 1L;
 67  
 
 68  2
     private String modelName = "";
 69  
     private ModelDefinition definition;
 70  2
     private DataModelValidator validator = new DataModelValidator();
 71  
 
 72  2
     private HandlerManager handlers = new HandlerManager(this);
 73  
     private ChangeCallbackRegistration bridgeCallbackReg;
 74  
 
 75  
     private Data root;
 76  
 
 77  
     private String parentPath;    //Set this if DataModel's root element is nested in another data element.
 78  
 
 79  2
     public DataModel() {
 80  
         // do nothing
 81  2
     }
 82  
 
 83  0
     public DataModel(String name) {
 84  0
         this.modelName = name;
 85  0
     }
 86  
 
 87  0
     public DataModel(final ModelDefinition definition, final Data root) {
 88  0
         this.definition = definition;
 89  0
         this.root = root;
 90  0
         validator.setDateParser((DateParser) GWT.create(ClientDateParser.class));
 91  0
     }
 92  
 
 93  
     public String getModelName() {
 94  0
         return modelName;
 95  
     }
 96  
 
 97  
     public void setModelName(String modelName) {
 98  0
         this.modelName = modelName;
 99  0
     }
 100  
 
 101  
     public <T> T get(final QueryPath path) {
 102  0
         return (T) root.query(path);
 103  
     }
 104  
 
 105  
     public <T> T get(final String path) {
 106  0
         return (T) get(QueryPath.parse(path));
 107  
     }
 108  
 
 109  
     /**
 110  
      * @return the root
 111  
      */
 112  
     public Data getRoot() {
 113  1
         return root;
 114  
     }
 115  
 
 116  
     public void resetRoot() {
 117  0
         root = null;
 118  0
     }
 119  
 
 120  
     public void remove(final QueryPath path) {
 121  0
         QueryPath parent = null;
 122  0
         QueryPath leavePath = null;
 123  0
         if (path != null && path.size() >= 2) {
 124  0
             parent = path.subPath(0, path.size() - 1);
 125  0
             leavePath = path.subPath(path.size() - 1, path.size());
 126  0
             Object parentData = this.get(parent);
 127  0
             if (parentData != null && parentData instanceof Data) {
 128  0
                 ((Data) parentData).remove(
 129  
                         new Data.StringKey(leavePath.toString()));
 130  
             }
 131  
 
 132  0
         } else if (path != null) {
 133  0
             root.remove(new Data.StringKey(path.toString()));
 134  
         }
 135  0
     }
 136  
 
 137  
     /** 
 138  
      * @param path The path in the data model
 139  
      * @return A map containing the path/value pairs for all matching elements, or an empty
 140  
      * map if no matching values found.
 141  
      */
 142  
     public Map<QueryPath, Object> query(final QueryPath path) {
 143  20
         Map<QueryPath, Object> result = new HashMap<QueryPath, Object>();
 144  20
         queryRelative(root, path, result);
 145  20
         return result;
 146  
     }
 147  
 
 148  
     /** 
 149  
      * @param path The path in the data model
 150  
      * @return A map containing the path/value pairs for all matching elements, or an empty
 151  
      * map if no matching values found.
 152  
      */
 153  
     public Map<QueryPath, Object> query(final String path) {
 154  2
         return query(QueryPath.parse(path));
 155  
     }
 156  
 
 157  
     /**
 158  
      * @param branch The data branch on which to perform the query
 159  
      * @param path   The path string relative to the root of the branch
 160  
      * @param result A map containing all matching paths and the corresponding data value. For a non-wildcard path
 161  
      *               this should return a single entry in the map of the path supplied and the corresponding value. For
 162  
      *               a path containing wildcard, an entry in the map will exist for each matching path and their corresponding values.
 163  
      *               Note: A path ending in a wild card will not match the _runtimeData element.
 164  
      */
 165  
     private void queryRelative(final Data branch, final QueryPath path, Map<QueryPath, Object> result) {
 166  
         //Should this add the entire branch to the result when query path is an empty string?
 167  
 
 168  22
         Data d = branch;
 169  
 
 170  40
         for (int i = 0; i < path.size(); i++) {
 171  25
             if (d == null) {
 172  
                 // dead end
 173  2
                 break;
 174  
             }
 175  23
             final Key key = path.get(i);
 176  23
             if (key.equals(Data.WILDCARD_KEY)) {
 177  5
                 final QueryPath relative = path.subPath(i + 1, path.size());
 178  5
                 if (!relative.isEmpty()) {
 179  
                     //Handle remaining path after wildcard
 180  2
                     for (final Property p : d) {
 181  2
                         if (p.getValueType().equals(Data.class)) {
 182  2
                             queryRelative((Data) p.getValue(), relative, result);
 183  
                         }
 184  
                     }
 185  
                 } else {
 186  
                     //The wildcard is last element in path, so add all sub-elements to result
 187  
                     //The wildcard will not match a _runtimeData path
 188  3
                     Set<Key> keys = d.keySet();
 189  3
                     for (Key wildcardKey : keys) {
 190  4
                         if (!("_runtimeData".equals(wildcardKey.get()))) {
 191  4
                             QueryPath wildcardPath = path.subPath(0, path.size() - 1);
 192  4
                             wildcardPath.add(wildcardKey);
 193  4
                             result.put(wildcardPath, d.get(wildcardKey));
 194  4
                         }
 195  
                     }
 196  
                 }
 197  3
                 break; //No need to continue once we process wildcard
 198  18
             } else if (i < path.size() - 1) {
 199  7
                 d = d.get(key);
 200  
             } else {
 201  11
                 final QueryPath resultPath = d.getQueryPath();
 202  11
                 resultPath.add(key);
 203  
 
 204  11
                 Object resultValue = d.get(key);
 205  
 
 206  
                 //If query is against DataModel whose root element is child of another data object, 
 207  
                 //need to strip of the parent path so result path is relative to root of child element
 208  11
                 if (parentPath != null) {
 209  2
                     String relativePath = resultPath.toString();
 210  2
                     if (relativePath.contains("/")) {
 211  2
                         relativePath = relativePath.substring(parentPath.length());
 212  2
                         result.put(QueryPath.parse(relativePath), resultValue);
 213  
                     }else{
 214  0
                        result.put(resultPath, resultValue); 
 215  
                     }
 216  2
                 } else {
 217  9
                     result.put(resultPath, resultValue);
 218  
                 }
 219  
             }
 220  
         }
 221  22
     }
 222  
 
 223  
     public void set(final QueryPath path, final Data value) {
 224  0
         set(path, new Data.DataValue(value));
 225  0
     }
 226  
 
 227  
     public void set(final QueryPath path, final Integer value) {
 228  0
         set(path, new Data.IntegerValue(value));
 229  0
     }
 230  
 
 231  
     public void set(final QueryPath path, final String value) {
 232  5
         set(path, new Data.StringValue(value));
 233  5
     }
 234  
 
 235  
     public void set(final QueryPath path, final Long value) {
 236  0
         set(path, new Data.LongValue(value));
 237  0
     }
 238  
 
 239  
     public void set(final QueryPath path, final Short value) {
 240  0
         set(path, new Data.ShortValue(value));
 241  0
     }
 242  
 
 243  
     public void set(final QueryPath path, final Double value) {
 244  0
         set(path, new Data.DoubleValue(value));
 245  0
     }
 246  
 
 247  
     public void set(final QueryPath path, final Float value) {
 248  0
         set(path, new Data.FloatValue(value));
 249  0
     }
 250  
 
 251  
     public void set(final QueryPath path, final Boolean value) {
 252  0
         set(path, new Data.BooleanValue(value));
 253  0
     }
 254  
 
 255  
     public void set(final QueryPath path, final Date value) {
 256  0
         set(path, new Data.DateValue(value));
 257  0
     }
 258  
 
 259  
     public void set(final QueryPath path, final Value value) {
 260  5
         definition.ensurePath(root, path, value instanceof DataValue);
 261  5
         if (path.size() > 1) {
 262  3
             final QueryPath q = path.subPath(0, path.size() - 1);
 263  3
             final Data d = root.query(q);
 264  3
             d.set(path.get(path.size() - 1), value);
 265  3
         } else {
 266  2
             root.set(path.get(0), value);
 267  
         }
 268  5
     }
 269  
 
 270  
     public DataType getType(final QueryPath path) {
 271  0
         return definition.getType(path);
 272  
     }
 273  
 
 274  
     public Metadata getMetadata(final QueryPath path) {
 275  0
         return definition.getMetadata(path);
 276  
     }
 277  
 
 278  
     /**
 279  
      * Set the top level data for this DataModel
 280  
      * @param root the root to set
 281  
      */
 282  
     public void setRoot(final Data root) {
 283  3
         if (bridgeCallbackReg != null) {
 284  1
             bridgeCallbackReg.remove();
 285  
         }
 286  3
         this.root = root;
 287  3
         bridgeCallbackReg = root.addChangeCallback(new ChangeCallback() {
 288  
             @Override
 289  
             public void onChange(ChangeType type, QueryPath path) {
 290  10
                 Action action = null;
 291  10
                 if (type == ChangeType.ADD) {
 292  9
                     action = Action.ADD;
 293  1
                 } else if (type == ChangeType.REMOVE) {
 294  0
                     action = Action.REMOVE;
 295  1
                 } else if (type == ChangeType.UPDATE) {
 296  1
                     action = Action.UPDATE;
 297  
                 }
 298  10
                 handlers.fireEvent(new DataModelChangeEvent(action, DataModel.this, path));
 299  10
             }
 300  
         });
 301  3
         handlers.fireEvent(new DataModelChangeEvent(Action.RELOAD, this, new QueryPath()));
 302  3
     }
 303  
 
 304  
     @Override
 305  
     public HandlerRegistration addModelChangeHandler(ModelChangeHandler handler) {
 306  0
         return handlers.addHandler(ModelChangeEvent.TYPE, handler);
 307  
     }
 308  
 
 309  
     public ModelDefinition getDefinition() {
 310  4
         return definition;
 311  
     }
 312  
 
 313  
     public void setDefinition(ModelDefinition definition) {
 314  3
         this.definition = definition;
 315  3
     }
 316  
 
 317  
 
 318  
     public String getParentPath() {
 319  0
         return parentPath;
 320  
     }
 321  
 
 322  
 
 323  
     /**
 324  
      * If the root element for this is a child of another data object, then the parent
 325  
      * path must be set to the path where this child data object can be found.
 326  
      *
 327  
      * @param parentPath
 328  
      */
 329  
     public void setParentPath(String parentPath) {
 330  1
         this.parentPath = parentPath;
 331  1
     }
 332  
 
 333  
     /**
 334  
      * Validates this data model against its ModelDefinition/Metadata and returns the result
 335  
      * to the callback
 336  
      * @param callback
 337  
      */
 338  
     public void validate(final Callback<List<ValidationResultInfo>> callback) {
 339  0
         List<ValidationResultInfo> result = validator.validate(this);
 340  0
         callback.exec(result);
 341  0
     }
 342  
 
 343  
     /**
 344  
      * Validates this data model against the next state in its ModelDefinition and returns the result
 345  
      * to the callback
 346  
      * @param callback
 347  
      */
 348  
     public void validateNextState(final Callback<List<ValidationResultInfo>> callback) {
 349  0
         List<ValidationResultInfo> result = validator.validateNextState(this);
 350  0
         callback.exec(result);
 351  0
     }
 352  
 
 353  
     /**
 354  
      * Validates a single field
 355  
      * @param fd
 356  
      * @param callback
 357  
      */
 358  
     public void validateField(FieldDescriptor fd, final Callback<List<ValidationResultInfo>> callback) {
 359  0
         List<ValidationResultInfo> result = validator.validate(fd, this);
 360  0
         callback.exec(result);
 361  0
     }
 362  
 
 363  
     /**
 364  
      * Checks to see if data exists for the path passed in
 365  
      * @param sPath
 366  
      * @return
 367  
      */
 368  
     public boolean isValidPath(String sPath) {
 369  0
         QueryPath path = QueryPath.parse(sPath);
 370  0
         boolean result = false;
 371  0
         Data root = this.getRoot();
 372  0
         for (int i = 0; i < path.size(); i++) {
 373  0
             Data.Key key = path.get(i);
 374  0
             if (!root.containsKey(key)) {
 375  0
                 result = false;
 376  0
                 break;
 377  0
             } else if (i < path.size() - 1) {
 378  0
                 root = (Data) root.get(key);
 379  
             } else {
 380  0
                 result = true;
 381  0
                 break;
 382  
             }
 383  
         }
 384  0
         return result;
 385  
     }
 386  
 
 387  
 }