cleanup
[idea/community.git] / platform / util / src / com / intellij / openapi / util / DefaultJDOMExternalizer.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.util;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.util.BitUtil;
20 import com.intellij.util.ReflectionUtil;
21 import com.intellij.util.xmlb.annotations.Transient;
22 import org.jdom.Element;
23 import org.jdom.Verifier;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26
27 import java.awt.*;
28 import java.lang.reflect.Field;
29 import java.lang.reflect.Modifier;
30 import java.util.List;
31
32 /**
33  * @deprecated {@link com.intellij.util.xmlb.XmlSerializer} should be used instead
34  * @author mike
35  */
36 @SuppressWarnings("HardCodedStringLiteral")
37 public class DefaultJDOMExternalizer {
38   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.DefaultJDOMExternalizer");
39
40   private DefaultJDOMExternalizer() {
41   }
42
43   public interface JDOMFilter{
44     boolean isAccept(@NotNull Field field);
45   }
46
47   public static void writeExternal(@NotNull Object data, @NotNull Element parentNode) throws WriteExternalException {
48     writeExternal(data, parentNode, null);
49   }
50
51   public static void writeExternal(@NotNull Object data,
52                                    @NotNull Element parentNode,
53                                    @Nullable("null means all elements accepted") JDOMFilter filter) throws WriteExternalException {
54     Field[] fields = data.getClass().getFields();
55
56     for (Field field : fields) {
57       if (field.getName().indexOf('$') >= 0) continue;
58       int modifiers = field.getModifiers();
59       if (!(Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) &&
60           /*!Modifier.isFinal(modifiers) &&*/ !Modifier.isTransient(modifiers) &&
61           field.getAnnotation(Transient.class) == null)) continue;
62
63       field.setAccessible(true); // class might be non-public
64       Class type = field.getType();
65       if (filter != null && !filter.isAccept(field) || field.getDeclaringClass().getAnnotation(Transient.class) != null) {
66         continue;
67       }
68       String value = null;
69       try {
70         if (type.isPrimitive()) {
71           if (type.equals(byte.class)) {
72             value = Byte.toString(field.getByte(data));
73           }
74           else if (type.equals(short.class)) {
75             value = Short.toString(field.getShort(data));
76           }
77           else if (type.equals(int.class)) {
78             value = Integer.toString(field.getInt(data));
79           }
80           else if (type.equals(long.class)) {
81             value = Long.toString(field.getLong(data));
82           }
83           else if (type.equals(float.class)) {
84             value = Float.toString(field.getFloat(data));
85           }
86           else if (type.equals(double.class)) {
87             value = Double.toString(field.getDouble(data));
88           }
89           else if (type.equals(char.class)) {
90             value = String.valueOf(field.getChar(data));
91           }
92           else if (type.equals(boolean.class)) {
93             value = Boolean.toString(field.getBoolean(data));
94           }
95           else {
96             continue;
97           }
98         }
99         else if (type.equals(String.class)) {
100           value = filterXMLCharacters((String)field.get(data));
101         }
102         else if (type.isEnum()) {
103           value = field.get(data).toString();
104         }
105         else if (type.equals(Color.class)) {
106           Color color = (Color)field.get(data);
107           if (color != null) {
108             value = Integer.toString(color.getRGB() & 0xFFFFFF, 16);
109           }
110         }
111         else if (ReflectionUtil.isAssignable(JDOMExternalizable.class, type)) {
112           Element element = new Element("option");
113           parentNode.addContent(element);
114           element.setAttribute("name", field.getName());
115           JDOMExternalizable domValue = (JDOMExternalizable)field.get(data);
116           if (domValue != null) {
117             Element valueElement = new Element("value");
118             element.addContent(valueElement);
119             domValue.writeExternal(valueElement);
120           }
121           continue;
122         }
123         else {
124           LOG.debug("Wrong field type: " + type);
125           continue;
126         }
127       }
128       catch (IllegalAccessException e) {
129         continue;
130       }
131       Element element = new Element("option");
132       parentNode.addContent(element);
133       element.setAttribute("name", field.getName());
134       if (value != null) {
135         element.setAttribute("value", value);
136       }
137     }
138   }
139
140   @Nullable
141   static String filterXMLCharacters(String value) {
142     if (value != null) {
143       StringBuilder builder = null;
144       for (int i=0; i<value.length();i++) {
145         char c = value.charAt(i);
146         if (Verifier.isXMLCharacter(c)) {
147           if (builder != null) {
148             builder.append(c);
149           }
150         }
151         else {
152           if (builder == null) {
153             builder = new StringBuilder(value.length()+5);
154             builder.append(value, 0, i);
155           }
156         }
157       }
158       if (builder != null) {
159         value = builder.toString();
160       }
161     }
162     return value;
163   }
164
165   public static void readExternal(@NotNull Object data, Element parentNode) throws InvalidDataException{
166     if (parentNode == null) return;
167
168     for (final Object o : parentNode.getChildren("option")) {
169       Element e = (Element)o;
170
171       String fieldName = e.getAttributeValue("name");
172       if (fieldName == null) {
173         throw new InvalidDataException();
174       }
175       try {
176         Field field = data.getClass().getField(fieldName);
177         Class type = field.getType();
178         int modifiers = field.getModifiers();
179         if (!BitUtil.isSet(modifiers, Modifier.PUBLIC) || BitUtil.isSet(modifiers, Modifier.STATIC)) continue;
180         field.setAccessible(true); // class might be non-public
181         if (BitUtil.isSet(modifiers, Modifier.FINAL)) {
182           // read external contents of final field
183           Object value = field.get(data);
184           if (JDOMExternalizable.class.isInstance(value)) {
185             final List children = e.getChildren("value");
186             for (Object child : children) {
187               Element valueTag = (Element)child;
188               ((JDOMExternalizable)value).readExternal(valueTag);
189             }
190           }
191           continue;
192         }
193         String value = e.getAttributeValue("value");
194         if (type.isPrimitive()) {
195           if (value != null) {
196             if (type.equals(byte.class)) {
197               try {
198                 field.setByte(data, Byte.parseByte(value));
199               }
200               catch (NumberFormatException ex) {
201                 throw new InvalidDataException();
202               }
203             }
204             else if (type.equals(short.class)) {
205               try {
206                 field.setShort(data, Short.parseShort(value));
207               }
208               catch (NumberFormatException ex) {
209                 throw new InvalidDataException();
210               }
211             }
212             else if (type.equals(int.class)) {
213               int i = toInt(value);
214               field.setInt(data, i);
215             }
216             else if (type.equals(long.class)) {
217               try {
218                 field.setLong(data, Long.parseLong(value));
219               }
220               catch (NumberFormatException ex) {
221                 throw new InvalidDataException();
222               }
223             }
224             else if (type.equals(float.class)) {
225               try {
226                 field.setFloat(data, Float.parseFloat(value));
227               }
228               catch (NumberFormatException ex) {
229                 throw new InvalidDataException();
230               }
231             }
232             else if (type.equals(double.class)) {
233               try {
234                 field.setDouble(data, Double.parseDouble(value));
235               }
236               catch (NumberFormatException ex) {
237                 throw new InvalidDataException();
238               }
239             }
240             else if (type.equals(char.class)) {
241               if (value.length() != 1) {
242                 throw new InvalidDataException();
243               }
244               field.setChar(data, value.charAt(0));
245             }
246             else if (type.equals(boolean.class)) {
247               if (value.equals("true")) {
248                 field.setBoolean(data, true);
249               }
250               else if (value.equals("false")) {
251                 field.setBoolean(data, false);
252               }
253               else {
254                 throw new InvalidDataException();
255               }
256             }
257             else {
258               throw new InvalidDataException();
259             }
260           }
261         }
262         else if (type.isEnum()) {
263           for (Object enumValue : type.getEnumConstants()) {
264             if (enumValue.toString().equals(value)) {
265               field.set(data, enumValue);
266               break;
267             }
268           }
269         }
270         else if (type.equals(String.class)) {
271           field.set(data, value);
272         }
273         else if (type.equals(Color.class)) {
274           Color color = toColor(value);
275           field.set(data, color);
276         }
277         else if (ReflectionUtil.isAssignable(JDOMExternalizable.class, type)) {
278           final List<Element> children = e.getChildren("value");
279           if (!children.isEmpty()) {
280             // compatibility with Selena's serialization which writes an empty tag for a bean which has a default value
281             JDOMExternalizable object = null;
282             for (Element element : children) {
283               object = (JDOMExternalizable)type.newInstance();
284               object.readExternal(element);
285             }
286
287             field.set(data, object);
288           }
289         }
290         else {
291           throw new InvalidDataException("wrong type: " + type);
292         }
293       }
294       catch (NoSuchFieldException ex) {
295         LOG.debug("No field '" + fieldName + "' in " + data.getClass(), ex);
296       }
297       catch (SecurityException ex) {
298         throw new InvalidDataException();
299       }
300       catch (IllegalAccessException ex) {
301         throw new InvalidDataException(ex);
302       }
303       catch (InstantiationException ex) {
304         throw new InvalidDataException();
305       }
306     }
307   }
308
309   public static int toInt(@NotNull String value) throws InvalidDataException {
310     int i;
311     try {
312       i = Integer.parseInt(value);
313     }
314     catch (NumberFormatException ex) {
315       throw new InvalidDataException(value, ex);
316     }
317     return i;
318   }
319
320   public static Color toColor(@Nullable String value) throws InvalidDataException {
321     Color color;
322     if (value == null) {
323       color = null;
324     }
325     else {
326       try {
327         int rgb = Integer.parseInt(value, 16);
328         color = new Color(rgb);
329       }
330       catch (NumberFormatException ex) {
331         LOG.debug("Wrong color value: " + value, ex);
332         throw new InvalidDataException("Wrong color value: " + value, ex);
333       }
334     }
335     return color;
336   }
337 }