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    }