001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 */
019package org.nuxeo.common.xmap;
020
021import java.io.File;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.text.DateFormat;
025import java.text.ParseException;
026import java.time.Duration;
027import java.util.Date;
028import java.util.Hashtable;
029import java.util.Map;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033import org.w3c.dom.Node;
034
035/**
036 * Value factories are used to decode values from XML strings.
037 * <p>
038 * To register a new factory for a given XMap instance use the method
039 * {@link XMap#setValueFactory(Class, XValueFactory)}.
040 *
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public abstract class XValueFactory {
044
045    static final Map<Class<?>, XValueFactory> defaultFactories = new Hashtable<>();
046
047    public abstract Object deserialize(Context context, String value);
048
049    public abstract String serialize(Context context, Object value);
050
051    public final Object getElementValue(Context context, Node element, boolean trim) {
052        String text = element.getTextContent();
053        return deserialize(context, trim ? text.trim() : text);
054    }
055
056    public final Object getAttributeValue(Context context, Node element, String name) {
057        Node at = element.getAttributes().getNamedItem(name);
058        return at != null ? deserialize(context, at.getNodeValue()) : null;
059    }
060
061    public static void addFactory(Class klass, XValueFactory factory) {
062        defaultFactories.put(klass, factory);
063    }
064
065    public static XValueFactory getFactory(Class type) {
066        return defaultFactories.get(type);
067    }
068
069    public static Object getValue(Context context, Class klass, String value) {
070        XValueFactory factory = defaultFactories.get(klass);
071        if (factory == null) {
072            return null;
073        }
074        return factory.deserialize(context, value);
075    }
076
077    public static final XValueFactory STRING = new XValueFactory() {
078        @Override
079        public Object deserialize(Context context, String value) {
080            return value;
081        }
082
083        @Override
084        public String serialize(Context context, Object value) {
085            return value.toString();
086        }
087    };
088
089    public static final XValueFactory INTEGER = new XValueFactory() {
090        @Override
091        public Object deserialize(Context context, String value) {
092            return Integer.valueOf(value);
093        }
094
095        @Override
096        public String serialize(Context context, Object value) {
097            return value.toString();
098        }
099    };
100
101    public static final XValueFactory LONG = new XValueFactory() {
102        @Override
103        public Object deserialize(Context context, String value) {
104            return Long.valueOf(value);
105        }
106
107        @Override
108        public String serialize(Context context, Object value) {
109            return value.toString();
110        }
111    };
112
113    public static final XValueFactory DOUBLE = new XValueFactory() {
114        @Override
115        public Object deserialize(Context context, String value) {
116            return Double.valueOf(value);
117        }
118
119        @Override
120        public String serialize(Context context, Object value) {
121            return value.toString();
122        }
123    };
124
125    public static final XValueFactory FLOAT = new XValueFactory() {
126        @Override
127        public Object deserialize(Context context, String value) {
128            return Float.valueOf(value);
129        }
130
131        @Override
132        public String serialize(Context context, Object value) {
133            return value.toString();
134        }
135    };
136
137    public static final XValueFactory BOOLEAN = new XValueFactory() {
138        @Override
139        public Object deserialize(Context context, String value) {
140            return Boolean.valueOf(value);
141        }
142
143        @Override
144        public String serialize(Context context, Object value) {
145            return value.toString();
146        }
147    };
148
149    public static final XValueFactory DATE = new XValueFactory() {
150        private final DateFormat df = DateFormat.getDateInstance();
151
152        @Override
153        public Object deserialize(Context context, String value) {
154            try {
155                return df.parse(value);
156            } catch (ParseException e) {
157                return null;
158            }
159        }
160
161        @Override
162        public String serialize(Context context, Object value) {
163            Date date = (Date) value;
164            return df.format(date);
165        }
166    };
167
168    public static final XValueFactory FILE = new XValueFactory() {
169        @Override
170        public Object deserialize(Context context, String value) {
171            return new File(value);
172        }
173
174        @Override
175        public String serialize(Context context, Object value) {
176            File file = (File) value;
177            return file.getName();
178        }
179    };
180
181    public static final XValueFactory URL = new XValueFactory() {
182        @Override
183        public Object deserialize(Context context, String value) {
184            try {
185                return new URL(value);
186            } catch (MalformedURLException e) {
187                return null;
188            }
189        }
190
191        @Override
192        public String serialize(Context context, Object value) {
193            return value.toString();
194        }
195    };
196
197    public static final XValueFactory CLASS = new XValueFactory() {
198        @Override
199        public Object deserialize(Context context, String value) {
200            try {
201                return context.loadClass(value);
202            } catch (ClassNotFoundException e) {
203                throw new XMapException("Cannot load class: " + value, e);
204            }
205        }
206
207        @Override
208        public String serialize(Context context, Object value) {
209            Class<?> clazz = (Class<?>) value;
210            return clazz.getName();
211        }
212    };
213
214    public static final XValueFactory RESOURCE = new XValueFactory() {
215        @Override
216        public Object deserialize(Context context, String value) {
217            return new Resource(context.getResource(value));
218        }
219
220        @Override
221        public String serialize(Context context, Object value) {
222            return value.toString();
223        }
224    };
225
226    public static final Pattern DURATION_SIMPLE_FORMAT = Pattern.compile(
227            "(?:(\\d+)d)?(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?(?:(\\d+)ms)?");
228
229    public static final XValueFactory DURATION = new XValueFactory() {
230
231        @Override
232        public Object deserialize(Context context, String value) {
233            if (value.startsWith("P") || value.startsWith("-P")) {
234                // Duration JDK format
235                return Duration.parse(value);
236            }
237            Matcher matcher = DURATION_SIMPLE_FORMAT.matcher(value);
238            if (matcher.matches()) {
239
240                long days = 0;
241                long hours = 0;
242                long minutes = 0;
243                long seconds = 0;
244                long millis = 0;
245                if (matcher.group(1) != null) {
246                    days = Long.parseLong(matcher.group(1));
247                }
248                if (matcher.group(2) != null) {
249                    hours = Long.parseLong(matcher.group(2));
250                }
251                if (matcher.group(3) != null) {
252                    minutes = Long.parseLong(matcher.group(3));
253                }
254                if (matcher.group(4) != null) {
255                    seconds = Long.parseLong(matcher.group(4));
256                }
257                if (matcher.group(5) != null) {
258                    millis = Long.parseLong(matcher.group(5));
259                }
260                return Duration.ofDays(days).plusHours(hours).plusMinutes(minutes).plusSeconds(seconds).plusMillis(
261                        millis);
262            }
263            throw new RuntimeException("Unable to read Duration=" + value);
264        }
265
266        @Override
267        public String serialize(Context context, Object value) {
268            // always use JDK format
269            return value.toString();
270        }
271    };
272
273    static {
274        addFactory(String.class, STRING);
275        addFactory(Integer.class, INTEGER);
276        addFactory(Long.class, LONG);
277        addFactory(Double.class, DOUBLE);
278        addFactory(Date.class, DATE);
279        addFactory(Boolean.class, BOOLEAN);
280        addFactory(File.class, FILE);
281        addFactory(URL.class, URL);
282
283        addFactory(int.class, INTEGER);
284        addFactory(long.class, LONG);
285        addFactory(double.class, DOUBLE);
286        addFactory(float.class, FLOAT);
287        addFactory(boolean.class, BOOLEAN);
288
289        addFactory(Class.class, CLASS);
290        addFactory(Resource.class, RESOURCE);
291
292        addFactory(Duration.class, DURATION);
293    }
294
295}