1 /* 2 * Copyright (c) 2011, 2017, 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 * <p> 56 * In this example, {@code Person} is the class type of the {@code TableView} 57 * {@link TableView#itemsProperty() items} list. 58 * {@code PropertyValueFactory} uses the constructor argument, 59 * {@code "firstName"}, to assume that {@code Person} has a method 60 * {@code firstNameProperty} with no formal parameters and a return type of 61 * {@code ObservableValue<String>}. 62 * </p> 63 * <p> 64 * If such a method exists, then it is invoked, and additionally assumed 65 * to return an instance of {@code Property<String>}. The return value is used 66 * to populate the {@link TableCell}. In addition, the {@code TableView} adds 67 * an observer to the return value, such that any changes fired will be observed 68 * by the {@code TableView}, resulting in the cell immediately updating. 69 * </p> 70 * <p> 71 * If no such method exists, then {@code PropertyValueFactory} 72 * assumes that {@code Person} has a method {@code getFirstName} or 73 * {@code isFirstName} with no formal parameters and a return type of 74 * {@code String}. If such a method exists, then it is invoked, and its return 75 * value is wrapped in a {@link ReadOnlyObjectWrapper} 76 * and returned to the {@code TableCell}. In this situation, 77 * the {@code TableCell} will not be able to observe changes to the property, 78 * unlike in the first approach above. 79 * </p> 80 * <p> 81 * The class {@code Person} must be declared public. If that class is in a named 82 * module, then the module must {@link Module#isOpen(String,Module) open} 83 * the containing package to at least the {@code javafx.base} module 84 * (or {@link Module#isExported(String) export} the containing package 85 * unconditionally). 86 * Otherwise the {@link #call call(TableColumn.CellDataFeatures)} method 87 * will log a warning and return {@code null}. 88 * </p> 89 * <p>For reference (and as noted in the TableColumn 90 * {@link TableColumn#cellValueFactory cell value factory} documentation), the 91 * long form of the code above would be the following: 92 * </p> 93 * 94 * <pre><code> 95 * TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name"); 96 * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() { 97 * public ObservableValue<String> call(CellDataFeatures<Person, String> p) { 98 * // p.getValue() returns the Person instance for a particular TableView row 99 * return p.getValue().firstNameProperty(); 100 * } 101 * }); 102 * } 103 * </code></pre> 104 * 105 * @see TableColumn 106 * @see TableView 107 * @see TableCell 108 * @see TreeItemPropertyValueFactory 109 * @see MapValueFactory 110 * @param <S> The type of the class contained within the TableView.items list. 111 * @param <T> The type of the class contained within the TableColumn cells. 112 * @since JavaFX 2.0 113 */ 114 public class PropertyValueFactory<S,T> implements Callback<CellDataFeatures<S,T>, ObservableValue<T>> { 115 116 private final String property; 117 118 private Class<?> columnClass; 119 private String previousProperty; 120 private PropertyReference<T> propertyRef; 121 122 /** 123 * Creates a default PropertyValueFactory to extract the value from a given 124 * TableView row item reflectively, using the given property name. 125 * 126 * @param property The name of the property with which to attempt to 127 * reflectively extract a corresponding value for in a given object. 128 */ 129 public PropertyValueFactory(@NamedArg("property") String property) { 130 this.property = property; 131 } 132 133 /** {@inheritDoc} */ 134 @Override public ObservableValue<T> call(CellDataFeatures<S,T> param) { 135 return getCellDataReflectively(param.getValue()); 136 } 137 138 /** 139 * Returns the property name provided in the constructor. 140 * @return the property name provided in the constructor 141 */ 142 public final String getProperty() { return property; } 143 144 private ObservableValue<T> getCellDataReflectively(S rowData) { 145 if (getProperty() == null || getProperty().isEmpty() || rowData == null) return null; 146 147 try { 148 // we attempt to cache the property reference here, as otherwise 149 // performance suffers when working in large data models. For 150 // a bit of reference, refer to RT-13937. 151 if (columnClass == null || previousProperty == null || 152 ! columnClass.equals(rowData.getClass()) || 153 ! previousProperty.equals(getProperty())) { 154 155 // create a new PropertyReference 156 this.columnClass = rowData.getClass(); 157 this.previousProperty = getProperty(); 158 this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty()); 159 } 160 161 if (propertyRef != null) { 162 if (propertyRef.hasProperty()) { 163 return propertyRef.getProperty(rowData); 164 } else { 165 T value = propertyRef.get(rowData); 166 return new ReadOnlyObjectWrapper<T>(value); 167 } 168 } 169 } catch (RuntimeException e) { 170 // log the warning and move on 171 final PlatformLogger logger = Logging.getControlsLogger(); 172 if (logger.isLoggable(Level.WARNING)) { 173 logger.warning("Can not retrieve property '" + getProperty() + 174 "' in PropertyValueFactory: " + this + 175 " with provided class type: " + rowData.getClass(), e); 176 } 177 propertyRef = null; 178 } 179 180 return null; 181 } 182 }