Coverage Report - javax.faces.component._SharedRendererUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
_SharedRendererUtils
49%
64/130
42%
35/82
10.143
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package javax.faces.component;
 20  
 
 21  
 import java.lang.reflect.Array;
 22  
 import java.lang.reflect.Method;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Arrays;
 25  
 import java.util.Collection;
 26  
 import java.util.HashSet;
 27  
 import java.util.Iterator;
 28  
 import java.util.LinkedList;
 29  
 import java.util.Queue;
 30  
 import java.util.Set;
 31  
 import java.util.SortedSet;
 32  
 import java.util.TreeSet;
 33  
 
 34  
 import javax.el.ValueExpression;
 35  
 import javax.faces.FacesException;
 36  
 import javax.faces.context.FacesContext;
 37  
 import javax.faces.convert.Converter;
 38  
 import javax.faces.convert.ConverterException;
 39  
 import javax.faces.model.SelectItem;
 40  
 import javax.faces.model.SelectItemGroup;
 41  
 
 42  
 /**
 43  
  * The util methods in this class are shared between the javax.faces.component package and the
 44  
  * org.apache.myfaces.renderkit package. Please note: Any changes here must also apply to the class in the other
 45  
  * package!
 46  
  */
 47  0
 class _SharedRendererUtils
 48  
 {
 49  
     static final String COLLECTION_TYPE_KEY = "collectionType";
 50  
     static final String VALUE_TYPE_KEY = "valueType";
 51  
 
 52  
     static Converter findUIOutputConverter(FacesContext facesContext,
 53  
             UIOutput component)
 54  
     {
 55  
         // Attention!
 56  
         // This code is duplicated in jsfapi component package.
 57  
         // If you change something here please do the same in the other class!
 58  
 
 59  16
         Converter converter = component.getConverter();
 60  16
         if (converter != null)
 61  
         {
 62  2
             return converter;
 63  
         }
 64  
 
 65  
         //Try to find out by value expression
 66  14
         ValueExpression expression = component.getValueExpression("value");
 67  14
         if (expression == null)
 68  
         {
 69  14
             return null;
 70  
         }
 71  
 
 72  0
         Class<?> valueType = expression.getType(facesContext.getELContext());
 73  0
         if (valueType == null)
 74  
         {
 75  0
             return null;
 76  
         }
 77  
 
 78  0
         if (Object.class.equals(valueType))
 79  
         {
 80  0
             return null; //There is no converter for Object class
 81  
         }
 82  
 
 83  
         try
 84  
         {
 85  0
             return facesContext.getApplication().createConverter(valueType);
 86  
         }
 87  0
         catch (FacesException e)
 88  
         {
 89  0
             log(facesContext, "No Converter for type " + valueType.getName()
 90  
                     + " found", e);
 91  0
             return null;
 92  
         }
 93  
     }
 94  
 
 95  
     static Object getConvertedUISelectManyValue(FacesContext facesContext, UISelectMany component,
 96  
             String[] submittedValue) throws ConverterException
 97  
     {
 98  16
         return  getConvertedUISelectManyValue(facesContext, component,
 99  
             submittedValue, false);
 100  
     }
 101  
 
 102  
     /**
 103  
      * Gets the converted value of a UISelectMany component.
 104  
      * If the considerValueType is true, this method will also consider the
 105  
      * valueType attribute of Tomahawk UISelectMany components.
 106  
      * 
 107  
      * @param facesContext
 108  
      * @param component
 109  
      * @param submittedValue
 110  
      * @param considerValueType
 111  
      * @return
 112  
      * @throws ConverterException
 113  
      */
 114  
     static Object getConvertedUISelectManyValue(FacesContext facesContext, UISelectMany component,  
 115  
             String[] submittedValue, boolean considerValueType) throws ConverterException
 116  
     {
 117  
         // Attention!
 118  
         // This code is duplicated in shared renderkit package (except for considerValueType).
 119  
         // If you change something here please do the same in the other class!
 120  
 
 121  16
         if (submittedValue == null)
 122  
         {
 123  0
             throw new NullPointerException("submittedValue");
 124  
         }
 125  
 
 126  16
         ValueExpression expression = component.getValueExpression("value");
 127  16
         Object targetForConvertedValues = null;
 128  
         
 129  
         // if the component has an attached converter, use it
 130  16
         Converter converter = component.getConverter();
 131  
         // at this point the valueType attribute is handled in shared.
 132  16
         if (converter == null && considerValueType)
 133  
         {
 134  
             // try to get a converter from the valueType attribute
 135  0
             converter = getValueTypeConverter(facesContext, component);
 136  
         }
 137  
         
 138  16
         if (expression != null)
 139  
         {
 140  14
             Class<?> modelType = expression
 141  
                     .getType(facesContext.getELContext());
 142  14
             if (modelType == null)
 143  
             {
 144  
                 // FIXME temporal workaround for MYFACES-2552
 145  0
                 return submittedValue;
 146  
             }
 147  14
             else if (modelType.isArray())
 148  
             {
 149  
                 // the target should be an array
 150  8
                 Class<?> componentType = modelType.getComponentType();
 151  
                 // check for optimization if the target is
 152  
                 // a string array --> no conversion needed
 153  8
                 if (String.class.equals(componentType))
 154  
                 {
 155  0
                     return submittedValue;
 156  
                 }
 157  8
                 if (converter == null)
 158  
                 {
 159  
                     // the compononent does not have an attached converter
 160  
                     // --> try to get a registered-by-class converter
 161  6
                     converter = facesContext.getApplication().createConverter(
 162  
                             componentType);
 163  
 
 164  6
                     if (converter == null && !Object.class.equals(componentType))
 165  
                     {
 166  
                         // could not obtain a Converter
 167  
                         // --> check if we maybe do not really have to convert
 168  
 
 169  
                         // target is not an Object array
 170  
                         // and not a String array (checked some lines above)
 171  
                         // and we do not have a Converter
 172  2
                         throw new ConverterException(
 173  
                                 "Could not obtain a Converter for "
 174  
                                         + componentType.getName());
 175  
                     }
 176  
                 }
 177  
                 // instantiate the array
 178  6
                 targetForConvertedValues = Array.newInstance(componentType,
 179  
                         submittedValue.length);
 180  6
             }
 181  6
             else if (Collection.class.isAssignableFrom(modelType) || Object.class.equals(modelType))
 182  
             {
 183  6
                 if (converter == null)
 184  
                 {
 185  
                     // try to get the by-type-converter from the type of the SelectItems
 186  0
                     _SelectItemsIterator iterator = new _SelectItemsIterator(component, facesContext);
 187  0
                     converter = getSelectItemsValueConverter(iterator, facesContext);
 188  
                 }
 189  
 
 190  6
                 Object collectionTypeAttr = component.getAttributes().get(
 191  
                         COLLECTION_TYPE_KEY);
 192  6
                 if (collectionTypeAttr != null)
 193  
                 {
 194  4
                     Class<?> collectionType = getClassFromAttribute(facesContext, collectionTypeAttr);
 195  4
                     if (collectionType == null)
 196  
                     {
 197  0
                         throw new FacesException(
 198  
                                 "The attribute "
 199  
                                         + COLLECTION_TYPE_KEY
 200  
                                         + " of component "
 201  
                                         + component.getClientId(facesContext)
 202  
                                         + " does not evaluate to a "
 203  
                                         + "String, a Class object or a ValueExpression pointing "
 204  
                                         + "to a String or a Class object.");
 205  
                     }
 206  
                     // now we have a collectionType --> but is it really some kind of Collection
 207  4
                     if (!Collection.class.isAssignableFrom(collectionType))
 208  
                     {
 209  0
                         throw new FacesException("The attribute "
 210  
                                 + COLLECTION_TYPE_KEY + " of component "
 211  
                                 + component.getClientId(facesContext)
 212  
                                 + " does not point to a valid type of Collection.");
 213  
                     }
 214  
                     // now we have a real collectionType --> try to instantiate it
 215  
                     try
 216  
                     {
 217  4
                         targetForConvertedValues = collectionType.newInstance();
 218  
                     }
 219  2
                     catch (Exception e)
 220  
                     {
 221  2
                         throw new FacesException("The Collection "
 222  
                                 + collectionType.getName()
 223  
                                 + "can not be instantiated.", e);
 224  2
                     }
 225  2
                 }
 226  2
                 else if (Collection.class.isAssignableFrom(modelType))
 227  
                 {
 228  
                     // component.getValue() will implement Collection at this point
 229  2
                     Collection<?> componentValue = (Collection<?>) component
 230  
                             .getValue();
 231  
                     // can we clone the Collection
 232  2
                     if (componentValue instanceof Cloneable)
 233  
                     {
 234  
                         // clone method of Object is protected --> use reflection
 235  
                         try
 236  
                         {
 237  2
                             Method cloneMethod = componentValue.getClass()
 238  
                                     .getMethod("clone");
 239  2
                             Collection<?> clone = (Collection<?>) cloneMethod
 240  
                                     .invoke(componentValue);
 241  2
                             clone.clear();
 242  2
                             targetForConvertedValues = clone;
 243  
                         }
 244  0
                         catch (Exception e)
 245  
                         {
 246  0
                             log(facesContext, "Could not clone "
 247  
                                     + componentValue.getClass().getName(), e);
 248  2
                         }
 249  
                     }
 250  
 
 251  
                     // if clone did not work
 252  2
                     if (targetForConvertedValues == null)
 253  
                     {
 254  
                         // try to create the (concrete) collection from modelType 
 255  
                         // or with the class object of componentValue (if any)
 256  
                         try
 257  
                         {
 258  0
                             targetForConvertedValues = (componentValue != null 
 259  
                                     ? componentValue.getClass()
 260  
                                     : modelType).newInstance();
 261  
                         }
 262  0
                         catch (Exception e)
 263  
                         {
 264  
                             // this did not work either
 265  
                             // use the standard concrete type
 266  0
                             if (SortedSet.class.isAssignableFrom(modelType))
 267  
                             {
 268  0
                                 targetForConvertedValues = new TreeSet();
 269  
                             }
 270  0
                             else if (Queue.class.isAssignableFrom(modelType))
 271  
                             {
 272  0
                                 targetForConvertedValues = new LinkedList();
 273  
                             }
 274  0
                             else if (Set.class.isAssignableFrom(modelType))
 275  
                             {
 276  0
                                 targetForConvertedValues = new HashSet(
 277  
                                         submittedValue.length);
 278  
                             }
 279  
                             else
 280  
                             {
 281  0
                                 targetForConvertedValues = new ArrayList(
 282  
                                         submittedValue.length);
 283  
                             }
 284  0
                         }
 285  
                     }
 286  2
                 }
 287  
                 else /* if (Object.class.equals(modelType)) */
 288  
                 {
 289  
                     // a modelType of Object is also permitted, in order to support
 290  
                     // managed bean properties of type Object
 291  
                     
 292  
                     // optimization: if we don't have a converter, we can return the submittedValue
 293  0
                     if (converter == null)
 294  
                     {
 295  0
                         return submittedValue;
 296  
                     }
 297  
                     
 298  0
                     targetForConvertedValues = new Object[submittedValue.length];
 299  
                 }
 300  4
             }
 301  
             else
 302  
             {
 303  
                 // the expression does neither point to an array nor to a collection
 304  0
                 throw new ConverterException(
 305  
                         "ValueExpression for UISelectMany must be of type Collection or Array.");
 306  
             }
 307  10
         }
 308  
         else
 309  
         {
 310  2
             targetForConvertedValues = new Object[submittedValue.length];
 311  
         }
 312  
 
 313  
         // convert the values with the selected converter (if any)
 314  
         // and store them in targetForConvertedValues
 315  12
         boolean isArray = (targetForConvertedValues.getClass().isArray());
 316  36
         for (int i = 0; i < submittedValue.length; i++)
 317  
         {
 318  
             // get the value
 319  
             Object value;
 320  24
             if (converter != null)
 321  
             {
 322  20
                 value = converter.getAsObject(facesContext, component,
 323  
                         submittedValue[i]);
 324  
             }
 325  
             else
 326  
             {
 327  4
                 value = submittedValue[i];
 328  
             }
 329  
             // store it in targetForConvertedValues
 330  24
             if (isArray)
 331  
             {
 332  16
                 Array.set(targetForConvertedValues, i, value);
 333  
             }
 334  
             else
 335  
             {
 336  8
                 ((Collection) targetForConvertedValues).add(value);
 337  
             }
 338  
         }
 339  
 
 340  12
         return targetForConvertedValues;
 341  
     }
 342  
     
 343  
     /**
 344  
      * Gets a Class object from a given component attribute. The attribute can
 345  
      * be a ValueExpression (that evaluates to a String or a Class) or a 
 346  
      * String (that is a fully qualified Java class name) or a Class object.
 347  
      * 
 348  
      * @param facesContext
 349  
      * @param attribute
 350  
      * @return
 351  
      * @throws FacesException if the value is a String and the represented
 352  
      *                        class cannot be found
 353  
      */
 354  
     static Class<?> getClassFromAttribute(FacesContext facesContext,
 355  
             Object attribute) throws FacesException
 356  
     {
 357  
         // Attention!
 358  
         // This code is duplicated in shared renderkit package.
 359  
         // If you change something here please do the same in the other class!
 360  
         
 361  4
         Class<?> type = null;
 362  
         
 363  
         // if there is a value, it must be a ...
 364  
         // ... a ValueExpression that evaluates to a String or a Class
 365  4
         if (attribute instanceof ValueExpression)
 366  
         {
 367  
             // get the value of the ValueExpression
 368  0
             attribute = ((ValueExpression) attribute)
 369  
                     .getValue(facesContext.getELContext());
 370  
         }
 371  
         // ... String that is a fully qualified Java class name
 372  4
         if (attribute instanceof String)
 373  
         {
 374  
             try
 375  
             {
 376  4
                 type = Class.forName((String) attribute);
 377  
             }
 378  0
             catch (ClassNotFoundException cnfe)
 379  
             {
 380  0
                 throw new FacesException(
 381  
                         "Unable to find class "
 382  
                                 + attribute
 383  
                                 + " on the classpath.", cnfe);
 384  4
             }
 385  
 
 386  
         }
 387  
         // ... a Class object
 388  0
         else if (attribute instanceof Class)
 389  
         {
 390  0
             type = (Class<?>) attribute;
 391  
         }
 392  
         
 393  4
         return type;
 394  
     }
 395  
     
 396  
     /**
 397  
      * Uses the valueType attribute of the given UISelectMany component to
 398  
      * get a by-type converter.
 399  
      * 
 400  
      * @param facesContext
 401  
      * @param component
 402  
      * @return
 403  
      */
 404  
     static Converter getValueTypeConverter(FacesContext facesContext, UISelectMany component)
 405  
     {
 406  0
         Converter converter = null;
 407  
         
 408  0
         Object valueTypeAttr = component.getAttributes().get(VALUE_TYPE_KEY);
 409  0
         if (valueTypeAttr != null)
 410  
         {
 411  
             // treat the valueType attribute exactly like the collectionType attribute
 412  0
             Class<?> valueType = getClassFromAttribute(facesContext, valueTypeAttr);
 413  0
             if (valueType == null)
 414  
             {
 415  0
                 throw new FacesException(
 416  
                         "The attribute "
 417  
                                 + VALUE_TYPE_KEY
 418  
                                 + " of component "
 419  
                                 + component.getClientId(facesContext)
 420  
                                 + " does not evaluate to a "
 421  
                                 + "String, a Class object or a ValueExpression pointing "
 422  
                                 + "to a String or a Class object.");
 423  
             }
 424  
             // now we have a valid valueType
 425  
             // --> try to get a registered-by-class converter
 426  0
             converter = facesContext.getApplication().createConverter(valueType);
 427  
             
 428  0
             if (converter == null)
 429  
             {
 430  0
                 facesContext.getExternalContext().log("Found attribute valueType on component " +
 431  
                         _ComponentUtils.getPathToComponent(component) +
 432  
                         ", but could not get a by-type converter for type " + 
 433  
                         valueType.getName());
 434  
             }
 435  
         }
 436  
         
 437  0
         return converter;
 438  
     }
 439  
     
 440  
     /**
 441  
      * Iterates through the SelectItems with the given Iterator and tries to obtain
 442  
      * a by-class-converter based on the Class of SelectItem.getValue().
 443  
      * @param iterator
 444  
      * @param facesContext
 445  
      * @return The first suitable Converter for the given SelectItems or null.
 446  
      */
 447  
     static Converter getSelectItemsValueConverter(Iterator<SelectItem> iterator, FacesContext facesContext)
 448  
     {
 449  
         // Attention!
 450  
         // This code is duplicated in jsfapi component package.
 451  
         // If you change something here please do the same in the other class!
 452  
         
 453  0
         Converter converter = null;
 454  0
         while (converter == null && iterator.hasNext())
 455  
         {
 456  0
             SelectItem item = iterator.next();
 457  0
             if (item instanceof SelectItemGroup)
 458  
             {
 459  0
                 Iterator<SelectItem> groupIterator = Arrays.asList(
 460  
                         ((SelectItemGroup) item).getSelectItems()).iterator();
 461  0
                 converter = getSelectItemsValueConverter(groupIterator, facesContext);
 462  0
             }
 463  
             else
 464  
             {
 465  0
                 Class<?> selectItemsType = item.getValue().getClass();
 466  
                 
 467  
                 // optimization: no conversion for String values
 468  0
                 if (String.class.equals(selectItemsType))
 469  
                 {
 470  0
                     return null;
 471  
                 }
 472  
                 
 473  
                 try
 474  
                 {
 475  0
                     converter = facesContext.getApplication().createConverter(selectItemsType);
 476  
                 }
 477  0
                 catch (FacesException e)
 478  
                 {
 479  
                     // nothing - try again
 480  0
                 }
 481  
             }
 482  0
         }
 483  0
         return converter;
 484  
     }
 485  
 
 486  
     /**
 487  
      * This method is different in the two versions of _SharedRendererUtils.
 488  
      */
 489  
     private static void log(FacesContext context, String msg, Exception e)
 490  
     {
 491  0
         context.getExternalContext().log(msg, e);
 492  0
     }
 493  
 }