/* * Copyright 2013 David Tinker * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.qdb.server.databind; import io.qdb.server.controller.JsonService; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; /** * Binds keys and values from a map to objects. Accumulates errors. */ public class DataBinder { private final JsonService jsonService; private boolean ignoreInvalidFields; private boolean updateMap; private Map<String, String> errors; public DataBinder(JsonService jsonService) { this.jsonService = jsonService; } public DataBinder ignoreInvalidFields(boolean on) { this.ignoreInvalidFields = on; return this; } /** If true then converted data from the map is put back in the map. */ public DataBinder updateMap(boolean on) { this.updateMap = on; return this; } /** * Bind the values of map to matching public field names from dto. Support basic data types and does conversions * from String to these types. If dto implements {@link HasAnySetter} the keys from the map that do not match * field names are passed to {@link HasAnySetter#set(String, Object)}. */ @SuppressWarnings("unchecked") public DataBinder bind(Map map, Object dto) { Class<?> cls = dto.getClass(); for (Object o : map.entrySet()) { Map.Entry e = (Map.Entry)o; String key = (String)e.getKey(); Object v = e.getValue(); Field f; try { f = cls.getField(key); } catch (NoSuchFieldException x) { if (dto instanceof HasAnySetter) { ((HasAnySetter)dto).set(key, v); } else if (!ignoreInvalidFields) { error(key, "Unknown field"); } continue; } Class t = f.getType(); if (v instanceof String && t != String.class) { String s = (String)v; try { if (t == Integer.TYPE || t == Integer.class) v = IntegerParser.INSTANCE.parseInt(s); else if (t == Long.TYPE || t == Long.class) v = IntegerParser.INSTANCE.parseLong(s); else if (t == Boolean.TYPE || t == Boolean.class) v = "true".equals(v); else if (t == Date.class) v = DateTimeParser.INSTANCE.parse(s); else if (t == String[].class) v = parseStringArray(s); else if (t == Double.TYPE || t == Double.class) v = Double.parseDouble(s); } catch (Exception x) { error(key, "Invalid value, expected " + t.getSimpleName() + ": [" + v + "]"); continue; } if (updateMap) map.put(key, v); } else if (t.isArray() && v != null) { Class vt = v.getClass(); if (vt.isArray() && vt.getComponentType() == Object.class) { if (t.getComponentType() == String.class) { Object[] va = (Object[])v; String[] sa = new String[va.length]; for (int i = 0; i < va.length; i++) sa[i] = (String)va[i]; v = sa; } } } try { f.set(dto, v); } catch (Exception x) { error(key, "Invalid value, expected " + f.getType().getSimpleName() + ": [" + v + "]"); } } return this; } private String[] parseStringArray(String s) throws IOException { if (s.length() == 0) return new String[0]; if (s.charAt(0) == '[') return jsonService.fromJson(new ByteArrayInputStream(s.getBytes("UTF8")), String[].class); return s.split("[\\s]*,[\\s]*"); } private void error(String field, String message) { if (errors == null) errors = new LinkedHashMap<String, String>(); errors.put(field, message); } /** * If there are any errors throw a {@link DataBindingException}. */ public void check() throws DataBindingException { if (errors != null) throw new DataBindingException(errors); } }