1 /* 2 * Copyright (c) 2012, 2018, 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.TreeItem; 33 import javafx.scene.control.TreeTableCell; 34 import javafx.scene.control.TreeTableColumn; 35 import javafx.scene.control.TreeTableColumn.CellDataFeatures; 36 import javafx.scene.control.TreeTableView; 37 import javafx.util.Callback; 38 import com.sun.javafx.property.PropertyReference; 39 import com.sun.javafx.scene.control.Logging; 40 import com.sun.javafx.logging.PlatformLogger; 41 import com.sun.javafx.logging.PlatformLogger.Level; 42 43 44 /** 45 * A convenience implementation of the Callback interface, designed specifically 46 * for use within the {@link TreeTableColumn} 47 * {@link TreeTableColumn#cellValueFactoryProperty() cell value factory}. An example 48 * of how to use this class is: 49 * 50 * <pre><code> 51 * TreeTableColumn<Person,String> firstNameCol = new TreeTableColumn<Person,String>("First Name"); 52 * firstNameCol.setCellValueFactory(new TreeItemPropertyValueFactory<Person,String>("firstName")); 53 * </code></pre> 54 * 55 * <p> 56 * In this example, {@code Person} is the class type of the {@link TreeItem} 57 * instances used in the {@link TreeTableView}. 58 * The class {@code Person} must be declared public. 59 * {@code TreeItemPropertyValueFactory} uses the constructor argument, 60 * {@code "firstName"}, to assume that {@code Person} has a public method 61 * {@code firstNameProperty} with no formal parameters and a return type of 62 * {@code ObservableValue<String>}. 63 * </p> 64 * <p> 65 * If such a method exists, then it is invoked, and additionally assumed 66 * to return an instance of {@code Property<String>}. The return value is used 67 * to populate the {@link TreeTableCell}. In addition, the {@code TreeTableView} 68 * adds an observer to the return value, such that any changes fired will be 69 * observed by the {@code TreeTableView}, resulting in the cell immediately 70 * updating. 71 * </p> 72 * <p> 73 * If no such method exists, then {@code TreeItemPropertyValueFactory} 74 * assumes that {@code Person} has a public method {@code getFirstName} or 75 * {@code isFirstName} with no formal parameters and a return type of 76 * {@code String}. If such a method exists, then it is invoked, and its return 77 * value is wrapped in a {@link ReadOnlyObjectWrapper} 78 * and returned to the {@code TreeTableCell}. In this situation, 79 * the {@code TreeTableCell} will not be able to observe changes to the property, 80 * unlike in the first approach above. 81 * </p> 82 * 83 * <p>For reference (and as noted in the TreeTableColumn 84 * {@link TreeTableColumn#cellValueFactoryProperty() cell value factory} documentation), the 85 * long form of the code above would be the following: 86 * </p> 87 * 88 * <pre><code> 89 * TreeTableColumn<Person,String> firstNameCol = new TreeTableColumn<Person,String>("First Name"); 90 * {@literal 91 * firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() { 92 * public ObservableValue<String> call(CellDataFeatures<Person, String> p) { 93 * // p.getValue() returns the TreeItem<Person> instance for a particular 94 * // TreeTableView row, and the second getValue() call returns the 95 * // Person instance contained within the TreeItem. 96 * return p.getValue().getValue().firstNameProperty(); 97 * } 98 * }); 99 * } 100 * } 101 * </code></pre> 102 * 103 * <p><b>Deploying an Application as a Module</b></p> 104 * <p> 105 * If the referenced class is in a named module, then it must be reflectively 106 * accessible to the {@code javafx.base} module. 107 * A class is reflectively accessible if the module 108 * {@link Module#isOpen(String,Module) opens} the containing package to at 109 * least the {@code javafx.base} module. 110 * Otherwise the {@link #call call(TreeTableColumn.CellDataFeatures)} method 111 * will log a warning and return {@code null}. 112 * </p> 113 * <p> 114 * For example, if the {@code Person} class is in the {@code com.foo} package 115 * in the {@code foo.app} module, the {@code module-info.java} might 116 * look like this: 117 * </p> 118 * 119 <pre>{@code module foo.app { 120 opens com.foo to javafx.base; 121 }}</pre> 122 * 123 * <p> 124 * Alternatively, a class is reflectively accessible if the module 125 * {@link Module#isExported(String) exports} the containing package 126 * unconditionally. 127 * </p> 128 * 129 * @see TreeTableColumn 130 * @see TreeTableView 131 * @see TreeTableCell 132 * @see PropertyValueFactory 133 * @see MapValueFactory 134 * @since JavaFX 8.0 135 */ 136 public class TreeItemPropertyValueFactory<S,T> implements Callback<TreeTableColumn.CellDataFeatures<S,T>, ObservableValue<T>> { 137 138 private final String property; 139 140 private Class<?> columnClass; 141 private String previousProperty; 142 private PropertyReference<T> propertyRef; 143 144 /** 145 * Creates a default PropertyValueFactory to extract the value from a given 146 * TableView row item reflectively, using the given property name. 147 * 148 * @param property The name of the property with which to attempt to 149 * reflectively extract a corresponding value for in a given object. 150 */ 151 public TreeItemPropertyValueFactory(@NamedArg("property") String property) { 152 this.property = property; 153 } 154 155 /** {@inheritDoc} */ 156 @Override public ObservableValue<T> call(CellDataFeatures<S, T> param) { 157 TreeItem<S> treeItem = param.getValue(); 158 return getCellDataReflectively(treeItem.getValue()); 159 } 160 161 /** 162 * Returns the property name provided in the constructor. 163 * @return the property name provided in the constructor 164 */ 165 public final String getProperty() { return property; } 166 167 private ObservableValue<T> getCellDataReflectively(S rowData) { 168 if (getProperty() == null || getProperty().isEmpty() || rowData == null) return null; 169 170 try { 171 // we attempt to cache the property reference here, as otherwise 172 // performance suffers when working in large data models. For 173 // a bit of reference, refer to RT-13937. 174 if (columnClass == null || previousProperty == null || 175 ! columnClass.equals(rowData.getClass()) || 176 ! previousProperty.equals(getProperty())) { 177 178 // create a new PropertyReference 179 this.columnClass = rowData.getClass(); 180 this.previousProperty = getProperty(); 181 this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty()); 182 } 183 184 if (propertyRef != null) { 185 return propertyRef.getProperty(rowData); 186 } 187 } catch (RuntimeException e) { 188 try { 189 // attempt to just get the value 190 T value = propertyRef.get(rowData); 191 return new ReadOnlyObjectWrapper<T>(value); 192 } catch (RuntimeException e2) { 193 // fall through to logged exception below 194 } 195 196 // log the warning and move on 197 final PlatformLogger logger = Logging.getControlsLogger(); 198 if (logger.isLoggable(Level.WARNING)) { 199 logger.warning("Can not retrieve property '" + getProperty() + 200 "' in TreeItemPropertyValueFactory: " + this + 201 " with provided class type: " + rowData.getClass(), e); 202 } 203 propertyRef = null; 204 } 205 206 return null; 207 } 208 }