Coverage Report - org.kuali.rice.krad.uif.modifier.CompareFieldCreateModifier
 
Classes in this File Line Coverage Branch Coverage Complexity
CompareFieldCreateModifier
0%
0/94
0%
0/50
2.733
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.krad.uif.modifier;
 17  
 
 18  
 import org.apache.commons.lang.StringUtils;
 19  
 import org.apache.log4j.Logger;
 20  
 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
 21  
 import org.kuali.rice.krad.uif.UifConstants;
 22  
 import org.kuali.rice.krad.uif.UifPropertyPaths;
 23  
 import org.kuali.rice.krad.uif.container.Group;
 24  
 import org.kuali.rice.krad.uif.field.DataField;
 25  
 import org.kuali.rice.krad.uif.view.View;
 26  
 import org.kuali.rice.krad.uif.component.Component;
 27  
 import org.kuali.rice.krad.uif.field.HeaderField;
 28  
 import org.kuali.rice.krad.uif.util.ComponentUtils;
 29  
 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
 30  
 
 31  
 import java.util.ArrayList;
 32  
 import java.util.HashMap;
 33  
 import java.util.HashSet;
 34  
 import java.util.List;
 35  
 import java.util.Map;
 36  
 import java.util.Set;
 37  
 
 38  
 /**
 39  
  * Generates <code>Field</code> instances to produce a comparison view among
 40  
  * objects of the same type
 41  
  *
 42  
  * <p>
 43  
  * Modifier is initialized with a List of <code>ComparableInfo</code> instances.
 44  
  * For each comparable info, a copy of the configured group field is made and
 45  
  * adjusted to the binding object path for the comparable. The comparison fields
 46  
  * are ordered based on the configured order property of the comparable. In
 47  
  * addition, a <code>HeaderField<code> can be generated to label each group
 48  
  * of comparison fields.
 49  
  * </p>
 50  
  *
 51  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 52  
  */
 53  
 public class CompareFieldCreateModifier extends ComponentModifierBase {
 54  0
     private static final Logger LOG = Logger.getLogger(CompareFieldCreateModifier.class);
 55  
 
 56  
     private static final long serialVersionUID = -6285531580512330188L;
 57  
 
 58  
     private int defaultOrderSequence;
 59  
     private boolean generateCompareHeaders;
 60  
 
 61  
     private HeaderField headerFieldPrototype;
 62  
     private List<ComparableInfo> comparables;
 63  
 
 64  0
     public CompareFieldCreateModifier() {
 65  0
         defaultOrderSequence = 1;
 66  0
         generateCompareHeaders = true;
 67  
 
 68  0
         comparables = new ArrayList<ComparableInfo>();
 69  0
     }
 70  
 
 71  
     /**
 72  
      * Calls <code>ViewHelperService</code> to initialize the header field prototype
 73  
      *
 74  
      * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#performInitialization(org.kuali.rice.krad.uif.view.View,
 75  
      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
 76  
      */
 77  
     @Override
 78  
     public void performInitialization(View view, Object model, Component component) {
 79  0
         super.performInitialization(view, model, component);
 80  
 
 81  0
         if (headerFieldPrototype != null) {
 82  0
             view.getViewHelperService().performComponentInitialization(view, model, headerFieldPrototype);
 83  
         }
 84  0
     }
 85  
 
 86  
     /**
 87  
      * Generates the comparison fields
 88  
      *
 89  
      * <p>
 90  
      * First the configured List of <code>ComparableInfo</code> instances are
 91  
      * sorted based on their order property. Then if generateCompareHeaders is
 92  
      * set to true, a <code>HeaderField</code> is created for each comparable
 93  
      * using the headerFieldPrototype and the headerText given by the
 94  
      * comparable. Finally for each field configured on the <code>Group</code>,
 95  
      * a corresponding comparison field is generated for each comparable and
 96  
      * adjusted to the binding object path given by the comparable in addition
 97  
      * to suffixing the id and setting the readOnly property
 98  
      * </p>
 99  
      *
 100  
      * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#performModification(org.kuali.rice.krad.uif.view.View,
 101  
      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
 102  
      */
 103  
     @SuppressWarnings("unchecked")
 104  
     @Override
 105  
     public void performModification(View view, Object model, Component component) {
 106  0
         if ((component != null) && !(component instanceof Group)) {
 107  0
             throw new IllegalArgumentException(
 108  
                     "Compare field initializer only support Group components, found type: " + component.getClass());
 109  
         }
 110  
 
 111  0
         if (component == null) {
 112  0
             return;
 113  
         }
 114  
 
 115  
         // list to hold the generated compare items
 116  0
         List<Component> comparisonItems = new ArrayList<Component>();
 117  
 
 118  
         // sort comparables by their order property
 119  0
         List<ComparableInfo> groupComparables = (List<ComparableInfo>) ComponentUtils.sort(comparables,
 120  
                 defaultOrderSequence);
 121  
 
 122  
         // evaluate expressions on comparables
 123  0
         Map<String, Object> context = new HashMap<String, Object>();
 124  0
         context.putAll(view.getContext());
 125  0
         context.put(UifConstants.ContextVariableNames.COMPONENT, component);
 126  
 
 127  0
         for (ComparableInfo comparable : groupComparables) {
 128  0
             KRADServiceLocatorWeb.getExpressionEvaluatorService().evaluateObjectExpressions(comparable, model,
 129  
                     context);
 130  
         }
 131  
 
 132  
         // generate compare header
 133  0
         if (isGenerateCompareHeaders()) {
 134  0
             for (ComparableInfo comparable : groupComparables) {
 135  0
                 HeaderField compareHeaderField = ComponentUtils.copy(headerFieldPrototype, comparable.getIdSuffix());
 136  0
                 compareHeaderField.setHeaderText(comparable.getHeaderText());
 137  
 
 138  0
                 comparisonItems.add(compareHeaderField);
 139  0
             }
 140  
         }
 141  
 
 142  
         // find the comparable to use for comparing value changes (if
 143  
         // configured)
 144  0
         boolean performValueChangeComparison = false;
 145  0
         String compareValueObjectBindingPath = null;
 146  0
         for (ComparableInfo comparable : groupComparables) {
 147  0
             if (comparable.isCompareToForValueChange()) {
 148  0
                 performValueChangeComparison = true;
 149  0
                 compareValueObjectBindingPath = comparable.getBindingObjectPath();
 150  
             }
 151  
         }
 152  
 
 153  
         // generate the compare items from the configured group
 154  0
         Group group = (Group) component;
 155  0
         for (Component item : group.getItems()) {
 156  0
             int defaultSuffix = 0;
 157  0
             for (ComparableInfo comparable : groupComparables) {
 158  0
                 String idSuffix = comparable.getIdSuffix();
 159  0
                 if (StringUtils.isBlank(idSuffix)) {
 160  0
                     idSuffix = UifConstants.IdSuffixes.COMPARE + defaultSuffix;
 161  
                 }
 162  
 
 163  0
                 Component compareItem = ComponentUtils.copy(item, idSuffix);
 164  
 
 165  0
                 ComponentUtils.setComponentPropertyDeep(compareItem, UifPropertyPaths.BIND_OBJECT_PATH,
 166  
                         comparable.getBindingObjectPath());
 167  0
                 if (comparable.isReadOnly()) {
 168  0
                     compareItem.setReadOnly(true);
 169  0
                     if (compareItem.getPropertyExpressions().containsKey("readOnly")) {
 170  0
                         compareItem.getPropertyExpressions().remove("readOnly");
 171  
                     }
 172  
                 }
 173  
 
 174  
                 // do value comparison
 175  0
                 if (performValueChangeComparison && comparable.isHighlightValueChange() && !comparable
 176  
                         .isCompareToForValueChange()) {
 177  0
                     performValueComparison(group, compareItem, model, compareValueObjectBindingPath);
 178  
                 }
 179  
 
 180  0
                 comparisonItems.add(compareItem);
 181  0
                 defaultSuffix++;
 182  0
             }
 183  0
         }
 184  
 
 185  
         // update the group's list of components
 186  0
         group.setItems(comparisonItems);
 187  0
     }
 188  
 
 189  
     /**
 190  
      * For each attribute field in the compare item, retrieves the field value and compares against the value for the
 191  
      * main comparable. If the value is different, adds script to the field on ready event to add the change icon to
 192  
      * the field and the containing group header
 193  
      *
 194  
      * @param group - group that contains the item and whose header will be highlighted for changes
 195  
      * @param compareItem - the compare item being generated and to pull attribute fields from
 196  
      * @param model - object containing the data
 197  
      * @param compareValueObjectBindingPath - object path for the comparison item
 198  
      */
 199  
     protected void performValueComparison(Group group, Component compareItem, Object model,
 200  
             String compareValueObjectBindingPath) {
 201  
         // get any attribute fields for the item so we can compare the values
 202  0
         List<DataField> itemFields = ComponentUtils.getComponentsOfTypeDeep(compareItem, DataField.class);
 203  0
         for (DataField field : itemFields) {
 204  0
             String fieldBindingPath = field.getBindingInfo().getBindingPath();
 205  0
             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, fieldBindingPath);
 206  
 
 207  0
             String compareBindingPath = StringUtils.replaceOnce(fieldBindingPath,
 208  
                     field.getBindingInfo().getBindingObjectPath(), compareValueObjectBindingPath);
 209  0
             Object compareValue = ObjectPropertyUtils.getPropertyValue(model, compareBindingPath);
 210  
 
 211  0
             boolean valueChanged = false;
 212  0
             if (!((fieldValue == null) && (compareValue == null))) {
 213  
                 // if one is null then value changed
 214  0
                 if ((fieldValue == null) || (compareValue == null)) {
 215  0
                     valueChanged = true;
 216  
                 } else {
 217  
                     // both not null, compare values
 218  0
                     valueChanged = !fieldValue.equals(compareValue);
 219  
                 }
 220  
             }
 221  
 
 222  
             // add script to show change icon
 223  0
             if (valueChanged) {
 224  0
                 String onReadyScript = "showChangeIcon('" + field.getId() + "');";
 225  
 
 226  
                 // add icon to group header
 227  0
                 Component headerField = group.getHeader();
 228  0
                 onReadyScript += "showChangeIconOnHeader('" + headerField.getId() + "');";
 229  
 
 230  0
                 field.setOnDocumentReadyScript(onReadyScript);
 231  
             }
 232  
 
 233  
             // TODO: add script for value changed?
 234  0
         }
 235  0
     }
 236  
 
 237  
     /**
 238  
      * Generates an id suffix for the comparable item
 239  
      *
 240  
      * <p>
 241  
      * If the idSuffix to use if configured on the <code>ComparableInfo</code>
 242  
      * it will be used, else the given integer index will be used with an
 243  
      * underscore
 244  
      * </p>
 245  
      *
 246  
      * @param comparable - comparable info to check for id suffix
 247  
      * @param index - sequence integer
 248  
      * @return String id suffix
 249  
      * @see org.kuali.rice.krad.uif.modifier.ComparableInfo.getIdSuffix()
 250  
      */
 251  
     protected String getIdSuffix(ComparableInfo comparable, int index) {
 252  0
         String idSuffix = comparable.getIdSuffix();
 253  0
         if (StringUtils.isBlank(idSuffix)) {
 254  0
             idSuffix = "_" + index;
 255  
         }
 256  
 
 257  0
         return idSuffix;
 258  
     }
 259  
 
 260  
     /**
 261  
      * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#getSupportedComponents()
 262  
      */
 263  
     @Override
 264  
     public Set<Class<? extends Component>> getSupportedComponents() {
 265  0
         Set<Class<? extends Component>> components = new HashSet<Class<? extends Component>>();
 266  0
         components.add(Group.class);
 267  
 
 268  0
         return components;
 269  
     }
 270  
 
 271  
     /**
 272  
      * @see org.kuali.rice.krad.uif.modifier.ComponentModifierBase#getComponentPrototypes()
 273  
      */
 274  
     public List<Component> getComponentPrototypes() {
 275  0
         List<Component> components = new ArrayList<Component>();
 276  
 
 277  0
         components.add(headerFieldPrototype);
 278  
 
 279  0
         return components;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Indicates the starting integer sequence value to use for
 284  
      * <code>ComparableInfo</code> instances that do not have the order property
 285  
      * set
 286  
      *
 287  
      * @return int default sequence starting value
 288  
      */
 289  
     public int getDefaultOrderSequence() {
 290  0
         return this.defaultOrderSequence;
 291  
     }
 292  
 
 293  
     /**
 294  
      * Setter for the default sequence starting value
 295  
      *
 296  
      * @param defaultOrderSequence
 297  
      */
 298  
     public void setDefaultOrderSequence(int defaultOrderSequence) {
 299  0
         this.defaultOrderSequence = defaultOrderSequence;
 300  0
     }
 301  
 
 302  
     /**
 303  
      * Indicates whether a <code>HeaderField</code> should be created for each
 304  
      * group of comparison fields
 305  
      *
 306  
      * <p>
 307  
      * If set to true, for each group of comparison fields a header field will
 308  
      * be created using the headerFieldPrototype configured on the modifier with
 309  
      * the headerText property of the comparable
 310  
      * </p>
 311  
      *
 312  
      * @return boolean true if the headers should be created, false if no
 313  
      *         headers should be created
 314  
      */
 315  
     public boolean isGenerateCompareHeaders() {
 316  0
         return this.generateCompareHeaders;
 317  
     }
 318  
 
 319  
     /**
 320  
      * Setter for the generate comparison headers indicator
 321  
      *
 322  
      * @param generateCompareHeaders
 323  
      */
 324  
     public void setGenerateCompareHeaders(boolean generateCompareHeaders) {
 325  0
         this.generateCompareHeaders = generateCompareHeaders;
 326  0
     }
 327  
 
 328  
     /**
 329  
      * Prototype instance to use for creating the <code>HeaderField</code> for
 330  
      * each group of comparison fields (if generateCompareHeaders is true)
 331  
      *
 332  
      * @return HeaderField header field prototype
 333  
      */
 334  
     public HeaderField getHeaderFieldPrototype() {
 335  0
         return this.headerFieldPrototype;
 336  
     }
 337  
 
 338  
     /**
 339  
      * Setter for the header field prototype
 340  
      *
 341  
      * @param headerFieldPrototype
 342  
      */
 343  
     public void setHeaderFieldPrototype(HeaderField headerFieldPrototype) {
 344  0
         this.headerFieldPrototype = headerFieldPrototype;
 345  0
     }
 346  
 
 347  
     /**
 348  
      * List of <code>ComparableInfo</code> instances the compare fields should
 349  
      * be generated for
 350  
      *
 351  
      * <p>
 352  
      * For each comparable, a copy of the fields configured for the
 353  
      * <code>Group</code> will be created for the comparison view
 354  
      * </p>
 355  
      *
 356  
      * @return List<ComparableInfo> comparables to generate fields for
 357  
      */
 358  
     public List<ComparableInfo> getComparables() {
 359  0
         return this.comparables;
 360  
     }
 361  
 
 362  
     /**
 363  
      * Setter for the list of comparable info instances
 364  
      *
 365  
      * @param comparables
 366  
      */
 367  
     public void setComparables(List<ComparableInfo> comparables) {
 368  0
         this.comparables = comparables;
 369  0
     }
 370  
 
 371  
 }