001 /*
002 GRANITE DATA SERVICES
003 Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004
005 This file is part of Granite Data Services.
006
007 Granite Data Services is free software; you can redistribute it and/or modify
008 it under the terms of the GNU Library General Public License as published by
009 the Free Software Foundation; either version 2 of the License, or (at your
010 option) any later version.
011
012 Granite Data Services is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 for more details.
016
017 You should have received a copy of the GNU Library General Public License
018 along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020
021 package org.granite.client.javafx;
022
023 import java.beans.PropertyDescriptor;
024 import java.io.IOException;
025 import java.io.ObjectInput;
026 import java.io.ObjectOutput;
027 import java.lang.reflect.Field;
028 import java.lang.reflect.Method;
029 import java.lang.reflect.Modifier;
030 import java.util.ArrayList;
031 import java.util.Arrays;
032 import java.util.Collections;
033 import java.util.Comparator;
034 import java.util.HashSet;
035 import java.util.List;
036 import java.util.Set;
037
038 import javafx.beans.value.ObservableValue;
039
040 import org.granite.client.tide.PropertyHolder;
041 import org.granite.client.tide.data.Id;
042 import org.granite.client.util.BeanUtil;
043 import org.granite.context.GraniteContext;
044 import org.granite.logging.Logger;
045 import org.granite.messaging.amf.io.convert.Converters;
046 import org.granite.messaging.amf.io.util.FieldProperty;
047 import org.granite.messaging.amf.io.util.MethodProperty;
048 import org.granite.messaging.amf.io.util.Property;
049 import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
050 import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedProperty;
051 import org.granite.messaging.amf.io.util.externalizer.annotation.IgnoredProperty;
052 import org.granite.messaging.service.annotations.IgnoredMethod;
053
054 /**
055 * @author William DRAI
056 */
057 public class JavaFXExternalizer extends DefaultExternalizer {
058
059 private static final Logger log = Logger.getLogger(JavaFXExternalizer.class);
060
061
062 public List<Property> findOrderedFields(final Class<?> clazz, boolean returnSettersWhenAvailable) {
063 List<Property> fields = orderedFields.get(clazz);
064
065 if (fields == null) {
066 PropertyDescriptor[] propertyDescriptors = BeanUtil.getProperties(clazz);
067 Converters converters = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters();
068
069 fields = new ArrayList<Property>();
070
071 Set<String> allFieldNames = new HashSet<String>();
072 allFieldNames.add("__initialized");
073 allFieldNames.add("__detachedState");
074 allFieldNames.add("__dirty");
075 allFieldNames.add("__handlerManager");
076
077 for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
078
079 List<Property> newFields = new ArrayList<Property>();
080
081 for (Method method : c.getDeclaredMethods()) {
082 if (method.getName().endsWith("Property") && method.getParameterTypes().length == 0 && ObservableValue.class.isAssignableFrom(method.getReturnType())) {
083 String propertyName = method.getName().substring(0, method.getName().length()-8);
084 if (!allFieldNames.contains(propertyName) &&
085 !Modifier.isTransient(method.getModifiers()) &&
086 !Modifier.isStatic(method.getModifiers()) &&
087 !isPropertyIgnored(method) &&
088 !method.isAnnotationPresent(IgnoredMethod.class)) {
089
090 PropertyDescriptor pdesc = null;
091 for (PropertyDescriptor pd : propertyDescriptors) {
092 if (pd.getName().equals(propertyName)) {
093 pdesc = pd;
094 break;
095 }
096 }
097 newFields.add(new JavaFXProperty(converters, propertyName, method, pdesc != null ? pdesc.getReadMethod() : null, pdesc != null ? pdesc.getWriteMethod() : null));
098 allFieldNames.add(propertyName);
099 }
100 }
101 }
102
103 // Standard declared fields.
104 for (Field field : c.getDeclaredFields()) {
105 if (!allFieldNames.contains(field.getName()) &&
106 !Modifier.isTransient(field.getModifiers()) &&
107 !Modifier.isStatic(field.getModifiers()) &&
108 !isPropertyIgnored(field) &&
109 !field.isAnnotationPresent(IgnoredProperty.class)) {
110
111 boolean found = false;
112 if (returnSettersWhenAvailable && propertyDescriptors != null) {
113 for (PropertyDescriptor pd : propertyDescriptors) {
114 if (pd.getName().equals(field.getName()) && pd.getWriteMethod() != null) {
115 newFields.add(new MethodProperty(converters, field.getName(), pd.getWriteMethod(), pd.getReadMethod()));
116 found = true;
117 break;
118 }
119 }
120 }
121 if (!found)
122 newFields.add(new FieldProperty(converters, field));
123 }
124 allFieldNames.add(field.getName());
125 }
126
127 // Getter annotated by @ExternalizedProperty.
128 if (propertyDescriptors != null) {
129 for (PropertyDescriptor property : propertyDescriptors) {
130 Method getter = property.getReadMethod();
131 if (getter != null &&
132 getter.isAnnotationPresent(ExternalizedProperty.class) &&
133 getter.getDeclaringClass().equals(c) &&
134 !allFieldNames.contains(property.getName())) {
135
136 newFields.add(new MethodProperty(converters, property.getName(), null, getter));
137 allFieldNames.add(property.getName());
138 }
139 }
140 }
141
142 try {
143 Field f = c.getDeclaredField("__externalizedProperties");
144 f.setAccessible(true);
145 final List<String> externalizedProperties = Arrays.asList((String[])f.get(null));
146 Collections.sort(newFields, new Comparator<Property>() {
147 public int compare(Property o1, Property o2) {
148 return externalizedProperties.indexOf(o1.getName()) - externalizedProperties.indexOf(o2.getName());
149 }
150 });
151 }
152 catch (NoSuchFieldException e) {
153 Collections.sort(newFields, new Comparator<Property>() {
154 public int compare(Property o1, Property o2) {
155 return o1.getName().compareTo(o2.getName());
156 }
157 });
158 }
159 catch (Exception e) {
160 throw new RuntimeException("Could not get value of __externalizedProperties", e);
161 }
162
163 fields.addAll(0, newFields);
164 }
165
166 List<Property> previousFields = (returnSettersWhenAvailable ? orderedSetterFields : orderedFields).putIfAbsent(clazz, fields);
167 if (previousFields != null)
168 fields = previousFields;
169 }
170
171 return fields;
172 }
173
174 @Override
175 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
176
177 Field initializedField = null;
178 Field detachedStateField = null;
179 Class<?> clazz = o.getClass();
180 while (clazz != null && clazz != Object.class) {
181 try {
182 initializedField = clazz.getDeclaredField("__initialized");
183 }
184 catch (NoSuchFieldException e) {
185 }
186 try {
187 detachedStateField = clazz.getDeclaredField("__detachedState");
188 }
189 catch (NoSuchFieldException e) {
190 }
191 clazz = clazz.getSuperclass();
192 }
193
194 boolean initialized = true;
195
196 if (initializedField != null && detachedStateField != null) {
197 // Read initialized flag.
198 initialized = ((Boolean)in.readObject()).booleanValue();
199
200 // Read detachedState.
201 String detachedState = (String)in.readObject();
202
203 initializedField.setAccessible(true);
204 initializedField.set(o, initialized);
205 detachedStateField.setAccessible(true);
206 detachedStateField.set(o, detachedState);
207 }
208
209 if (initialized) {
210 List<Property> fields = findOrderedFields(o.getClass(), false);
211 log.debug("Reading entity %s with fields %s", o.getClass().getName(), fields);
212 for (Property field : fields) {
213 Object value = in.readObject();
214 field.setProperty(o, value, true);
215 }
216 }
217 else {
218 List<Property> fields = findOrderedFields(o.getClass(), false);
219 log.debug("Reading entity %s with fields %s", o.getClass().getName(), fields);
220 for (Property field : fields) {
221 if (!field.isAnnotationPresent(Id.class))
222 continue;
223 Object value = in.readObject();
224 field.setProperty(o, value, true);
225 }
226 }
227 }
228
229 @Override
230 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
231
232 boolean initialized = true;
233 String detachedState = null;
234
235 Field initializedField = null;
236 Field detachedStateField = null;
237 Class<?> clazz = o.getClass();
238 while (clazz != null && clazz != Object.class) {
239 try {
240 initializedField = clazz.getDeclaredField("__initialized");
241 initializedField.setAccessible(true);
242 initialized = initializedField.getBoolean(o);
243 }
244 catch (NoSuchFieldException e) {
245 }
246 try {
247 detachedStateField = clazz.getDeclaredField("__detachedState");
248 detachedStateField.setAccessible(true);
249 detachedState = (String)detachedStateField.get(o);
250 }
251 catch (NoSuchFieldException e) {
252 }
253 clazz = clazz.getSuperclass();
254 }
255
256 if (initializedField != null && detachedStateField != null) {
257 if (!initialized) {
258 // Write initialized flag.
259 out.writeObject(Boolean.FALSE);
260 // Write detachedState.
261 out.writeObject(detachedState);
262
263 for (Property field : findOrderedFields(o.getClass(), false)) {
264 if (field.isAnnotationPresent(Id.class)) {
265 // Write entity id.
266 out.writeObject(field.getProperty(o));
267 break;
268 }
269 }
270 return;
271 }
272
273 // Write initialized flag.
274 out.writeObject(Boolean.TRUE);
275 // Write detachedState.
276 out.writeObject(detachedState);
277 }
278
279 // Externalize entity fields.
280 List<Property> fields = findOrderedFields(o.getClass(), false);
281 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
282 for (Property field : fields) {
283 Object value = field.getProperty(o);
284
285 if (value != null && value instanceof PropertyHolder)
286 value = ((PropertyHolder)value).getObject();
287
288 if (isValueIgnored(value))
289 out.writeObject(null);
290 else
291 out.writeObject(value);
292 }
293 }
294 }