1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.control.cell; 27 28 import javafx.beans.NamedArg; 29 import javafx.beans.property.Property; 30 import javafx.beans.property.ReadOnlyObjectWrapper; 31 import javafx.beans.value.ObservableValue; 32 import javafx.scene.control.TableCell; 33 import javafx.scene.control.TableColumn; 34 import javafx.scene.control.TableColumn.CellDataFeatures; 35 import javafx.scene.control.TableView; 36 import javafx.util.Callback; 37 38 import sun.util.logging.PlatformLogger; 39 import sun.util.logging.PlatformLogger.Level; 40 import com.sun.javafx.property.PropertyReference; 41 import com.sun.javafx.scene.control.Logging; 42 43 44 /** 45 * A convenience implementation of the Callback interface, designed specifically 46 * for use within the {@link TableColumn} 47 * {@link TableColumn#cellValueFactoryProperty() cell value factory}. An example 48 * of how to use this class is: 49 * 50 * <pre><code> 51 * TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); 52 * firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName")); 53 * </code></pre> 54 * 55 * In this example, the "firstName" string is used as a reference to an assumed 56 * <code>firstNameProperty()</code> method in the <code>Person</code> class type 57 * (which is the class type of the TableView 58 * {@link TableView#itemsProperty() items} list). Additionally, this method must 59 * return a {@link Property} instance. If a method meeting these requirements 60 * is found, then the {@link TableCell} is populated with this 61 * {@literal ObservableValue<T>}. 62 * In addition, the TableView will automatically add an observer to the 63 * returned value, such that any changes fired will be observed by the TableView, 64 * resulting in the cell immediately updating. 65 * 66 * <p>If no method matching this pattern exists, there is fall-through support 67 * for attempting to call get<property>() or is<property>() (that is, 68 * <code>getFirstName()</code> or <code>isFirstName()</code> in the example 69 * above). If a method matching this pattern exists, the value returned from this method 70 * is wrapped in a {@link ReadOnlyObjectWrapper} and returned to the TableCell. 71 * However, in this situation, this means that the TableCell will not be able 72 * to observe the ObservableValue for changes (as is the case in the first 73 * approach above). 74 * 75 * <p>For reference (and as noted in the TableColumn 76 * {@link TableColumn#cellValueFactory cell value factory} documentation), the 77 * long form of the code above would be the following: 78 * 79 * <pre><code> 80 * TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); 81 * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() { 82 * public ObservableValue<String> call(CellDataFeatures<Person, String> p) { 83 * // p.getValue() returns the Person instance for a particular TableView row 84 * return p.getValue().firstNameProperty(); 85 * } 86 * }); 87 * } 88 * </code></pre> 89 * 90 * @see TableColumn 91 * @see TableView 92 * @see TableCell 93 * @see TreeItemPropertyValueFactory 94 * @see MapValueFactory 95 * @param <S> The type of the class contained within the TableView.items list. 96 * @param <T> The type of the class contained within the TableColumn cells. 97 * @since JavaFX 2.0 98 */ 99 public class PropertyValueFactory<S,T> implements Callback<CellDataFeatures<S,T>, ObservableValue<T>> { 100 101 private final String property; 102 103 private Class<?> columnClass; 104 private String previousProperty; 105 private PropertyReference<T> propertyRef; 106 107 /** 108 * Creates a default PropertyValueFactory to extract the value from a given 109 * TableView row item reflectively, using the given property name. 110 * 111 * @param property The name of the property with which to attempt to 112 * reflectively extract a corresponding value for in a given object. 113 */ 114 public PropertyValueFactory(@NamedArg("property") String property) { 115 this.property = property; 116 } 117 118 /** {@inheritDoc} */ 119 @Override public ObservableValue<T> call(CellDataFeatures<S,T> param) { 120 return getCellDataReflectively(param.getValue()); 121 } 122 123 /** 124 * Returns the property name provided in the constructor. 125 * @return the property name provided in the constructor 126 */ 127 public final String getProperty() { return property; } 128 129 private ObservableValue<T> getCellDataReflectively(S rowData) { 130 if (getProperty() == null || getProperty().isEmpty() || rowData == null) return null; 131 132 try { 133 // we attempt to cache the property reference here, as otherwise 134 // performance suffers when working in large data models. For 135 // a bit of reference, refer to RT-13937. 136 if (columnClass == null || previousProperty == null || 137 ! columnClass.equals(rowData.getClass()) || 138 ! previousProperty.equals(getProperty())) { 139 140 // create a new PropertyReference 141 this.columnClass = rowData.getClass(); 142 this.previousProperty = getProperty(); 143 this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty()); 144 } 145 146 if (propertyRef.hasProperty()) { 147 return propertyRef.getProperty(rowData); 148 } else { 149 T value = propertyRef.get(rowData); 150 return new ReadOnlyObjectWrapper<T>(value); 151 } 152 } catch (IllegalStateException e) { 153 // log the warning and move on 154 final PlatformLogger logger = Logging.getControlsLogger(); 155 if (logger.isLoggable(Level.WARNING)) { 156 logger.finest("Can not retrieve property '" + getProperty() + 157 "' in PropertyValueFactory: " + this + 158 " with provided class type: " + rowData.getClass(), e); 159 } 160 } 161 162 return null; 163 } 164 }