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