001package gudusoft.gsqlparser.dlineage.dataflow.metadata.sqlflow;
002
003import gudusoft.gsqlparser.dlineage.metadata.Coordinate;
004import gudusoft.gsqlparser.dlineage.metadata.Server;
005import gudusoft.gsqlparser.dlineage.metadata.Sqlflow;
006import gudusoft.gsqlparser.dlineage.metadata.TreeNode;
007import gudusoft.gsqlparser.util.json.JSON;
008import gudusoft.gsqlparser.util.json.JSONField;
009
010import java.lang.reflect.Array;
011import java.lang.reflect.Method;
012import java.lang.reflect.Modifier;
013import java.lang.reflect.ParameterizedType;
014import java.lang.reflect.Type;
015import java.util.ArrayList;
016import java.util.LinkedHashMap;
017import java.util.List;
018import java.util.Map;
019
020public final class SqlflowObjectAdapter {
021
022    private SqlflowObjectAdapter() {
023    }
024
025    public static Sqlflow fromJson(String json) {
026        return fromMap((Map) JSON.parseObject(json));
027    }
028
029    public static Sqlflow fromMap(Map metadata) {
030        Sqlflow sqlflow = bind(metadata, Sqlflow.class, null, null);
031        restoreTreeReferences(sqlflow, null, null);
032        return sqlflow;
033    }
034
035    private static <T> T bind(Map values, Class<T> type, TreeNode parent, Server server) {
036        try {
037            T target = type.newInstance();
038            for (Method method : type.getMethods()) {
039                if (!isSetter(method)) {
040                    continue;
041                }
042                String property = propertyName(method.getName());
043                Object value = values.get(property);
044                if (value == null) {
045                    continue;
046                }
047                method.invoke(target, bindValue(value, method.getParameterTypes()[0], method.getGenericParameterTypes()[0], parent, server));
048            }
049            return target;
050        } catch (Exception e) {
051            throw new IllegalArgumentException("Cannot bind sqlflow metadata " + type.getName(), e);
052        }
053    }
054
055    private static Object bindValue(Object value, Class<?> type, Type genericType, TreeNode parent, Server server) {
056        if (value == null) {
057            return null;
058        }
059        if (type == String.class || type == Boolean.class || type == boolean.class) {
060            return value;
061        }
062        if (type.isArray() && value instanceof String && type.getComponentType() == Coordinate.class) {
063            return Coordinate.parse((String) value);
064        }
065        if (value instanceof Number) {
066            Number number = (Number) value;
067            if (type == Integer.class || type == int.class) return number.intValue();
068            if (type == Long.class || type == long.class) return number.longValue();
069            if (type == Double.class || type == double.class) return number.doubleValue();
070            if (type == Float.class || type == float.class) return number.floatValue();
071            if (type == Short.class || type == short.class) return number.shortValue();
072            if (type == Byte.class || type == byte.class) return number.byteValue();
073        }
074        if (type.isArray()) {
075            List values = (List) value;
076            Object array = Array.newInstance(type.getComponentType(), values.size());
077            for (int i = 0; i < values.size(); i++) {
078                Array.set(array, i, bindValue(values.get(i), type.getComponentType(), type.getComponentType(), parent, server));
079            }
080            return array;
081        }
082        if (List.class.isAssignableFrom(type)) {
083            Type elementType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
084            Class<?> elementClass = (Class<?>) elementType;
085            List<Object> bound = new ArrayList<Object>();
086            for (Object item : (List) value) {
087                bound.add(bindValue(item, elementClass, elementClass, parent, server));
088            }
089            return bound;
090        }
091        return bind((Map) value, type, parent, server);
092    }
093
094    private static void restoreTreeReferences(Object value, TreeNode parent, Server server) {
095        if (value == null) {
096            return;
097        }
098        if (value instanceof Sqlflow) {
099            List<Server> servers = ((Sqlflow) value).getServers();
100            if (servers != null) {
101                for (Server item : servers) {
102                    restoreTreeReferences(item, null, item);
103                }
104            }
105            return;
106        }
107        if (value instanceof TreeNode) {
108            TreeNode node = (TreeNode) value;
109            node.setParent(parent);
110            node.setServer(server);
111            parent = node;
112        }
113        for (Method method : value.getClass().getMethods()) {
114            if (!isSerializableGetter(method)) {
115                continue;
116            }
117            try {
118                Object child = method.invoke(value);
119                if (child instanceof Iterable) {
120                    for (Object item : (Iterable) child) {
121                        restoreTreeReferences(item, parent, server);
122                    }
123                } else if (child != null && child.getClass().isArray()) {
124                    for (int i = 0; i < Array.getLength(child); i++) {
125                        restoreTreeReferences(Array.get(child, i), parent, server);
126                    }
127                } else if (child instanceof TreeNode) {
128                    restoreTreeReferences(child, parent, server);
129                }
130            } catch (Exception e) {
131                throw new IllegalArgumentException("Cannot restore sqlflow metadata references", e);
132            }
133        }
134    }
135
136    public static Map<String, Object> toMap(Object value) {
137        return (Map<String, Object>) convert(value);
138    }
139
140    private static Object convert(Object value) {
141        if (value == null || value instanceof String || value instanceof Number || value instanceof Boolean) {
142            return value;
143        }
144        if (value instanceof Enum) {
145            return ((Enum) value).name();
146        }
147        if (value instanceof Map) {
148            Map<Object, Object> converted = new LinkedHashMap<Object, Object>();
149            for (Object entryObject : ((Map) value).entrySet()) {
150                Map.Entry entry = (Map.Entry) entryObject;
151                converted.put(entry.getKey(), convert(entry.getValue()));
152            }
153            return converted;
154        }
155        if (value instanceof Iterable) {
156            List<Object> converted = new ArrayList<Object>();
157            for (Object item : (Iterable) value) {
158                converted.add(convert(item));
159            }
160            return converted;
161        }
162        if (value.getClass().isArray()) {
163            List<Object> converted = new ArrayList<Object>();
164            for (int i = 0; i < Array.getLength(value); i++) {
165                converted.add(convert(Array.get(value, i)));
166            }
167            return converted;
168        }
169
170        Map<String, Object> converted = new LinkedHashMap<String, Object>();
171        for (Method method : value.getClass().getMethods()) {
172            if (!isSerializableGetter(method)) {
173                continue;
174            }
175            try {
176                Object propertyValue = method.invoke(value);
177                if (propertyValue != null) {
178                    converted.put(propertyName(method.getName()), convert(propertyValue));
179                }
180            } catch (Exception e) {
181                throw new IllegalArgumentException("Cannot read sqlflow metadata property " + method.getName(), e);
182            }
183        }
184        return converted;
185    }
186
187    private static boolean isSetter(Method method) {
188        return Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("set")
189                && method.getParameterTypes().length == 1;
190    }
191
192    private static boolean isSerializableGetter(Method method) {
193        if (!Modifier.isPublic(method.getModifiers()) || method.getParameterTypes().length != 0
194                || "getClass".equals(method.getName()) || (!method.getName().startsWith("get")
195                && !method.getName().startsWith("is"))) {
196            return false;
197        }
198        JSONField jsonField = method.getAnnotation(JSONField.class);
199        if (jsonField != null && !jsonField.serialize()) {
200            return false;
201        }
202        return true;
203    }
204
205    private static String propertyName(String getterName) {
206        int prefixLength = getterName.startsWith("is") ? 2 : 3;
207        String name = getterName.substring(prefixLength);
208        return Character.toLowerCase(name.charAt(0)) + name.substring(1);
209    }
210}