001 /*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2008-2017 the original author or authors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package griffon.javafx.scene.control;
019
020 import javafx.beans.value.ObservableValue;
021
022 import javax.annotation.Nonnull;
023 import javax.annotation.Nullable;
024 import java.lang.reflect.InvocationTargetException;
025 import java.lang.reflect.Method;
026 import java.util.Arrays;
027 import java.util.LinkedHashMap;
028 import java.util.Map;
029
030 import static griffon.util.GriffonClassUtils.requireState;
031 import static griffon.util.GriffonNameUtils.getNaturalName;
032 import static griffon.util.GriffonNameUtils.requireNonBlank;
033 import static java.lang.System.arraycopy;
034 import static java.util.Objects.requireNonNull;
035
036 /**
037 * @author Andres Almiray
038 * @since 2.11.0
039 */
040 public class DefaultTableFormat<E> implements TableViewFormat<E> {
041 public static class Column<E,T> {
042 private final String name;
043 private final String title;
044 private final Double size;
045 private final TableCellFactory<E,T> tableCellFactory;
046
047 public Column(@Nonnull String name) {
048 this(name, getNaturalName(name), null, null);
049 }
050
051 public Column(@Nonnull String name, @Nonnull String title) {
052 this(name, title, null, null);
053 }
054
055 public Column(@Nonnull String name, @Nonnull Double size) {
056 this(name, getNaturalName(name), size, null);
057 }
058
059 public Column(@Nonnull String name, @Nonnull TableCellFactory<E,T> tableCellFactory) {
060 this(name, getNaturalName(name), null, tableCellFactory);
061 }
062
063 public Column(@Nonnull String name, @Nonnull String title, @Nonnull Double size) {
064 this(name, title, size, null);
065 }
066
067 public Column(@Nonnull String name, @Nonnull String title, @Nonnull TableCellFactory<E,T> tableCellFactory) {
068 this(name, title, null, tableCellFactory);
069 }
070
071 public Column(@Nonnull String name, @Nonnull Double size, @Nonnull TableCellFactory<E,T> tableCellFactory) {
072 this(name, getNaturalName(name), size, tableCellFactory);
073 }
074
075 public Column(@Nonnull String name, @Nonnull String title, @Nullable Double size, @Nonnull TableCellFactory<E,T> tableCellFactory) {
076 this.name = requireNonBlank(name, "Argument 'name' must not be blank");
077 this.title = requireNonBlank(title, "Argument 'title' must not be blank");
078 if (size != null) {
079 requireState(size > 0, "Argument 'size' must be greater than 0.0d");
080 requireState(size <= 1, "Argument 'size' must be less than or equal to 1.0d");
081 }
082 this.size = size;
083 this.tableCellFactory = tableCellFactory;
084 }
085
086 @Nonnull
087 public String getName() {
088 return name;
089 }
090
091 @Nonnull
092 public String getTitle() {
093 return title;
094 }
095
096 @Nullable
097 public Double getSize() {
098 return size;
099 }
100
101 @Nullable
102 public TableCellFactory<E,T> getTableCellFactory() {
103 return tableCellFactory;
104 }
105 }
106
107 private final Column[] columns;
108 private final Map<Class<?>, Map<String, Method>> observableMetadata = new LinkedHashMap<>();
109
110 public DefaultTableFormat(@Nonnull String... names) {
111 requireNonNull(names, "Argument 'names' must not be null");
112 requireState(names.length > 0, "Column size must be greater than zero");
113 this.columns = new Column[names.length];
114 for (int i = 0; i < names.length; i++) {
115 this.columns[i] = new Column(names[i]);
116 }
117 }
118
119 public DefaultTableFormat(@Nonnull Column... columns) {
120 requireNonNull(columns, "Argument 'columns' must not be null");
121 requireState(columns.length > 0, "Column size must be greater than zero");
122 this.columns = new Column[columns.length];
123 arraycopy(columns, 0, this.columns, 0, columns.length);
124 }
125
126 @Override
127 public int getColumnCount() {
128 return columns.length;
129 }
130
131 @Nonnull
132 @Override
133 public String getColumnName(int index) {
134 return columns[index].getTitle();
135 }
136
137 @Nullable
138 @Override
139 public Double getColumnSize(int index) {
140 return columns[index].getSize();
141 }
142
143 @Nonnull
144 @Override
145 public ObservableValue<?> getObservableValue(@Nonnull E instance, int index) {
146 final String columnName = columns[index].getName();
147 Class<?> klass = instance.getClass();
148 Map<String, Method> metadata = observableMetadata.computeIfAbsent(klass, this::harvestMetadata);
149
150 try {
151 Method method = metadata.get(columnName);
152 if (method == null) {
153 throw new IllegalStateException("Could not find a method in " + klass +
154 " returning " + ObservableValue.class.getSimpleName() + " associated with column " + columnName);
155 }
156 return (ObservableValue<?>) method.invoke(instance);
157 } catch (IllegalAccessException e) {
158 throw new IllegalStateException(e);
159 } catch (InvocationTargetException e) {
160 throw new IllegalStateException(e.getTargetException());
161 }
162 }
163
164 @Nullable
165 @Override
166 public TableCellFactory getTableCellFactory(int index) {
167 return columns[index].getTableCellFactory();
168 }
169
170 @Nonnull
171 private Map<String, Method> harvestMetadata(@Nonnull Class<?> klass) {
172 Map<String, Method> map = new LinkedHashMap<>();
173
174 for (Method method : klass.getMethods()) {
175 if (ObservableValue.class.isAssignableFrom(method.getReturnType()) &&
176 method.getParameterCount() == 0) {
177 Arrays.stream(columns)
178 .map(Column::getName)
179 .filter(name -> method.getName().startsWith(name))
180 .findFirst()
181 .ifPresent(name -> map.put(name, method));
182 }
183 }
184
185 return map;
186 }
187 }
|